From 221f0c8ef810cac36e8fd31f6cb44688b5996cf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Wed, 1 Nov 2017 20:14:23 +0100 Subject: [PATCH] Repo init with mostly unchanged import of LW code --- .gitignore | 3 + .vim.local/bundles-init/YouCompleteMe.cfg.vim | 1 + .../bundles-init/YouCompleteMe.load.vim | 1 + .../bundles-init/vim-uncrustify.cfg.vim | 2 + .../bundles-init/vim-uncrustify.load.vim | 1 + .vim.local/bundles-init/vimmake.cfg.vim | 42 + .vim.local/bundles-init/vimmake.load.vim | 1 + .vim.local/ftplugin/cpp.vim | 3 + .vim.local/ftplugin/srd.vim | 1 + .vim.local/scripts/vimmake.build | 2 + .vim.local/scripts/vimmake.clean | 2 + .vim.local/scripts/vimmake.rebuild | 3 + .vim.local/scripts/vimmake.test | 14 + .vim.local/syntax/srd.vim | 1 + .vim.local/vimrc | 7 + .vim.local/ycm_extra_conf.py | 143 + include/ebcl/Alloc.hh | 240 ++ include/ebcl/Arrays.hh | 446 +++ include/ebcl/BinaryStreams.hh | 169 ++ include/ebcl/Buffers.hh | 104 + include/ebcl/Config.hh | 25 + include/ebcl/DynLib.hh | 48 + include/ebcl/Externals.hh | 19 + include/ebcl/Files.hh | 187 ++ include/ebcl/GameLoop.hh | 30 + include/ebcl/HashIndex.hh | 79 + include/ebcl/HashTables.hh | 203 ++ include/ebcl/MemoryStreams.hh | 62 + include/ebcl/Messages.hh | 282 ++ include/ebcl/Mods.hh | 247 ++ include/ebcl/Pointers.hh | 423 +++ include/ebcl/Registration.hh | 85 + include/ebcl/SRDBinary.hh | 78 + include/ebcl/SRDData.hh | 361 +++ include/ebcl/SRDDefinitions.hh | 321 ++ include/ebcl/SRDIO.hh | 187 ++ include/ebcl/SRDPPCommands.hh | 101 + include/ebcl/SRDParser.hh | 90 + include/ebcl/SRDParserConfig.hh | 99 + include/ebcl/SRDText.hh | 93 + include/ebcl/Streams.hh | 110 + include/ebcl/Strings.hh | 457 +++ include/ebcl/Threading.hh | 150 + include/ebcl/Types.hh | 498 ++++ include/ebcl/Utilities.hh | 393 +++ include/ebcl/Version.hh | 19 + include/ebcl/Version.hh.in | 19 + include/ebcl/inline/Alloc.hh | 376 +++ include/ebcl/inline/Arrays.hh | 1594 ++++++++++ include/ebcl/inline/BinaryStreams.hh | 145 + include/ebcl/inline/Buffers.hh | 263 ++ include/ebcl/inline/DynLib.hh | 30 + include/ebcl/inline/Files.hh | 99 + include/ebcl/inline/HashIndex.hh | 29 + include/ebcl/inline/HashTables.hh | 421 +++ include/ebcl/inline/MemoryStreams.hh | 43 + include/ebcl/inline/Messages.hh | 154 + include/ebcl/inline/Mods.hh | 59 + include/ebcl/inline/Pointers.hh | 854 ++++++ include/ebcl/inline/Registration.hh | 124 + include/ebcl/inline/SRDBinary.hh | 33 + include/ebcl/inline/SRDData.hh | 438 +++ include/ebcl/inline/SRDDefinitions.hh | 525 ++++ include/ebcl/inline/SRDIO.hh | 80 + include/ebcl/inline/SRDParserConfig.hh | 33 + include/ebcl/inline/SRDText.hh | 25 + include/ebcl/inline/Streams.hh | 98 + include/ebcl/inline/Strings.hh | 591 ++++ include/ebcl/inline/Threading.hh | 352 +++ include/ebcl/inline/Types.hh | 1000 +++++++ include/ebcl/inline/Utilities.hh | 303 ++ src/Console-Unix.hh | 667 +++++ src/Console.cc | 1346 +++++++++ src/ConsoleLogWriter.cc | 83 + src/CwdFileLogger.cc | 153 + src/DynLib.cc | 135 + src/Files.cc | 326 ++ src/GameLoop.cc | 140 + src/HashIndex.cc | 231 ++ src/LW.cc | 603 ++++ src/Log.cc | 1041 +++++++ src/MemoryStreams.cc | 60 + src/Messages.cc | 86 + src/ModInterface.cc | 20 + src/Mods.cc | 2646 +++++++++++++++++ src/Pointers.cc | 103 + src/SRDBinary.cc | 567 ++++ src/SRDData.cc | 657 ++++ src/SRDDefinitions.cc | 297 ++ src/SRDIO.cc | 231 ++ src/SRDPPCommands.cc | 2518 ++++++++++++++++ src/SRDParser.cc | 686 +++++ src/SRDParserConfig.cc | 982 ++++++ src/SRDPreproc.cc | 1416 +++++++++ src/SRDText.cc | 1329 +++++++++ src/Streams.cc | 42 + src/Strings.cc | 2015 +++++++++++++ src/TemplateInstantiation.cc | 32 + src/TextFileLogger.cc | 159 + src/Utilities.cc | 160 + src/VFS.cc | 930 ++++++ src/VFSDrivers.cc | 614 ++++ src/files.mk | 36 + tests/alloc-pool.cc | 332 +++ tests/arrays-auto.cc | 920 ++++++ tests/arrays-basic.cc | 506 ++++ tests/arrays-multi.cc | 867 ++++++ tests/arrays-objects.cc | 875 ++++++ tests/arrays-static-basic.cc | 468 +++ tests/arrays-static-objects.cc | 745 +++++ tests/binary-reader.cc | 164 + tests/binary-writer.cc | 228 ++ tests/buffers.cc | 319 ++ tests/console-edit.cc | 468 +++ tests/console-text.cc | 405 +++ tests/data/inputstream.txt | 1 + tests/hash-functions.cc | 83 + tests/hash-index.cc | 276 ++ tests/key-value-table.cc | 414 +++ tests/list.mk | 53 + tests/log-data.cc | 649 ++++ tests/object-table.cc | 517 ++++ tests/optional.cc | 287 ++ tests/ptr-common.hh | 28 + tests/ptr-owned.cc | 279 ++ tests/ptr-shared.cc | 390 +++ tests/ptr-weak.cc | 486 +++ tests/ring-buffer.cc | 357 +++ tests/run-test.cc | 13 + tests/srd-bin-reader.cc | 559 ++++ tests/srd-bin-writer.cc | 245 ++ tests/srd-lexer.cc | 687 +++++ tests/srd-mem-target.cc | 195 ++ tests/srd-parser-cfg.cc | 784 +++++ tests/srd-parser-defs.cc | 880 ++++++ tests/srd-parser.cc | 606 ++++ tests/srd-preproc-cmd-arithmetic.cc | 877 ++++++ tests/srd-preproc-cmd-casts.cc | 1100 +++++++ tests/srd-preproc-cmd-common.hh | 108 + tests/srd-preproc-cmd-compare.cc | 340 +++ tests/srd-preproc-cmd-core.cc | 1303 ++++++++ tests/srd-preproc-cmd-functions.cc | 441 +++ tests/srd-preproc-cmd-input.cc | 543 ++++ tests/srd-preproc-cmd-introspect.cc | 322 ++ tests/srd-preproc-cmd-logic.cc | 609 ++++ tests/srd-preproc-cmd-macros.cc | 243 ++ tests/srd-preproc-cmd-misc.cc | 165 + tests/srd-preproc-cmd-strings.cc | 931 ++++++ tests/srd-preproc-cmd-variables.cc | 372 +++ tests/srd-preproc-core.cc | 245 ++ tests/srd-preproc-location.hh | 133 + tests/srd-preproc-tracking.cc | 676 +++++ tests/srd-text-writer.cc | 331 +++ tests/stream-file-input.cc | 268 ++ tests/strings-builder.cc | 1101 +++++++ tests/strings-iterator.cc | 156 + tests/strings-storage.cc | 328 ++ tests/strings.cc | 1184 ++++++++ tests/union.cc | 386 +++ tests/variant.cc | 1016 +++++++ tests/vfs.cc | 318 ++ 161 files changed, 61414 insertions(+) create mode 100644 .gitignore create mode 100644 .vim.local/bundles-init/YouCompleteMe.cfg.vim create mode 100644 .vim.local/bundles-init/YouCompleteMe.load.vim create mode 100644 .vim.local/bundles-init/vim-uncrustify.cfg.vim create mode 100644 .vim.local/bundles-init/vim-uncrustify.load.vim create mode 100644 .vim.local/bundles-init/vimmake.cfg.vim create mode 100644 .vim.local/bundles-init/vimmake.load.vim create mode 100644 .vim.local/ftplugin/cpp.vim create mode 120000 .vim.local/ftplugin/srd.vim create mode 100755 .vim.local/scripts/vimmake.build create mode 100755 .vim.local/scripts/vimmake.clean create mode 100755 .vim.local/scripts/vimmake.rebuild create mode 100755 .vim.local/scripts/vimmake.test create mode 120000 .vim.local/syntax/srd.vim create mode 100644 .vim.local/vimrc create mode 100644 .vim.local/ycm_extra_conf.py create mode 100644 include/ebcl/Alloc.hh create mode 100644 include/ebcl/Arrays.hh create mode 100644 include/ebcl/BinaryStreams.hh create mode 100644 include/ebcl/Buffers.hh create mode 100644 include/ebcl/Config.hh create mode 100644 include/ebcl/DynLib.hh create mode 100644 include/ebcl/Externals.hh create mode 100644 include/ebcl/Files.hh create mode 100644 include/ebcl/GameLoop.hh create mode 100644 include/ebcl/HashIndex.hh create mode 100644 include/ebcl/HashTables.hh create mode 100644 include/ebcl/MemoryStreams.hh create mode 100644 include/ebcl/Messages.hh create mode 100644 include/ebcl/Mods.hh create mode 100644 include/ebcl/Pointers.hh create mode 100644 include/ebcl/Registration.hh create mode 100644 include/ebcl/SRDBinary.hh create mode 100644 include/ebcl/SRDData.hh create mode 100644 include/ebcl/SRDDefinitions.hh create mode 100644 include/ebcl/SRDIO.hh create mode 100644 include/ebcl/SRDPPCommands.hh create mode 100644 include/ebcl/SRDParser.hh create mode 100644 include/ebcl/SRDParserConfig.hh create mode 100644 include/ebcl/SRDText.hh create mode 100644 include/ebcl/Streams.hh create mode 100644 include/ebcl/Strings.hh create mode 100644 include/ebcl/Threading.hh create mode 100644 include/ebcl/Types.hh create mode 100644 include/ebcl/Utilities.hh create mode 100644 include/ebcl/Version.hh create mode 100644 include/ebcl/Version.hh.in create mode 100644 include/ebcl/inline/Alloc.hh create mode 100644 include/ebcl/inline/Arrays.hh create mode 100644 include/ebcl/inline/BinaryStreams.hh create mode 100644 include/ebcl/inline/Buffers.hh create mode 100644 include/ebcl/inline/DynLib.hh create mode 100644 include/ebcl/inline/Files.hh create mode 100644 include/ebcl/inline/HashIndex.hh create mode 100644 include/ebcl/inline/HashTables.hh create mode 100644 include/ebcl/inline/MemoryStreams.hh create mode 100644 include/ebcl/inline/Messages.hh create mode 100644 include/ebcl/inline/Mods.hh create mode 100644 include/ebcl/inline/Pointers.hh create mode 100644 include/ebcl/inline/Registration.hh create mode 100644 include/ebcl/inline/SRDBinary.hh create mode 100644 include/ebcl/inline/SRDData.hh create mode 100644 include/ebcl/inline/SRDDefinitions.hh create mode 100644 include/ebcl/inline/SRDIO.hh create mode 100644 include/ebcl/inline/SRDParserConfig.hh create mode 100644 include/ebcl/inline/SRDText.hh create mode 100644 include/ebcl/inline/Streams.hh create mode 100644 include/ebcl/inline/Strings.hh create mode 100644 include/ebcl/inline/Threading.hh create mode 100644 include/ebcl/inline/Types.hh create mode 100644 include/ebcl/inline/Utilities.hh create mode 100644 src/Console-Unix.hh create mode 100644 src/Console.cc create mode 100644 src/ConsoleLogWriter.cc create mode 100644 src/CwdFileLogger.cc create mode 100644 src/DynLib.cc create mode 100644 src/Files.cc create mode 100644 src/GameLoop.cc create mode 100644 src/HashIndex.cc create mode 100644 src/LW.cc create mode 100644 src/Log.cc create mode 100644 src/MemoryStreams.cc create mode 100644 src/Messages.cc create mode 100644 src/ModInterface.cc create mode 100644 src/Mods.cc create mode 100644 src/Pointers.cc create mode 100644 src/SRDBinary.cc create mode 100644 src/SRDData.cc create mode 100644 src/SRDDefinitions.cc create mode 100644 src/SRDIO.cc create mode 100644 src/SRDPPCommands.cc create mode 100644 src/SRDParser.cc create mode 100644 src/SRDParserConfig.cc create mode 100644 src/SRDPreproc.cc create mode 100644 src/SRDText.cc create mode 100644 src/Streams.cc create mode 100644 src/Strings.cc create mode 100644 src/TemplateInstantiation.cc create mode 100644 src/TextFileLogger.cc create mode 100644 src/Utilities.cc create mode 100644 src/VFS.cc create mode 100644 src/VFSDrivers.cc create mode 100644 src/files.mk create mode 100644 tests/alloc-pool.cc create mode 100644 tests/arrays-auto.cc create mode 100644 tests/arrays-basic.cc create mode 100644 tests/arrays-multi.cc create mode 100644 tests/arrays-objects.cc create mode 100644 tests/arrays-static-basic.cc create mode 100644 tests/arrays-static-objects.cc create mode 100644 tests/binary-reader.cc create mode 100644 tests/binary-writer.cc create mode 100644 tests/buffers.cc create mode 100644 tests/console-edit.cc create mode 100644 tests/console-text.cc create mode 100644 tests/data/inputstream.txt create mode 100644 tests/hash-functions.cc create mode 100644 tests/hash-index.cc create mode 100644 tests/key-value-table.cc create mode 100644 tests/list.mk create mode 100644 tests/log-data.cc create mode 100644 tests/object-table.cc create mode 100644 tests/optional.cc create mode 100644 tests/ptr-common.hh create mode 100644 tests/ptr-owned.cc create mode 100644 tests/ptr-shared.cc create mode 100644 tests/ptr-weak.cc create mode 100644 tests/ring-buffer.cc create mode 100644 tests/run-test.cc create mode 100644 tests/srd-bin-reader.cc create mode 100644 tests/srd-bin-writer.cc create mode 100644 tests/srd-lexer.cc create mode 100644 tests/srd-mem-target.cc create mode 100644 tests/srd-parser-cfg.cc create mode 100644 tests/srd-parser-defs.cc create mode 100644 tests/srd-parser.cc create mode 100644 tests/srd-preproc-cmd-arithmetic.cc create mode 100644 tests/srd-preproc-cmd-casts.cc create mode 100644 tests/srd-preproc-cmd-common.hh create mode 100644 tests/srd-preproc-cmd-compare.cc create mode 100644 tests/srd-preproc-cmd-core.cc create mode 100644 tests/srd-preproc-cmd-functions.cc create mode 100644 tests/srd-preproc-cmd-input.cc create mode 100644 tests/srd-preproc-cmd-introspect.cc create mode 100644 tests/srd-preproc-cmd-logic.cc create mode 100644 tests/srd-preproc-cmd-macros.cc create mode 100644 tests/srd-preproc-cmd-misc.cc create mode 100644 tests/srd-preproc-cmd-strings.cc create mode 100644 tests/srd-preproc-cmd-variables.cc create mode 100644 tests/srd-preproc-core.cc create mode 100644 tests/srd-preproc-location.hh create mode 100644 tests/srd-preproc-tracking.cc create mode 100644 tests/srd-text-writer.cc create mode 100644 tests/stream-file-input.cc create mode 100644 tests/strings-builder.cc create mode 100644 tests/strings-iterator.cc create mode 100644 tests/strings-storage.cc create mode 100644 tests/strings.cc create mode 100644 tests/union.cc create mode 100644 tests/variant.cc create mode 100644 tests/vfs.cc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a71c39e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vim.local/signature +.vim.local/data +output/* diff --git a/.vim.local/bundles-init/YouCompleteMe.cfg.vim b/.vim.local/bundles-init/YouCompleteMe.cfg.vim new file mode 100644 index 0000000..0296572 --- /dev/null +++ b/.vim.local/bundles-init/YouCompleteMe.cfg.vim @@ -0,0 +1 @@ +let g:ycm_global_ycm_extra_conf = g:vim_local_path . "/ycm_extra_conf.py" diff --git a/.vim.local/bundles-init/YouCompleteMe.load.vim b/.vim.local/bundles-init/YouCompleteMe.load.vim new file mode 100644 index 0000000..6394535 --- /dev/null +++ b/.vim.local/bundles-init/YouCompleteMe.load.vim @@ -0,0 +1 @@ +NeoBundle "Valloric/YouCompleteMe" diff --git a/.vim.local/bundles-init/vim-uncrustify.cfg.vim b/.vim.local/bundles-init/vim-uncrustify.cfg.vim new file mode 100644 index 0000000..ad32d16 --- /dev/null +++ b/.vim.local/bundles-init/vim-uncrustify.cfg.vim @@ -0,0 +1,2 @@ +let g:uncrustify_cfg_file_path = g:vim_local_path . "/../coding-style-uncrustify.cfg" + diff --git a/.vim.local/bundles-init/vim-uncrustify.load.vim b/.vim.local/bundles-init/vim-uncrustify.load.vim new file mode 100644 index 0000000..1e23ceb --- /dev/null +++ b/.vim.local/bundles-init/vim-uncrustify.load.vim @@ -0,0 +1 @@ +NeoBundle "cofyc/vim-uncrustify" diff --git a/.vim.local/bundles-init/vimmake.cfg.vim b/.vim.local/bundles-init/vimmake.cfg.vim new file mode 100644 index 0000000..00e32cc --- /dev/null +++ b/.vim.local/bundles-init/vimmake.cfg.vim @@ -0,0 +1,42 @@ +let g:vimmake_path = g:vim_local_path . 'scripts' + +let g:vimmake_mode = {} +let g:vimmake_mode['clean'] = 'async' +let g:vimmake_mode['build'] = 'async' +let g:vimmake_mode['rebuild'] = 'async' +let g:vimmake_mode['test'] = 'async' + +function! DoBuild(full) + silent call vimmake#toggle_quickfix(12,1) + if a:full == 0 + VimTool build + else + VimTool rebuild + endif +endfunc + +function! KillBuild() + silent call vimmake#toggle_quickfix(0) + VimStop +endfunc + +function! RunTest() + silent call vimmake#toggle_quickfix(12,1) + VimTool test +endfunc + +augroup QuickfixStatus + au! BufWinEnter quickfix setlocal + \ statusline=%t\ [%{g:vimmake_build_status}]\ %{exists('w:quickfix_title')?\ '\ '.w:quickfix_title\ :\ ''}\ %=%-15(%l,%c%V%)\ %P +augroup END + +noremap :silent call vimmake#toggle_quickfix(12) +inoremap :silent call vimmake#toggle_quickfix(12)li +noremap :silent call DoBuild(0) +inoremap :silent call DoBuild(0)li +noremap :silent call DoBuild(1) +inoremap :silent call DoBuild(1)li +noremap :silent call KillBuild() +inoremap :silent call KillBuild()li +noremap :silent call RunTest() +inoremap :silent call RunTest()li diff --git a/.vim.local/bundles-init/vimmake.load.vim b/.vim.local/bundles-init/vimmake.load.vim new file mode 100644 index 0000000..4de0be2 --- /dev/null +++ b/.vim.local/bundles-init/vimmake.load.vim @@ -0,0 +1 @@ +NeoBundle 'skywind3000/vimmake' diff --git a/.vim.local/ftplugin/cpp.vim b/.vim.local/ftplugin/cpp.vim new file mode 100644 index 0000000..0a95d16 --- /dev/null +++ b/.vim.local/ftplugin/cpp.vim @@ -0,0 +1,3 @@ +setlocal colorcolumn=121 +noremap :call Uncrustify('cpp') +vnoremap :call RangeUncrustify('cpp') diff --git a/.vim.local/ftplugin/srd.vim b/.vim.local/ftplugin/srd.vim new file mode 120000 index 0000000..ebfb2b1 --- /dev/null +++ b/.vim.local/ftplugin/srd.vim @@ -0,0 +1 @@ +../../data/srd-ftplugin.vim \ No newline at end of file diff --git a/.vim.local/scripts/vimmake.build b/.vim.local/scripts/vimmake.build new file mode 100755 index 0000000..e23cbe2 --- /dev/null +++ b/.vim.local/scripts/vimmake.build @@ -0,0 +1,2 @@ +#!/bin/sh +make -j 8 all diff --git a/.vim.local/scripts/vimmake.clean b/.vim.local/scripts/vimmake.clean new file mode 100755 index 0000000..6825c47 --- /dev/null +++ b/.vim.local/scripts/vimmake.clean @@ -0,0 +1,2 @@ +#!/bin/sh +make clean-extra diff --git a/.vim.local/scripts/vimmake.rebuild b/.vim.local/scripts/vimmake.rebuild new file mode 100755 index 0000000..f9636cb --- /dev/null +++ b/.vim.local/scripts/vimmake.rebuild @@ -0,0 +1,3 @@ +#!/bin/sh +make clean-extra +make -j 8 all diff --git a/.vim.local/scripts/vimmake.test b/.vim.local/scripts/vimmake.test new file mode 100755 index 0000000..62163bd --- /dev/null +++ b/.vim.local/scripts/vimmake.test @@ -0,0 +1,14 @@ +#!/bin/bash +if [ "$VIM_RELDIR" = "tests" ] && [ "$VIM_FILEEXT" = ".cc" ]; then + if make -j 8 all; then + if [ -f "output/fast/test-$VIM_FILENOEXT" ]; then + LD_LIBRARY_PATH=./output/fast \ + ./output/fast/test-$VIM_FILENOEXT 2>&1 \ + | sed -e s/"$VIM_FILENAME"/'tests\/'"$VIM_FILENAME"/g + else + LD_LIBRARY_PATH=./output/fast \ + ./output/fast/run-all-tests 2>&1 \ + | sed -e 's/^\([a-z\-]\+\.cc\)/tests\/\1/g' + fi + fi +fi diff --git a/.vim.local/syntax/srd.vim b/.vim.local/syntax/srd.vim new file mode 120000 index 0000000..f5c327e --- /dev/null +++ b/.vim.local/syntax/srd.vim @@ -0,0 +1 @@ +../../data/srd-syntax.vim \ No newline at end of file diff --git a/.vim.local/vimrc b/.vim.local/vimrc new file mode 100644 index 0000000..a6e61cc --- /dev/null +++ b/.vim.local/vimrc @@ -0,0 +1,7 @@ +let g:vim_local = { + \ "vardata" : g:vim_local_path . "/data" +\ } + +au BufNewFile,BufRead *.srd setf srd + +"let &wildignore=&wildignore . ',glef.c,glef.h' diff --git a/.vim.local/ycm_extra_conf.py b/.vim.local/ycm_extra_conf.py new file mode 100644 index 0000000..ac1dd6d --- /dev/null +++ b/.vim.local/ycm_extra_conf.py @@ -0,0 +1,143 @@ +# This file is NOT licensed under the GPLv3, which is the license for the rest +# of YouCompleteMe. +# +# Here's the license text for this file: +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# 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 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. +# +# For more information, please refer to + +import os +import ycm_core + +# These are the compilation flags that will be used in case there's no +# compilation database set (by default, one is not set). +# CHANGE THIS LIST OF FLAGS. YES, THIS IS THE DROID YOU HAVE BEEN LOOKING FOR. +flags = [ + '-Wall', + '-Wextra', + '-Werror', + '-Wno-long-long', + '-Wno-variadic-macros', + '-fexceptions', + '-std=c++14', + '-x', + 'c++', + '-I' , + './include' +] + + +# Set this to the absolute path to the folder (NOT the file!) containing the +# compile_commands.json file to use that instead of 'flags'. See here for +# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html +# +# You can get CMake to generate this file for you by adding: +# set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) +# to your CMakeLists.txt file. +# +# Most projects will NOT need to set this to anything; you can just change the +# 'flags' list of compilation flags. Notice that YCM itself uses that approach. +compilation_database_folder = '' + +if os.path.exists( compilation_database_folder ): + database = ycm_core.CompilationDatabase( compilation_database_folder ) +else: + database = None + +SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.m', '.mm' ] + +def DirectoryOfThisScript(): + return os.path.dirname( os.path.dirname( os.path.abspath( __file__ ) ) ) + + +def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): + if not working_directory: + return list( flags ) + new_flags = [] + make_next_absolute = False + path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ] + for flag in flags: + new_flag = flag + + if make_next_absolute: + make_next_absolute = False + if not flag.startswith( '/' ): + new_flag = os.path.join( working_directory, flag ) + + for path_flag in path_flags: + if flag == path_flag: + make_next_absolute = True + break + + if flag.startswith( path_flag ): + path = flag[ len( path_flag ): ] + new_flag = path_flag + os.path.join( working_directory, path ) + break + + if new_flag: + new_flags.append( new_flag ) + return new_flags + + +def IsHeaderFile( filename ): + extension = os.path.splitext( filename )[ 1 ] + return extension in [ '.h', '.hxx', '.hpp', '.hh' ] + + +def GetCompilationInfoForFile( filename ): + # The compilation_commands.json file generated by CMake does not have entries + # for header files. So we do our best by asking the db for flags for a + # corresponding source file, if any. If one exists, the flags for that file + # should be good enough. + if IsHeaderFile( filename ): + basename = os.path.splitext( filename )[ 0 ] + for extension in SOURCE_EXTENSIONS: + replacement_file = basename + extension + if os.path.exists( replacement_file ): + compilation_info = database.GetCompilationInfoForFile( + replacement_file ) + if compilation_info.compiler_flags_: + return compilation_info + return None + return database.GetCompilationInfoForFile( filename ) + + +def FlagsForFile( filename, **kwargs ): + if database: + # Bear in mind that compilation_info.compiler_flags_ does NOT return a + # python list, but a "list-like" StringVec object + compilation_info = GetCompilationInfoForFile( filename ) + if not compilation_info: + return None + + final_flags = MakeRelativePathsInFlagsAbsolute( + compilation_info.compiler_flags_, + compilation_info.compiler_working_dir_ ) + + else: + relative_to = DirectoryOfThisScript() + final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to ) + + return { 'flags': final_flags } diff --git a/include/ebcl/Alloc.hh b/include/ebcl/Alloc.hh new file mode 100644 index 0000000..96ed934 --- /dev/null +++ b/include/ebcl/Alloc.hh @@ -0,0 +1,240 @@ +/******************************************************************************/ +/* ALLOCATORS *****************************************************************/ +/******************************************************************************/ + +#ifndef _H_EBCL_ALLOC +#define _H_EBCL_ALLOC +#include +#include +#include +namespace lw { + + +/* Helpers for pool allocators. */ +struct T_PoolHelper +{ + /* Storage type for an object */ + template< + size_t ObjectSize , + size_t ObjectAlign + > using T_Object = std::aligned_storage_t< ObjectSize , ObjectAlign >; + + /* List of object storage items. The storage type is assumed to + * have a void* field called list. + */ + template< + typename T_Storage , + size_t ListSize + > struct T_List final + { + static constexpr size_t BitmapSize + = ( ListSize / 8 ) + ( ListSize % 8 ? 1 : 0 ); + + using T_Self = T_List< T_Storage , ListSize >; + + T_Storage storage[ ListSize ]; // Stored data + T_Self* next; // Next list in chain + size_t free; // Free objects + uint8_t bitmap[ BitmapSize ]; // Allocation map + + T_List( ) = delete; + T_List( T_Self const& ) = delete; + T_List( T_Self&& ) = delete; + + // Create a new list, replacing the chain's head + explicit T_List( T_Self*& head ) noexcept; + + // Find an unused item in the list. The list must have free + // items. + T_Storage* findUnused( ) noexcept; + // Find the index of an item. The item must be part of the list. + size_t indexOf( T_Storage const* item ) noexcept; + + // Destroy a complete chain of lists + static void destroy( T_Self*& head ) noexcept; + }; + + /* The core of a pool allocator */ + template< + typename T_Storage , + size_t PerList , + size_t MaxFreeLists + > struct T_Allocator final + { + static_assert( PerList > 1 , "allocator lists must contain at least 2 items" ); + static_assert( MaxFreeLists > 0 , "allocator must have at least 1 free list" ); + + using T_List_ = T_List< T_Storage , PerList >; + using T_Self = T_Allocator< T_Storage , PerList , MaxFreeLists >; + + // The chains + T_List_* free_; + T_List_* partial_; + T_List_* full_; + size_t nFree_; + + T_Allocator( ) noexcept; + ~T_Allocator( ); + + T_Allocator( T_Self const& ) = delete; + T_Allocator( T_Self&& ) = delete; + + /* Allocate an object. */ + T_Storage* allocate( ) noexcept; + /* Free an object */ + void free( T_Storage* item ) noexcept; + + /* Get the amount of lists. Mostly used for testing. */ + size_t countFreeLists( ) const noexcept; + size_t countPartialLists( ) const noexcept; + size_t countFullLists( ) const noexcept; + + /* Get the amount of total, free and used objects */ + void getUsage( size_t& total , + size_t& free , + size_t& used ) const noexcept; + + private: + /* Move a list of objects from one of the allocation lists to another. */ + static void moveList( + T_List_* list , + T_List_*& from , + T_List_*& to ) noexcept; + + /* Count the amount of lists in an allocation list */ + static size_t countLists( + T_List_ const* head ) noexcept; + }; +}; + +/*============================================================================*/ + +/* The pool allocator implements a thread-unsafe pool of objects, similar to a + * slab allocator. + */ +template< + size_t ObjectSize , size_t ObjectAlign , + size_t PerList = std::max( size_t( 8 ) , 4096u / ObjectSize ) , + size_t MaxFreeLists = 4 +> +class T_PoolAllocator +{ + public: + using T_Self = T_PoolAllocator< ObjectSize , ObjectAlign , PerList , MaxFreeLists >; + + private: + // Structure that stores a single allocated item + struct T_Storage_ final + { + T_PoolHelper::T_Object< ObjectSize , ObjectAlign > data; + void* list; + }; + + using T_Alloc_ = T_PoolHelper::T_Allocator< T_Storage_ , PerList , MaxFreeLists >; + T_Alloc_ alloc_; + + public: + T_PoolAllocator( ) noexcept = default; + T_PoolAllocator( T_Self const& ) = delete; + T_PoolAllocator( T_Self&& ) = delete; + + /* Allocate an object. */ + void* allocate( size_t requested ) noexcept; + /* Free an object */ + void free( void* item ) noexcept; + + /* Get the amount of lists. Mostly used for testing. */ + size_t countFreeLists( ) const noexcept; + size_t countPartialLists( ) const noexcept; + size_t countFullLists( ) const noexcept; + + /* Get the amount of total, free and used objects */ + void getUsage( size_t& total , + size_t& free , + size_t& used ) const noexcept; +}; + +/*============================================================================*/ + +/* Pool allocator with multithreading support. Allocation is performed from + * a thread-specific pool allocator; when the memory is freed, the memory is + * either returned directly to the pool if the freeing thread is the same as + * the allocating thread, or queued for the original thread to free later. + */ +template< + size_t ObjectSize , size_t ObjectAlign , + size_t PerList = std::max( size_t( 8 ) , 4096u / ObjectSize ) , + size_t MaxFreeLists = 4 +> +class T_ThreadedPoolAllocator +{ + public: + using T_Self = T_ThreadedPoolAllocator< + ObjectSize , ObjectAlign , PerList , MaxFreeLists >; + + private: + // Pointer to either the owning pool or the next object in the + // pending list + struct T_Storage_; + union T_ExtraPointer_ + { + T_Storage_* next; + T_Self* pool; + }; + + // Structure that stores a single allocated item + struct T_Storage_ final + { + T_PoolHelper::T_Object< ObjectSize , ObjectAlign > data; + void* list; + T_ExtraPointer_ extra; + }; + + // The head for the "remote" freeing stack + struct T_FreeHead_ final + { + uintptr_t aba{ 0 }; + T_Storage_* head{ nullptr }; + }; + + // Atomic stack head, plus padding to prevent false sharing between + // the stack head and the actual allocator. + char padding_0_[ 64 ]; + std::atomic< T_FreeHead_ > freeHead_; + char padding_1_[ 64 - sizeof( std::atomic< T_FreeHead_ > ) ]; + + using T_Alloc_ = T_PoolHelper::T_Allocator< + T_Storage_ , PerList , MaxFreeLists >; + T_Alloc_ alloc_; + + public: + T_ThreadedPoolAllocator( ) noexcept = default; + T_ThreadedPoolAllocator( T_Self const& ) = delete; + T_ThreadedPoolAllocator( T_Self&& ) = delete; + + /* Allocate an object. */ + void* allocate( size_t requested ) noexcept; + /* Free an object */ + void free( void* item ) noexcept; + + /* Get the amount of lists. Mostly used for testing. */ + size_t countFreeLists( ) const noexcept; + size_t countPartialLists( ) const noexcept; + size_t countFullLists( ) const noexcept; + + /* Get the amount of total, free and used objects */ + void getUsage( size_t& total , + size_t& free , + size_t& used ) const noexcept; + + private: + // Try to take an item from the stack. + T_Storage_* takeFromStack( ) noexcept; + // Add an item to the stack + void addToStack( T_Storage_* item ) noexcept; +}; + + +} +#endif //_H_EBCL_ALLOC +#include diff --git a/include/ebcl/Arrays.hh b/include/ebcl/Arrays.hh new file mode 100644 index 0000000..ef20884 --- /dev/null +++ b/include/ebcl/Arrays.hh @@ -0,0 +1,446 @@ +/******************************************************************************/ +/* ARRAYS *********************************************************************/ +/******************************************************************************/ + +#ifndef _H_EBCL_ARRAYS +#define _H_EBCL_ARRAYS +#include +#include +#include +#include +namespace lw { + + +/*= DYNAMIC ARRAYS ===========================================================*/ + +template< class T > +class T_Array final +{ + public: + static constexpr uint32_t DEFAULT_GROWTH( ) + { + return std::max< uint32_t >( 1 , 4096 / sizeof( T ) ); + } + + private: + typedef T_Array< T > MyType_; + + T* data_; + uint32_t capacity_; + uint32_t size_; + uint32_t growth_; + + public: + M_TEMPLATE_POINTERS( MyType_ ); + + T_Array( ) noexcept; + explicit T_Array( uint32_t growth ) noexcept; + + // --------------------------------------------------------------------- + + T_Array( MyType_ const& source ) noexcept; + MyType_& operator= ( MyType_ const& other ) noexcept; + + T_Array( MyType_&& source ) noexcept; + MyType_& operator= ( MyType_&& other ) noexcept; + + template< typename TP > + friend void swap( T_Array< TP >& lhs , + T_Array< TP >& rhs ) noexcept; + + // --------------------------------------------------------------------- + + ~T_Array( ); + + MyType_& clear( ) noexcept; + MyType_& free( ) noexcept; + + // --------------------------------------------------------------------- + + uint32_t capacity( ) const noexcept; + uint32_t size( ) const noexcept; + uint32_t growth( ) const noexcept; + + MyType_& ensureCapacity( uint32_t capacity ) noexcept; + + // --------------------------------------------------------------------- + + T& operator[] ( uint32_t index ) noexcept; + T const& operator[] ( uint32_t index ) const noexcept; + + int32_t indexOf( T const& item ) const noexcept; + bool contains( T const& item ) const noexcept; + + // --------------------------------------------------------------------- + + uint32_t add( T const& item ) noexcept; + uint32_t add( T&& item ) noexcept; + + template< typename... Args > + T& addNew( Args&& ... args ); + + // --------------------------------------------------------------------- + + MyType_& addAll( MyType_ const& other ) noexcept; + MyType_& addAll( MyType_&& other ) noexcept; + + // --------------------------------------------------------------------- + + MyType_& operator<< ( T const& item ) noexcept; + MyType_& operator<< ( T&& item ) noexcept; + MyType_& operator<< ( MyType_ const& other ) noexcept; + MyType_& operator<< ( MyType_&& other ) noexcept; + + // --------------------------------------------------------------------- + + void insert( uint32_t index , + T const& item ) noexcept; + void insert( uint32_t index , + T&& item ) noexcept; + + template< typename... Args > + T& insertNew( uint32_t index , + Args&& ... args ); + + // --------------------------------------------------------------------- + + void remove( uint32_t index ) noexcept; + void removeSwap( uint32_t index ) noexcept; + + // --------------------------------------------------------------------- + + void sort( F_Comparator< T > cmp = T_Comparator< T >::compare ) noexcept; + void sort( uint32_t first , + uint32_t items , + F_Comparator< T > cmp = T_Comparator< T >::compare ) noexcept; + + // --------------------------------------------------------------------- + + MyType_ copyRange( uint32_t first , + uint32_t last = UINT32_MAX ) const noexcept; + + MyType_ moveRange( uint32_t first , + uint32_t last = UINT32_MAX ) noexcept; +}; + +// Instantiate some common types directly +extern template class T_Array< uint32_t >; + + +/*= STATICALLY ALLOCATED ARRAYS ==============================================*/ +/* These arrays offer the same interface as dynamic arrays, but are in fact + * implemented as in-place storage. + */ + +template< typename Type , uint32_t Size > +class T_StaticArray final +{ + static_assert( Size > 0 , "Size must be greater than 0" ); + + public: + using T_Self = T_StaticArray< Type , Size >; + + private: + // Actual storage type + using T_Storage_ = std::aligned_storage_t< + sizeof( Type ) , alignof( Type ) + >; + + T_Storage_ storage_[ Size ]; + uint32_t size_; + + public: + M_TEMPLATE_POINTERS( T_Self ); + + T_StaticArray( ) noexcept; + + // Copy + T_StaticArray( T_Self const& source ) noexcept; + T_Self& operator= ( T_Self const& other ) noexcept; + + // Move + T_StaticArray( T_Self&& source ) noexcept; + T_Self& operator= ( T_Self&& other ) noexcept; + + ~T_StaticArray( ) noexcept; + T_Self& clear( ) noexcept; + + template< typename T , uint32_t S > + friend void swap( + T_StaticArray< T , S >& lhs , + T_StaticArray< T , S >& rhs ) noexcept; + + // --------------------------------------------------------------------- + + constexpr uint32_t capacity( ) const noexcept; + uint32_t size( ) const noexcept; + + // --------------------------------------------------------------------- + + Type& operator[] ( uint32_t index ) noexcept; + Type const& operator[] ( uint32_t index ) const noexcept; + + int32_t indexOf( Type const& item ) const noexcept; + bool contains( Type const& item ) const noexcept; + + // --------------------------------------------------------------------- + + uint32_t add( Type&& item ) noexcept; + uint32_t add( Type const& item ) noexcept; + template< typename... Args > + Type& addNew( Args&& ... args ); + + // --------------------------------------------------------------------- + + T_Self& addAll( T_Self const& other ) noexcept; + T_Self& addAll( T_Self&& other ) noexcept; + + // --------------------------------------------------------------------- + + T_Self& operator<< ( Type const& item ) noexcept; + T_Self& operator<< ( Type&& item ) noexcept; + T_Self& operator<< ( T_Self const& other ) noexcept; + T_Self& operator<< ( T_Self&& other ) noexcept; + + // --------------------------------------------------------------------- + + void insert( uint32_t index , + Type&& item ) noexcept; + void insert( uint32_t index , + Type const& item ) noexcept; + template< typename... Args > + Type& insertNew( uint32_t index , + Args&& ... args ); + + // --------------------------------------------------------------------- + + void remove( uint32_t index ) noexcept; + void removeSwap( uint32_t index ) noexcept; + + // --------------------------------------------------------------------- + + void sort( F_Comparator< Type > cmp = T_Comparator< Type >::compare ) noexcept; + void sort( uint32_t first , + uint32_t items , + F_Comparator< Type > cmp = T_Comparator< Type >::compare ) noexcept; + + // --------------------------------------------------------------------- + + T_Self copyRange( uint32_t first , + uint32_t last = UINT32_MAX ) const noexcept; + T_Self moveRange( uint32_t first , + uint32_t last = UINT32_MAX ) noexcept; +}; + + +/*= MULTI-ARRAYS ============================================================-*/ +/* + * Arrays that allow storing multiple values for one entry. Values must be + * added in order. + */ + +template< typename T > +class T_MultiArray +{ + public: + using T_Data = T_Array< T >; + + private: + typedef T_MultiArray< T > MyType_; + + T_Array< uint32_t > meta_; + T_Data values_; + + public: + M_TEMPLATE_POINTERS( T_MultiArray ); + + template< typename TP > + friend void swap( T_MultiArray< TP >& lhs , T_MultiArray< TP >& rhs ); + + // Start the next entry + void next( ); + // Add a value to the current entry + void add( T value ); + // Add a value, calling its constructor + template< + typename Q = T , + typename... Args , + typename = std::enable_if_t< std::is_class< Q >::value > + > + Q& addNew( Args&& ... args ); + // Copy an array's contents into the current entry + void copyFrom( T_Data const& source ); + + // Returns the amount of entries + uint32_t size( ) const; + // Returns the index of the first value for an entry + uint32_t firstOf( uint32_t item ) const; + // Returns the amount of values for an entry + uint32_t sizeOf( uint32_t item ) const; + + // Access a value in an entry + T const& get( uint32_t item , uint32_t sub ) const; + T& get( uint32_t item , uint32_t sub ); + + // Access a value using its index + T const& operator[] ( uint32_t index ) const; + T& operator[] ( uint32_t index ); + + // Is a value present in an entry? + bool contains( uint32_t index , T const& value ) const; + + // Reset storage, don't free memory + void clear( ); + // Reset storage and free memory + void free( ); + + // Copy values from an entry into the current entry + void copyFrom( uint32_t index ); + // Copy another multi-array's entry into the current entry + void copyFrom( MyType_ const& other , uint32_t index ); + + // Copy values from another entry into the current entry if + // they are not already present. Duplicate entries in the + // source array are still copied, though. + void copyUnique( uint32_t index ); + // Copy another multi-array's entry into the current entry, + // ignoring values that are already present. Duplicates in + // the source array are still copied. + void copyUnique( MyType_ const& other , uint32_t index ); + + // Sort an entry's values + void sort( uint32_t index , F_Comparator< T > cmp = T_Comparator< T >::compare ); +}; + + +// Instantiate some common types directly +extern template class T_MultiArray< uint32_t >; + + +/*= AUTOMATIC ARRAYS =========================================================*/ +/* Arrays that are stored in-place up to some limit, then transform into + * dynamic arrays if necessary. + */ + +template< + typename T , uint32_t StaticSize , + uint32_t DynamicGrowth = StaticSize * 4 +> class T_AutoArray +{ + public: + using T_Self = T_AutoArray< T , StaticSize , DynamicGrowth >; + + private: + using T_Static_ = T_StaticArray< T , StaticSize >; + using T_Dynamic_ = T_Array< T >; + + T_Union< T_Static_ , T_Dynamic_ > array_; + + // --------------------------------------------------------------------- + + public: + M_TEMPLATE_POINTERS( T_Self ); + + T_AutoArray( ) noexcept; + + T_AutoArray( T_Self const& source ) noexcept; + T_Self& operator =( T_Self const& source ) noexcept; + + T_AutoArray( T_Self&& source ) noexcept; + T_Self& operator =( T_Self&& source ) noexcept; + + // --------------------------------------------------------------------- + + template< typename TP , uint32_t S , uint32_t G > + friend void swap( + T_AutoArray< TP , S , G >& lhs , + T_AutoArray< TP , S , G >& rhs ) noexcept; + + // --------------------------------------------------------------------- + + T_Self& clear( ) noexcept; + T_Self& free( ) noexcept; + + // --------------------------------------------------------------------- + + uint32_t capacity( ) const noexcept; + uint32_t size( ) const noexcept; + constexpr uint32_t growth( ) const noexcept; + bool isStatic( ) const noexcept; + + T_Self& ensureCapacity( uint32_t capacity ) noexcept; + + // --------------------------------------------------------------------- + + T& operator[] ( uint32_t index ) noexcept; + T const& operator[] ( uint32_t index ) const noexcept; + + int32_t indexOf( T const& item ) const noexcept; + bool contains( T const& item ) const noexcept; + + // --------------------------------------------------------------------- + + uint32_t add( T const& item ) noexcept; + uint32_t add( T&& item ) noexcept; + + template< typename... Args > + T& addNew( Args&& ... args ); + + // --------------------------------------------------------------------- + + T_Self& addAll( T_Self const& other ) noexcept; + T_Self& addAll( T_Self&& other ) noexcept; + + // --------------------------------------------------------------------- + + T_Self& operator<< ( T const& item ) noexcept; + T_Self& operator<< ( T&& item ) noexcept; + T_Self& operator<< ( T_Self const& other ) noexcept; + T_Self& operator<< ( T_Self&& other ) noexcept; + + // --------------------------------------------------------------------- + + void insert( uint32_t index , + T const& item ) noexcept; + void insert( uint32_t index , + T&& item ) noexcept; + + template< typename... Args > + T& insertNew( uint32_t index , + Args&& ... args ); + + // --------------------------------------------------------------------- + + void remove( uint32_t index ) noexcept; + void removeSwap( uint32_t index ) noexcept; + + // --------------------------------------------------------------------- + + void sort( F_Comparator< T > cmp = T_Comparator< T >::compare ) noexcept; + void sort( uint32_t first , + uint32_t items , + F_Comparator< T > cmp = T_Comparator< T >::compare ) noexcept; + + // --------------------------------------------------------------------- + + T_Self copyRange( uint32_t first , + uint32_t last = UINT32_MAX ) const noexcept; + + T_Self moveRange( uint32_t first , + uint32_t last = UINT32_MAX ) noexcept; + + // --------------------------------------------------------------------- + + private: + void convertToDynamic_( uint32_t capacity ) noexcept; + + T_Static_& static_( ) noexcept; + T_Static_ const& static_( ) const noexcept; + T_Dynamic_& dynamic_( ) noexcept; + T_Dynamic_ const& dynamic_( ) const noexcept; +}; + + +} +#endif // _H_EBCL_ARRAYS +#include diff --git a/include/ebcl/BinaryStreams.hh b/include/ebcl/BinaryStreams.hh new file mode 100644 index 0000000..64186e5 --- /dev/null +++ b/include/ebcl/BinaryStreams.hh @@ -0,0 +1,169 @@ +/******************************************************************************/ +/* BINARY READER/WRITER FOR STREAMS *******************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= OBJECT READER/WRITER TEMPLATES ===========================================*/ +/* + * This pair of templates must be specialized to handle specific types. + */ + +struct T_BinaryReader; +struct T_BinaryWriter; + +template< typename T > +struct T_ObjectReader +{ + static T read( T_BinaryReader const& reader ); +}; + + +template< typename T > +struct T_ObjectWriter +{ + static void write( T_BinaryWriter const& writer , T const& string ); +}; + + +// Helper macros to declare and define readers and writers +#define M_DECLARE_OBJECT_READER( Type ) \ + template< > \ + struct T_ObjectReader< Type > \ + { \ + static Type read( T_BinaryReader const& ); \ + } + + +#define M_DEFINE_OBJECT_READER( Type ) \ + Type T_ObjectReader< Type >::read( T_BinaryReader const & reader ) + +#define M_DECLARE_OBJECT_WRITER( Type ) \ + template< > \ + struct T_ObjectWriter< Type > \ + { \ + static void write( T_BinaryWriter const& , \ + Type const& ); \ + } + + +#define M_DEFINE_OBJECT_WRITER( Type ) \ + void T_ObjectWriter< Type >::write( T_BinaryWriter const & writer , \ + Type const & item ) + + +/*= READER ===================================================================*/ + +struct T_BinaryReader +{ + private: + A_InputStream& stream_; + const E_Endian endian_; + + public: + T_BinaryReader( ) = delete; + + explicit T_BinaryReader( A_InputStream& stream , E_Endian endian = E_Endian::NATIVE ) noexcept; + + T_BinaryReader( T_BinaryReader const& other ) noexcept = delete; + T_BinaryReader( T_BinaryReader&& other ) noexcept = delete; + + A_InputStream& stream( ) const; + E_Endian endian( ) const; + + template< typename T > + T readNumericNative( ) const; + template< typename T > + T readNumericLittleEndian( ) const; + template< typename T > + T readNumericBigEndian( ) const; + + template< typename T > + T read( ) const; + + private: + template< + typename T , + bool IsNum = std::is_integral< T >::value + || std::is_floating_point< T >::value + > + struct T_Reader_ { }; + + + template< typename T > + struct T_Reader_< T , true > + { + static T read( T_BinaryReader const& reader ); + }; + + + template< typename T > + struct T_Reader_< T , false > + { + static T read( T_BinaryReader const& reader ); + }; + + +}; + + +/*= WRITER ===================================================================*/ + +struct T_BinaryWriter +{ + private: + A_OutputStream& stream_; + const E_Endian endian_; + + public: + T_BinaryWriter( ) noexcept = delete; + + explicit T_BinaryWriter( A_OutputStream& stream , E_Endian endian = E_Endian::NATIVE ) noexcept; + + T_BinaryWriter( T_BinaryWriter const& other ) noexcept = delete; + T_BinaryWriter( T_BinaryWriter&& other ) noexcept = delete; + + A_OutputStream& stream( ) const; + E_Endian endian( ) const; + + template< typename T > + void writeNumericNative( T value ) const; + template< typename T > + void writeNumericLittleEndian( T const& value ) const; + template< typename T > + void writeNumericBigEndian( T const& value ) const; + + template< typename T > + void write( T const& value ) const; + + private: + template< + typename T , + bool IsNum = std::is_integral< T >::value + || std::is_floating_point< T >::value + > + struct T_Writer_ { }; + + + template< typename T > + struct T_Writer_< T , true > + { + static void write( T_BinaryWriter const& writer , T value ); + }; + + + template< typename T > + struct T_Writer_< T , false > + { + static void write( T_BinaryWriter const& writer , T const& value ); + }; + + +}; + + +} +#include diff --git a/include/ebcl/Buffers.hh b/include/ebcl/Buffers.hh new file mode 100644 index 0000000..e430a88 --- /dev/null +++ b/include/ebcl/Buffers.hh @@ -0,0 +1,104 @@ +/******************************************************************************/ +/* BUFFERS ********************************************************************/ +/******************************************************************************/ + +/* Buffers can be used to manipulate raw data. No constructors or destructors + * will ever be called. + * + * Buffers are available in two flavors: fixed size (where the size of the + * buffer is a template argument) and dynamic (where the size can be changed). + */ + +#pragma once +#include +namespace lw { + + +/*============================================================================*/ + +class T_BufferBase +{ + protected: + uint8_t* data_; + + T_BufferBase( ) = delete; + explicit T_BufferBase( uint8_t* buffer ) noexcept; + explicit T_BufferBase( size_t size ); + T_BufferBase( T_BufferBase&& source ) noexcept; + + public: + ~T_BufferBase( ); + + friend void swap( T_BufferBase& lhs , T_BufferBase& rhs ); + + uint8_t * data( ) const; +}; + + +/*============================================================================*/ + +template< size_t SIZE , class T > +class T_FixedBuffer : public T_BufferBase +{ + private: + typedef T_FixedBuffer< SIZE , T > MyType_; + static constexpr size_t BYTES_ = SIZE * sizeof( T ); + + public: + T_FixedBuffer( ); + T_FixedBuffer( MyType_&& source ) noexcept; + T_FixedBuffer( MyType_ const& source ); + + template< size_t SZ , typename TP > + friend void swap( T_FixedBuffer< SZ , TP >& lhs , T_FixedBuffer< SZ , TP >& rhs ) noexcept; + + MyType_& operator= ( MyType_ const& other ); + MyType_& operator= ( MyType_&& other ) noexcept; + + size_t bytes( ) const; + T& operator[] ( size_t index ); + T const& operator[] ( size_t index ) const; + + MyType_& setAll( T const& value ); +}; + + +/*============================================================================*/ + +template< class T > +class T_Buffer : public T_BufferBase +{ + private: + typedef T_Buffer< T > MyType_; + + protected: + size_t size_; + + public: + T_Buffer( ) noexcept; + T_Buffer( size_t size ); + T_Buffer( T const* data , size_t size ); + + T_Buffer( MyType_ const& other ); + T_Buffer( MyType_&& other ) noexcept; + + MyType_& operator= ( MyType_ const& other ); + MyType_& operator= ( MyType_&& other ); + + template< typename TP > + friend void swap( T_Buffer< TP >& lhs , T_Buffer< TP >& rhs ) noexcept; + + size_t size( ) const; + size_t bytes( ) const; + + T& operator[] ( size_t index ); + T const& operator[] ( size_t index ) const; + + MyType_& resize( size_t newSize ); + MyType_& setAll( T const& value ); + MyType_& copyAll( T const* data ); +}; + + +} // namespace +#include diff --git a/include/ebcl/Config.hh b/include/ebcl/Config.hh new file mode 100644 index 0000000..b78c10a --- /dev/null +++ b/include/ebcl/Config.hh @@ -0,0 +1,25 @@ +/******************************************************************************/ +/* CONFIGURATION FOR THE BUILD ************************************************/ +/******************************************************************************/ + +#ifndef _H_EBCL_CONFIG +#define _H_EBCL_CONFIG + + +/*= DEBUGGING OPTIONS ========================================================*/ + +/* Disable memory allocators */ +//#define EBCL_CFG_NO_ALLOCATORS + + +/*= TUNING ===================================================================*/ + +/* Pool allocator parameters for shared pointer references */ +#define EBCL_CFG_SHAREDPTR_REFPOOL_SIZE 512 +#define EBCL_CFG_SHAREDPTR_REFPOOL_MAXFREE 2 + +/*----------------------------------------------------------------------------*/ + + + +#endif // _H_EBCL_CONFIG diff --git a/include/ebcl/DynLib.hh b/include/ebcl/DynLib.hh new file mode 100644 index 0000000..642f7e4 --- /dev/null +++ b/include/ebcl/DynLib.hh @@ -0,0 +1,48 @@ +/******************************************************************************/ +/* DYNAMIC LIBRARIES **********************************************************/ +/******************************************************************************/ + +#ifndef _H_EBCL_DYNLIB +#define _H_EBCL_DYNLIB + +#include +#include +namespace lw { + + +/*= DYNAMIC LIBRARY SUPPORT ==================================================*/ + +class T_DynLib : public A_PrivateImplementation +{ + public: + T_DynLib( ) = delete; + T_DynLib( T_DynLib const& ) = delete; + T_DynLib& operator =( T_DynLib const& ) = delete; + + /* Initialise and set the name of the library to load */ + explicit T_DynLib( T_String const& name ); + explicit T_DynLib( char const* const name ); + + /* Load/unload the library */ + bool load( ); + void unload( ); + + /* Check whether the library has been loaded */ + bool isLoaded( ) const noexcept; + /* Obtain the last error that was encoutered */ + T_String lastError( ) const noexcept; + + /* Symbol access. The library MUST be loaded. */ + void* getRawSymbol( char const* name ) const noexcept; + template< typename T > + T* getPointer( char const* name ) const noexcept; + template< typename T > + std::function< T > getFunction( + char const* name ) const noexcept; +}; +M_CLASS_POINTERS( DynLib ); + + +} // namespace lw +#endif // _H_EBCL_DYNLIB +#include diff --git a/include/ebcl/Externals.hh b/include/ebcl/Externals.hh new file mode 100644 index 0000000..6c21fa0 --- /dev/null +++ b/include/ebcl/Externals.hh @@ -0,0 +1,19 @@ +/******************************************************************************/ +/* EXTERNAL HEADER FILES ******************************************************/ +/******************************************************************************/ + + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/include/ebcl/Files.hh b/include/ebcl/Files.hh new file mode 100644 index 0000000..fb6633c --- /dev/null +++ b/include/ebcl/Files.hh @@ -0,0 +1,187 @@ +/******************************************************************************/ +/* FILES **********************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_FILES +#define _H_LW_LIB_FILES +#include +#include +namespace lw { + + +/*= FILE ACCESS ==============================================================*/ + +enum class E_FileMode { + READ_ONLY , + READ_WRITE , + OVERWRITE +}; + + +class T_File final +{ + private: + T_String path_; + E_FileMode mode_; + FILE* file_; + size_t size_ , pos_; + + T_File( ); + + public: + // Construct from a file path. Does not open the file. + T_File( T_String const& path , E_FileMode mode ); + + // Move constructor and assignment + T_File( T_File&& other ) noexcept; + T_File& operator= ( T_File&& other ) noexcept; + + // Disabled copy constructor / assignment operator + T_File( T_File const& ) = delete; + T_File& operator= ( T_File const& ) = delete; + + // Destructor - must close the file if it is open + ~T_File( ); + + // Swapping + friend void swap( T_File& lhs , T_File& rhs ) noexcept; + + // ------------------------------------------------------------------- + + // Get the path + T_String const& path( ) const noexcept; + // Get the mode + E_FileMode mode( ) const noexcept; + + // Open the file. Throws an exception if that fails. Will be called + // automatically by most methods. + virtual void open( ); + + // Close the file. Never throws, even if the file's already closed or + // if closing fails. + void close( ) noexcept; + + // Check if the file is open + bool isOpen( ) const noexcept; + + // ------------------------------------------------------------------- + + // Get the file's size + size_t size( ) const noexcept; + // Get the current file position + size_t position( ) const noexcept; + + // Set the position relative to the start or the end of the file + void position( size_t position , bool fromEnd = false ); + // Set the position relative to the current position + void move( ssize_t offset ); + + // Read/write methods + size_t read( void* data , size_t size ); + size_t write( void const* data , size_t size ); + + // Flushing + void flush( ); +}; + + +M_CLASS_POINTERS( File ); + + +/*= FILE STREAMS =============================================================*/ + +class T_FileInputStream final : public A_InputStream +{ + protected: + RP_File fileRaw_; + OP_File fileOwned_; + size_t start_; + + public: + T_FileInputStream( ) = delete; + + // Construct from a file instance + explicit T_FileInputStream( T_File& file , ssize_t offset = 0 , + size_t limit = SIZE_MAX ); + // Construct from a file owning pointer + explicit T_FileInputStream( OP_File&& file , ssize_t offset = 0 , + size_t limit = SIZE_MAX ); + + // Copy constructor and assignment + T_FileInputStream( T_FileInputStream const& ); + T_FileInputStream& operator= ( T_FileInputStream const& ); + + // Move constructor and assignment + T_FileInputStream( T_FileInputStream&& other ) noexcept; + T_FileInputStream& operator= ( T_FileInputStream&& other ) noexcept; + + // Swapping + void swap( T_FileInputStream& rhs ) noexcept; + + // Get the file + T_File& file( ) const noexcept; + // Get the start offset + size_t offset( ) const noexcept; + + // ------------------------------------------------------------------- + + size_t read( void* data , size_t size ) override; + + // ------------------------------------------------------------------- + private: + void init( ssize_t offset , size_t limit ); +}; + + +M_CLASS_POINTERS( FileInputStream ); + +/*---------------------------------------------------------------------------*/ + +class T_FileOutputStream final : public A_OutputStream +{ + protected: + RP_File fileRaw_; + OP_File fileOwned_; + size_t start_; + + public: + T_FileOutputStream( ) = delete; + + // Construct from a file. + explicit T_FileOutputStream( T_File& file , ssize_t offset = 0 ); + // Construct from a file owning pointer + explicit T_FileOutputStream( OP_File&& file , ssize_t offset = 0 ); + + // Copy constructor and assignment + T_FileOutputStream( T_FileOutputStream const& ); + T_FileOutputStream& operator= ( T_FileOutputStream const& ); + + // Move constructor and assignment + T_FileOutputStream( T_FileOutputStream&& other ) noexcept; + T_FileOutputStream& operator= ( T_FileOutputStream&& other ) noexcept; + + // Swapping + void swap( T_FileOutputStream& rhs ) noexcept; + + // Get the file + T_File& file( ) const noexcept; + // Get the start offset + size_t offset( ) const noexcept; + + // ------------------------------------------------------------------- + + size_t write( void const* data , size_t size ) override; + void flush( ) override; + + // ------------------------------------------------------------------- + private: + void init( ssize_t offset ); +}; + + +M_CLASS_POINTERS( FileOutputStream ); + + +} +#endif // _H_LW_LIB_FILES +#include diff --git a/include/ebcl/GameLoop.hh b/include/ebcl/GameLoop.hh new file mode 100644 index 0000000..f82677a --- /dev/null +++ b/include/ebcl/GameLoop.hh @@ -0,0 +1,30 @@ +/******************************************************************************/ +/* GAME'S MAIN LOOP ***********************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_GAMELOOP +#define _H_LW_LIB_GAMELOOP +#include +namespace lw { + + +/*= GAME LOOP ================================================================*/ + +class T_GameLoop : public A_PrivateImplementation +{ + public: + T_GameLoop( ) noexcept; + T_GameLoop( T_GameLoop const& ) = delete; + T_GameLoop( T_GameLoop&& ) = delete; + + bool active( ) const noexcept; + void start( ) noexcept; + void shutdown( ) noexcept; + + void putMessage( T_UIMessage&& message ) noexcept; +}; +M_CLASS_POINTERS( GameLoop ); + + +} +#endif // _H_LW_LIB_GAMELOOP diff --git a/include/ebcl/HashIndex.hh b/include/ebcl/HashIndex.hh new file mode 100644 index 0000000..27948d7 --- /dev/null +++ b/include/ebcl/HashIndex.hh @@ -0,0 +1,79 @@ +/******************************************************************************/ +/* HASH INDEX *****************************************************************/ +/******************************************************************************/ + +#pragma once +#include +#include +namespace lw { + + +/* + * This is based on http://glampert.com/2016/05-04/dissecting-idhashindex, + * which explains how the hash tables in idTech4 work. Not looking at the code + * to avoid GPL-related issues, so my implementation will probably suck. + * + * Unlike the original thing, this version considers that: + * - all additions are made at the end of the array (and therefore at the + * end of the index), + * - erasing is done by swapping the element being erased and the last + * element of the index. + * + * It also keeps track of the hash key for a given index in order to make + * erasing easier. + */ + +class T_HashIndex +{ + public: + static constexpr uint32_t INVALID_INDEX = 0xffffffff; + static constexpr uint32_t DEFAULT_SIZE = 1024; + static constexpr uint32_t DEFAULT_GROWTH = 1024; + + private: + static uint32_t invalidIndex_; + + uint32_t hashSize_; + uint32_t* hash_; + + uint32_t indexSize_; + uint32_t indexGrowth_; + uint32_t indexUsed_; + uint32_t* index_; + uint32_t* indexReverse_; + + uint32_t hashMask_; + uint32_t lookupMask_; + + void enlargeIndex( uint32_t needed ); + void allocateIfNecessary( ); + + public: + T_HashIndex( ) noexcept; + T_HashIndex( uint32_t hashSize , uint32_t indexSize , uint32_t growth = DEFAULT_GROWTH ) noexcept; + + T_HashIndex( T_HashIndex const& source ); + T_HashIndex( T_HashIndex&& source ) noexcept; + + ~T_HashIndex( ); + + T_HashIndex& operator =( T_HashIndex const& other ); + T_HashIndex& operator =( T_HashIndex&& other ) noexcept; + + friend void swap( T_HashIndex& lhs , T_HashIndex& rhs ) noexcept; + + void free( ); + void clear( ); + + void add( uint32_t key ); + void remove( uint32_t index ); + + uint32_t first( uint32_t key ) const; + uint32_t next( uint32_t index ) const; +}; +M_CLASS_POINTERS( HashIndex ); +void swap( T_HashIndex& lhs , T_HashIndex& rhs ) noexcept; + + +} // namespace +#include diff --git a/include/ebcl/HashTables.hh b/include/ebcl/HashTables.hh new file mode 100644 index 0000000..527b6e7 --- /dev/null +++ b/include/ebcl/HashTables.hh @@ -0,0 +1,203 @@ +/******************************************************************************/ +/* HASH TABLES ****************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_HASHTABLES +#define _H_LW_LIB_HASHTABLES +#include +#include +namespace lw { + + +// F_KeyMatch< K > - Equality check function for keys +template< typename K > +using F_KeyMatch = std::function< bool( K const& , K const& ) >; + + +// T_DefaultKeyMatch< K > - Default equality check for keys +template< typename K > +struct T_DefaultKeyMatch +{ + static bool keysMatch( K const& , K const& ); +}; + + +/*= KEY / VALUE TABLE ========================================================*/ + +/* + * T_KeyValueTable - Hash table with separate key and value storage. + * This class is meant to be used when data with unrelated types need to be + * associated with each other, e.g. string -> int mapping. + */ + +template< + typename KeyType , + typename ValueType + > +class T_KeyValueTable +{ + public: + typedef F_KeyMatch< KeyType > F_Match; + + private: + typedef T_KeyValueTable< KeyType , ValueType > MyType_; + + F_Match match_; + T_HashIndex index_; + T_Array< KeyType > keys_; + T_Array< ValueType > values_; + + public: + M_TEMPLATE_POINTERS( MyType_ ); + + // --------------------------------------------------------------------- + + T_KeyValueTable( uint32_t initialSize = T_HashIndex::DEFAULT_SIZE , + uint32_t hashSize = T_HashIndex::DEFAULT_SIZE , + uint32_t growth = T_HashIndex::DEFAULT_GROWTH , + F_Match match = T_DefaultKeyMatch< KeyType >::keysMatch ); + + template< typename K , typename T > + friend void swap( T_KeyValueTable< K , T >& lhs , T_KeyValueTable< K , T >& rhs ); + + // --------------------------------------------------------------------- + + // Add a new value (fails if key already present) + template< + typename A , typename B , + T_EnableIfTypesMatch< KeyType , A > = true , + T_EnableIfTypesMatch< ValueType , B > = true + > bool add( A&& k , B&& v ); + + // Update an existing value (fails if key not present) + template< + typename A , + T_EnableIfTypesMatch< ValueType , A > = true + > bool update( KeyType const& k , A&& v ); + + // Add or modify a key/value pair + template< + typename A , typename B , + T_EnableIfTypesMatch< KeyType , A > = true , + T_EnableIfTypesMatch< ValueType , B > = true + > void set( A&& k , B&& v ); + + // Remove a key/value pair + bool remove( KeyType const& k ); + + void clear( ); + void free( ); + + // --------------------------------------------------------------------- + + uint32_t size( ) const; + T_Array< KeyType > const& keys( ) const; + T_Array< ValueType > const& values( ) const; + uint32_t indexOf( KeyType const& k ) const; + bool contains( KeyType const& k ) const; + + ValueType const * get( KeyType const& k ) const; + ValueType * get( KeyType const& k ); + + ValueType& operator[] ( uint32_t index ); + ValueType const& operator[] ( uint32_t index ) const; + + // --------------------------------------------------------------------- + + private: + uint32_t find( KeyType const& k , uint32_t hash ) const; +}; + +template< typename K , typename T > +void swap( T_KeyValueTable< K , T >& lhs , T_KeyValueTable< K , T >& rhs ); + + +/*= OBJECT TABLE =============================================================*/ + +/* + * This class is meant to be used to store objects that somehow contain their + * own keys. Of course, modifying one of these objects' key after it's been + * inserted will cause major SNAFU's. + */ + +template< + typename KeyType , + typename ValueType + > +class T_ObjectTable +{ + public: + typedef F_KeyMatch< KeyType > F_Match; + typedef std::function< KeyType( ValueType const& ) > F_GetKey; + + private: + typedef T_ObjectTable< KeyType , ValueType > MyType_; + + F_Match match_; + F_GetKey keyGetter_; + T_HashIndex index_; + T_Array< ValueType > values_; + + public: + M_TEMPLATE_POINTERS( MyType_ ); + T_ObjectTable( ) = delete; + + // --------------------------------------------------------------------- + + T_ObjectTable( F_GetKey keyGetter , uint32_t initialSize = T_HashIndex::DEFAULT_SIZE , + uint32_t hashSize = T_HashIndex::DEFAULT_SIZE , + uint32_t growth = T_HashIndex::DEFAULT_GROWTH , + F_Match match = T_DefaultKeyMatch< KeyType >::keysMatch ); + + template< typename K , typename T > + friend void swap( T_ObjectTable< K , T >& lhs , T_ObjectTable< K , T >& rhs ); + + // --------------------------------------------------------------------- + + template< + typename A , + T_EnableIfTypesMatch< ValueType , A > = true + > bool add( A&& v ); + + template< + typename A , + T_EnableIfTypesMatch< ValueType , A > = true + > bool update( A&& v ); + + template< + typename A , + T_EnableIfTypesMatch< ValueType , A > = true + > void set( A&& v ); + + bool remove( KeyType const& k ); + + void clear( ); + void free( ); + + // --------------------------------------------------------------------- + + uint32_t size( ) const; + uint32_t indexOf( KeyType const& k ) const; + bool contains( KeyType const& k ) const; + T_Array< KeyType > keys( ) const; + T_Array< ValueType > const& values( ) const; + + ValueType const * get( KeyType const& k ) const; + ValueType * get( KeyType const& k ); + + ValueType& operator[] ( uint32_t index ); + ValueType const& operator[] ( uint32_t index ) const; + + // --------------------------------------------------------------------- + + private: + uint32_t find( KeyType const& k , uint32_t hash ) const; +}; + +template< typename K , typename T > +void swap( T_ObjectTable< K , T >& lhs , T_ObjectTable< K , T >& rhs ); + + +} +#endif // _H_LW_LIB_HASHTABLES +#include diff --git a/include/ebcl/MemoryStreams.hh b/include/ebcl/MemoryStreams.hh new file mode 100644 index 0000000..024905e --- /dev/null +++ b/include/ebcl/MemoryStreams.hh @@ -0,0 +1,62 @@ +/******************************************************************************/ +/* MEMORY STREAMS *************************************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +class T_MemoryInputStream final : public A_InputStream +{ + private: + uint8_t const* buffer_; + + public: + T_MemoryInputStream( ) = delete; + T_MemoryInputStream( void const* buffer , size_t size ); + + T_MemoryInputStream( T_MemoryInputStream const& other ) noexcept; + friend void swap( T_MemoryInputStream& lhs , T_MemoryInputStream& rhs ) noexcept; + + T_MemoryInputStream& operator= ( T_MemoryInputStream const& other ) noexcept; + + template< int S , typename T > + explicit T_MemoryInputStream( T_FixedBuffer< S , T > const& buffer ); + + template< typename T > + explicit T_MemoryInputStream( T_Buffer< T > const& buffer ); + + size_t read( void* data , size_t size ) override; +}; +M_CLASS_POINTERS( MemoryInputStream ); + + +/*----------------------------------------------------------------------------*/ + +class T_MemoryOutputStream final : public A_OutputStream +{ + typedef std::function< uint8_t*( uint8_t* , size_t ) > F_Resizer; + + private: + uint8_t* buffer_; + F_Resizer resizer_; + + public: + T_MemoryOutputStream( ) = delete; + + T_MemoryOutputStream( void* buffer , size_t size , F_Resizer resizer = nullptr ); + + template< int S , typename T > + explicit T_MemoryOutputStream( T_FixedBuffer< S , T >& buffer ); + + template< typename T > + explicit T_MemoryOutputStream( T_Buffer< T >& buffer ); + + size_t write( void const* data , size_t size ) override; +}; +M_CLASS_POINTERS( MemoryOutputStream ); + + +} // namespace +#include diff --git a/include/ebcl/Messages.hh b/include/ebcl/Messages.hh new file mode 100644 index 0000000..4e288c2 --- /dev/null +++ b/include/ebcl/Messages.hh @@ -0,0 +1,282 @@ +/******************************************************************************/ +/* UI<=>GAME MESSAGES *********************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_MESSAGES +#define _H_LW_LIB_MESSAGES +#include +#include +namespace lw { + + +/*= VIEWS ====================================================================*/ + +/* Empty abstract class that serves as a base for game views */ +class A_GameView +{ + public: + virtual ~A_GameView( ) = 0; +}; +M_ABSTRACT_POINTERS( GameView ); + + +/* Abstract class for a view builder + * + * User interfaces need to access the game's data in order to display it. View + * builders provide a way to do that; they are passed to the game's main loop + * by the UI. Once set, they will be used to create and update view data. + */ +class A_ViewBuilder +{ + public: + virtual ~A_ViewBuilder( ) = 0; + + /* This method creates a view instance. It is called twice when the + * builder is installed, in order to create a "double buffer" - one + * view can be sent to the UI while the other is being updated. + */ + virtual OP_GameView createView( ) = 0; + + /* This method is responsible for updating a view instance. */ + virtual void updateView( OP_GameView view ) = 0; +}; +M_ABSTRACT_POINTERS( ViewBuilder ); + + +/*= GAME STATE ===============================================================*/ + +/* State of the main loop */ +enum class E_GameState { + NO_GAME , // No game loaded + GAME_PAUSED , // Game is loaded but not running + GAME_SLOW , // Game is running at slow speed + GAME_NORMAL , // Game is running at normal speed + GAME_FAST // Game is running at high speed +}; +M_LSHIFT_OP( T_StringBuilder , E_GameState ); + + +/*= PROGRESS INFORMATION =====================================================*/ + +/* Progress information for long operations - part (main or sub) */ +class T_ProgressInfoPart +{ + private: + T_String text_; + uint32_t progress_; + uint32_t total_; + + public: + T_ProgressInfoPart( ) = delete; + T_ProgressInfoPart( T_String text , uint32_t progress , uint32_t total ) noexcept; + T_ProgressInfoPart( T_ProgressInfoPart&& other ) noexcept; + T_ProgressInfoPart( T_ProgressInfoPart const& other ); + + T_String const& text( ) const noexcept; + uint32_t progress( ) const noexcept; + uint32_t total( ) const noexcept; +}; + +/* Progress information for long operations - main */ +class T_ProgressInfo +{ + private: + T_ProgressInfoPart main_; + T_Optional< T_ProgressInfoPart > sub_; + + public: + T_ProgressInfo( ) = delete; + T_ProgressInfo( T_String text , uint32_t progress , uint32_t total ) noexcept; + explicit T_ProgressInfo( T_ProgressInfoPart main ) noexcept; + T_ProgressInfo( T_ProgressInfoPart main , T_ProgressInfoPart sub ) noexcept; + T_ProgressInfo( T_ProgressInfoPart main , + T_String sText , uint32_t sProgress , uint32_t sTotal ) noexcept; + T_ProgressInfo( T_String text , uint32_t progress , uint32_t total , + T_String sText , uint32_t sProgress , uint32_t sTotal ) noexcept; + T_ProgressInfo( T_ProgressInfo&& other ) noexcept; + T_ProgressInfo( T_ProgressInfo const& other ) = delete; + + T_ProgressInfoPart const& main( ) const noexcept; + bool hasSub( ) const noexcept; + T_ProgressInfoPart const& sub( ) const; +}; +M_CLASS_POINTERS( ProgressInfo ); + + +/*= MESSAGES =================================================================*/ + +/* Types of messages sent by the UI to the game */ +enum class E_GameUIMessage { + /* Create a new game + * Data: XXX + */ + NEW , + /* Load an existing game + * Data: uint32_t - game identifier + */ + LOAD , + /* Save the current game + * Data: none + */ + SAVE , + /* Quit the current game + * Data: none + */ + STOP , + /* Quit current game (if any) the exit the main loop + * Data: none + */ + QUIT , + + /* Abort the current operation + * Data: none + */ + ABORT , + + /* Delete a saved game + * Data: uint32_t - game identifier + */ + DELETE , + /* Copy or rename a saved game + * Data: XXX + */ + COPY_OR_RENAME , + + /* Set the current game's speed + * Data: E_GameState (but NO_GAME will be ignored) + */ + SET_SPEED , + /* Execute computation steps + * Data: XXX + */ + STEPS , + /* Set a view builder + * Data: XXX + */ + SET_VIEW , + /* Indicate that a view has been displayed + * Data: T_String - identifier of the view + */ + VIEW_DISPLAYED , + /* Send a query to the game + * Data: XXX + */ + QUERY , + /* Send a command to the game + * Data: XXX + */ + COMMAND +}; +M_LSHIFT_OP( T_StringBuilder , E_GameUIMessage ); + +/*----------------------------------------------------------------------------*/ + +/* Types of messages sent by the main loop to the UI */ +enum class E_GameLoopMessage { + /* Main loop exiting + * Data: none + */ + TERMINATED , + /* Progress information + * Data: T_ProgressInfo + */ + PROGRESS , + /* Operation completed + * Data: none + */ + DONE , + /* State changed + * Data: E_GameState + */ + STATE_CHANGED , + /* Indicate that a view is available + * Data: XXX + */ + VIEW_AVAILABLE , + /* Query response + * Data: XXX + */ + QUERY_RESPONSE , + /* Command execution - success + * Data: none + */ + COMMAND_OK , + /* Command execution - syntax error + * Data: XXX + */ + COMMAND_SYNTAX , + /* Command execution - error + * Data: XXX + */ + COMMAND_ERROR , +}; +M_LSHIFT_OP( T_StringBuilder , E_GameLoopMessage ); + +/*----------------------------------------------------------------------------*/ + +/* Types for message data */ +using T_GameUIMessageData = T_Union< + uint32_t , + T_String , + E_GameState + >; +using T_GameLoopMessageData = T_Union< + T_ProgressInfo , + E_GameState + >; + +/*----------------------------------------------------------------------------*/ + +/* Message direction */ +enum class E_MessageDirection { + FROM_UI_TO_GAME , + FROM_GAME_TO_UI +}; + +/* Message class template, can be used for both directions */ +template< + E_MessageDirection Direction , + typename EType = std::conditional_t< + Direction == E_MessageDirection::FROM_UI_TO_GAME , + E_GameUIMessage , E_GameLoopMessage > , + typename DType = std::conditional_t< + Direction == E_MessageDirection::FROM_UI_TO_GAME , + T_GameUIMessageData , T_GameLoopMessageData > +> +class T_GameMessage +{ + public: + using T_Type = EType; + using T_Data = DType; + + private: + using T_Self_ = T_GameMessage< Direction >; + T_Optional< T_Type > type_; + T_Optional< T_Data > data_; + + public: + constexpr T_GameMessage( ) noexcept; + constexpr T_GameMessage( T_Type type ) noexcept; + T_GameMessage( T_Type type , T_Data data ) noexcept; + T_GameMessage( T_Self_ const& ) = delete; + T_GameMessage( T_Self_&& other ) noexcept; + + T_Self_& operator =( T_Self_ const& ) = delete; + T_Self_& operator =( T_Self_&& other ) noexcept; + + constexpr bool hasMessage( ) const noexcept; + constexpr T_Type type( ) const noexcept; + + template< typename T > + constexpr T const& data( ) const; +}; + +/*----------------------------------------------------------------------------*/ + +using T_UIMessage = T_GameMessage< E_MessageDirection::FROM_UI_TO_GAME >; +using T_LoopMessage = T_GameMessage< E_MessageDirection::FROM_GAME_TO_UI >; + + +} // namespace lw +#endif // _H_LW_LIB_MESSAGES +#include diff --git a/include/ebcl/Mods.hh b/include/ebcl/Mods.hh new file mode 100644 index 0000000..88572b2 --- /dev/null +++ b/include/ebcl/Mods.hh @@ -0,0 +1,247 @@ +/******************************************************************************/ +/* MODDING SYSTEM *************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_MODS +#define _H_LW_LIB_MODS +#include +#include +namespace lw { + + +/*= MOD INFORMATION ==========================================================*/ + +/* Mod identifier (name + version) */ +struct T_ModIdentifier { + T_String name; + uint32_t version; // Main version number used for dependencies + + bool operator ==( T_ModIdentifier const& other ) const noexcept; + bool operator !=( T_ModIdentifier const& other ) const noexcept; + + int compare( T_ModIdentifier const& other ) const noexcept; +}; +M_CLASS_POINTERS( ModIdentifier ); +M_DECLARE_HASH( T_ModIdentifier ); +M_DECLARE_COMPARATOR( T_ModIdentifier ); +M_LSHIFT_OP( T_StringBuilder , T_ModIdentifier const& ); + +/* Types of mods */ +enum class E_ModType { + DATA , // This mod consists of data only + NATIVE , // This mod consists of native code + UI , // This mod is an user interface +}; + +/*----------------------------------------------------------------------------*/ + +/* Mod information record */ +struct T_ModInfo +{ + T_String path; + E_ModType type; + + /* Mod name, version and revision (the latter is for changes that don't + * affect the interface). + */ + T_ModIdentifier identifier; + uint32_t revision; + + /* Required library version number */ + uint32_t libVersion; + + /* List of dependencies */ + T_Array< T_ModIdentifier > modDeps; + + /* Is this mod an user interface mod? */ + bool isUserInterface( ) const noexcept; +}; +M_CLASS_POINTERS( ModInfo ); +M_LSHIFT_OP( T_StringBuilder , T_ModInfo const& ); + + +/*= MODS MANAGER CONFIGURATION ===============================================*/ + +/* Loading mode for a mod */ +enum class E_ModMode { + EXCLUDE , // Never load this mod + AUTO , // Load the most appropriate version of the mod + VERSION , // Load a specific version of the mod, fails + // if the mod in the specified version is not + // present + REQUIRE // Require that the specified mod be present + + // Note that both VERSION and REQUIRE can apply to an UI-specific mod; + // while the mod will not be loaded if the corresponding UI isn't, + // its presence will still be required. +}; + +/* Mods manager configuration */ +class T_ModsManagerConfiguration : public A_PrivateImplementation +{ + public: + T_ModsManagerConfiguration( ) noexcept; + + T_ModsManagerConfiguration( + T_ModsManagerConfiguration const& ) = delete; + T_ModsManagerConfiguration& operator =( + T_ModsManagerConfiguration const& ) = delete; + + T_ModsManagerConfiguration( + T_ModsManagerConfiguration&& ) noexcept; + T_ModsManagerConfiguration& operator =( + T_ModsManagerConfiguration&& other ) noexcept; + + // --------------------------------------------------------------------- + + /* Set a mod configuration entry */ + void setAuto( T_String const& name ) noexcept; + void setRequired( T_String const& name ) noexcept; + void setExcluded( T_String const& name ) noexcept; + void setVersion( T_String const& name , + uint32_t version ) noexcept; + void setVersion( T_String const& name , + uint32_t version , + uint32_t revision ) noexcept; + + /* Query mod configuration */ + T_Array< T_String > configured( ) const noexcept; + E_ModMode modeFor( T_String const& name ) const noexcept; + uint32_t requiredVersion( T_String const& name ) const noexcept; + T_Optional< uint32_t > requiredRevision( + T_String const& name ) const noexcept; + + // --------------------------------------------------------------------- + + /* Set or clear an UI preference */ + void setUIPreference( + T_String const& name , + int32_t weight ) noexcept; + void clearUIPreference( + T_String const& name ) noexcept; + + /* List UI preferences */ + T_Array< T_String > uiPreferences( ) const noexcept; + /* Get the preference for an UI */ + int32_t uiPreference( + T_String const& name ) const noexcept; + + // --------------------------------------------------------------------- + + /* Filter a list of mods using the configuration's exclusions and + * version limits. + */ + T_Array< RPC_ModInfo > filterMods( + T_Array< RPC_ModInfo > const& mods ) const noexcept; + /* Generate the list of required mods. This includes all mods that + * have been set as required, and all mods that have a version + * requirement. + */ + T_Array< T_String > requiredMods( ) const noexcept; + + // --------------------------------------------------------------------- + + /* Get a default configuration */ + static T_ModsManagerConfiguration DefaultConfiguration( ) noexcept; + + /* Get the parser's configuration used to load mods manager configuration */ + static T_SRDParserConfig GetParserConfig( ); +}; +M_CLASS_POINTERS( ModsManagerConfiguration ); + + +/*= MODS DEPENDENCY GRAPH ====================================================*/ + +class T_ModsDependencies : public A_PrivateImplementation +{ + public: + T_ModsDependencies( ) noexcept; + T_ModsDependencies( + T_Logger& logger , + T_Array< RPC_ModInfo > const& mods , + T_ModsManagerConfiguration const& config ) noexcept; + + T_ModsDependencies( + T_ModsDependencies const& other ) noexcept; + T_ModsDependencies& operator =( + T_ModsDependencies const& other ) noexcept; + + T_ModsDependencies( + T_ModsDependencies&& other ) noexcept; + T_ModsDependencies& operator =( + T_ModsDependencies&& other ) noexcept; + + friend M_DECLARE_SWAP( T_ModsDependencies ); + + // --------------------------------------------------------------------- + + bool resolved( ) const; + bool ambiguous( ) const; + T_Array< T_String > userInterfaces( ) const; + T_Array< RPC_ModInfo > const& commonMods( ) const; + T_Array< RPC_ModInfo >::RPC forUserInterface( + T_String const& name ) const; +}; +M_CLASS_POINTERS( ModsDependencies ); +M_DECLARE_SWAP( T_ModsDependencies ); + + +/*= MODS MANAGER =============================================================*/ + +/* Function type for a function that generates a feedback function used to + * update the initialisation progress when the mods are being initialised. + */ +using F_CreateInitUpdater = std::function< F_UpdateInitProgress( RPC_ModInfo ) >; + +class T_ModsManager : public A_PrivateImplementation +{ + public: + T_ModsManager( ) = delete; + T_ModsManager( T_ModsManager const& ) = delete; + T_ModsManager( T_ModsManager&& ) noexcept = delete; + + T_ModsManager( T_ModsManagerConfiguration&& config ) noexcept; + + // --------------------------------------------------------------------- + + /* Find available mods, load their descriptions */ + bool scanForMods( ); + /* Get the list of available mods */ + T_Array< RPC_ModInfo > availableMods( ) const noexcept; + + /* Resolve dependencies between mods using the configuration; + * returns true if there is a valid set of mods. + */ + bool resolveDependencies( ) noexcept; + + // --------------------------------------------------------------------- + + /* Pre-initialise common mods */ + bool preinitCommon( ) noexcept; + /* Pre-initialise UI mods and try to start the user interface. */ + OP_UserInterface preinitUIMods( + T_String const& ui ) noexcept; + /* Try to pre-initialise an UI mod. */ + OP_UserInterface preinitUIMods( ) noexcept; + + // --------------------------------------------------------------------- + + /* Get the amount of loaded mods */ + uint32_t modsCount( ) const noexcept; + /* Initialise the mods, sending progress updates along the way */ + bool initialise( + F_CreateInitUpdater const& fciu ) noexcept; + + // --------------------------------------------------------------------- + + /* Shutdown all initialised mods. */ + void shutdown( ) noexcept; + /* Unload all mods, calling post-shutdown routines if available */ + void unload( ) noexcept; +}; +M_CLASS_POINTERS( ModsManager ); + + +} // namespace lw +#endif // _H_LW_LIB_MODS +#include diff --git a/include/ebcl/Pointers.hh b/include/ebcl/Pointers.hh new file mode 100644 index 0000000..6965a4c --- /dev/null +++ b/include/ebcl/Pointers.hh @@ -0,0 +1,423 @@ +/******************************************************************************/ +/* POINTERS *******************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_POINTERS +#define _H_LW_LIB_POINTERS +#include +namespace lw { + +template< typename T > class T_OwnPtr; +template< typename T > class T_SharedPtr; +template< typename T > class T_WeakPtr; + +/*= OWNING POINTERS ==========================================================*/ + +template< typename T > +class T_OwnPtr +{ + template< typename RT , typename... AT > + friend T_OwnPtr< RT > NewOwned( AT&& ... arguments ); + + template< + typename RT + > friend T_OwnPtr< RT > OwnRawPointer( RT*& ) noexcept; + + template< + typename RT , typename OT , + T_EnableForChild< RT , OT > + > friend T_OwnPtr< RT > OwnRawPointer( OT*& ) noexcept; + + template< + typename RT , typename OT , + T_EnableForParent< RT , OT > + > friend T_OwnPtr< RT > OwnRawPointer( OT*& ); + + template< typename > + friend class T_OwnPtr; + + friend T_SharedPtr< T >; + + private: + typedef T_OwnPtr< T > MyType_; + + T* p_; + + T_OwnPtr( T* p ) noexcept; + + public: + T_OwnPtr( ); + ~T_OwnPtr( ); + + T_OwnPtr( MyType_ const& ) = delete; + MyType_& operator= ( MyType_ const& ) = delete; + + // --------------------------------------------------------------------- + + template< typename Q , T_EnableForChild< T , Q > = true > + T_OwnPtr( T_OwnPtr< Q >&& source ) noexcept; + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_OwnPtr( T_OwnPtr< Q >&& source ); + + // --------------------------------------------------------------------- + + template< typename Q , T_EnableForChild< T , Q > = true > + MyType_& operator= ( T_OwnPtr< Q >&& source ) noexcept; + + template< typename Q , T_EnableForParent< T , Q > = false > + MyType_& operator= ( T_OwnPtr< Q >&& source ); + + // --------------------------------------------------------------------- + + template< typename TP > + friend void swap( T_OwnPtr< TP >& lhs , T_OwnPtr< TP >& rhs ) noexcept; + + void clear( ); + + bool operator== ( const T* p ) const; + bool operator!= ( const T* p ) const; + operator bool ( ) const; + bool operator! ( ) const; + + T * get( ) const; + T* operator-> ( ) const; + T& operator* ( ) const; + + T_SharedPtr< T > makeShared( ); +}; + + +// NewOwned< T >( ... ) - New T as an owned pointer +template< + typename Type , + typename... ArgTypes + > +T_OwnPtr< Type > NewOwned( ArgTypes&& ... arguments ); + +// OwnRawPointer< T >( pointer ) - Transfer a raw pointer to an owned pointer. +// The raw pointer will be set to nullptr. +template< typename Type > +T_OwnPtr< Type > OwnRawPointer( Type*& pointer ) noexcept; + +template< + typename Type , typename Other , + T_EnableForChild< Type , Other > = true , + std::enable_if_t< !std::is_same< Type , Other >::value , bool > = true +> T_OwnPtr< Type > OwnRawPointer( Other*& pointer ) noexcept; + +template< + typename Type , typename Other , + T_EnableForParent< Type , Other > = false +> T_OwnPtr< Type > OwnRawPointer( Other*& pointer ); + + +/*= SHARED POINTERS ==========================================================*/ +/* WARNING: these pointers are NOT thread-safe! */ + +class T_Reference_; + +// Weak pointer chaining +struct T_WeakChain_ +{ + T_Reference_* * const ref; + T_WeakChain_* prev; + T_WeakChain_* next; + + explicit T_WeakChain_( T_Reference_*& ref ); + void unchain( ); + void init( ); +}; + + +// Base class for shared and weak pointers +template< typename T > +class T_BasePtr_ +{ + protected: + typedef T_BasePtr_< T > T_Base_; + + T_Reference_* ref_; + + explicit T_BasePtr_( T_Reference_* ref ) noexcept; + + T_BasePtr_( ) = delete; + T_BasePtr_( T_Base_ const& ) = delete; + T_BasePtr_( T_Base_&& ) = delete; + + public: + operator bool ( ) const; + bool operator! ( ) const; + + T* operator-> ( ) const; + explicit operator T* ( ) const; + T& operator* ( ) const; +}; + + +// Exception thrown when makeOwned( ) is called on a shared pointers that has +// more than one reference. +class X_TooManyReferences : public std::runtime_error +{ + public: + X_TooManyReferences( ); +}; + + +// T_SharedPtr< T > - Shared pointer implementation +template< typename T > +class T_SharedPtr : public T_BasePtr_< T > +{ + private: + typedef T_BasePtr_< T > T_Base_; + typedef T_SharedPtr< T > T_Self_; + typedef T_WeakPtr< T > T_Weak_; + + template< typename > + friend class T_SharedPtr; + + template< typename > + friend class T_WeakPtr; + + template< + typename RT + > friend T_SharedPtr< RT > ShareRawPointer( RT*& ) noexcept; + + template< + typename RT , typename OT , + T_EnableForChild< RT , OT > + > friend T_SharedPtr< RT > ShareRawPointer( OT*& ) noexcept; + + template< + typename RT , typename OT , + T_EnableForParent< RT , OT > + > friend T_SharedPtr< RT > ShareRawPointer( OT*& ); + + template< typename OT , typename... AT > + friend T_SharedPtr< OT > NewShared( AT&& ... arguments ); + friend T_OwnPtr< T >; + + using T_Base_::ref_; + + T_Reference_ * setRef( T_Reference_* from ); + void clearRef( ); + + explicit T_SharedPtr( T* ptr ); + + public: + T_SharedPtr( ) noexcept; + ~T_SharedPtr( ); + + // --------------------------------------------------------------------- + // Copy constructors + + T_SharedPtr( T_Self_ const& source ); + + template< typename Q , T_EnableForChild< T , Q > = true > + T_SharedPtr( T_SharedPtr< Q > const& source ); + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_SharedPtr( T_SharedPtr< Q > const& source ); + + // --------------------------------------------------------------------- + // Move constructors + + template< typename Q , T_EnableForChild< T , Q > = true > + T_SharedPtr( T_SharedPtr< Q >&& source ) noexcept; + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_SharedPtr( T_SharedPtr< Q >&& source ); + + // --------------------------------------------------------------------- + // Copy assignment operators + + T_Self_& operator= ( T_Self_ const& other ); + + template< typename Q , T_EnableForChild< T , Q > = true > + T_Self_& operator= ( T_SharedPtr< Q > const& other ); + + template< typename Q , T_EnableForParent< T , Q > = false > + T_Self_& operator= ( T_SharedPtr< Q > const& other ); + + // --------------------------------------------------------------------- + // Move assignment operators + + template< typename Q , T_EnableForChild< T , Q > = true > + T_Self_& operator= ( T_SharedPtr< Q >&& other ) noexcept; + + template< typename Q , T_EnableForParent< T , Q > = false > + T_Self_& operator= ( T_SharedPtr< Q >&& other ); + + // --------------------------------------------------------------------- + + template< typename TP > + friend void swap( T_SharedPtr< TP >& lhs , T_SharedPtr< TP >& rhs ) noexcept; + + bool operator== ( T_Self_ const& other ) const; + bool operator!= ( T_Self_ const& other ) const; + bool operator== ( T_Weak_ const& other ) const; + bool operator!= ( T_Weak_ const& other ) const; + + void clear( ); + + T_OwnPtr< T > makeOwned( ); +}; + + +// T_WeakPtr< T > - Weak pointer implementation +template< typename T > +class T_WeakPtr : public T_BasePtr_< T > +{ + private: + typedef T_BasePtr_< T > T_Base_; + typedef T_SharedPtr< T > T_Shared_; + typedef T_WeakPtr< T > T_Self_; + + template< typename > friend class T_SharedPtr; + template< typename > friend class T_WeakPtr; + friend T_Reference_; + + using T_Base_::ref_; + + T_WeakChain_ chain_; + + T_WeakPtr( T_Reference_* ref ); + + public: + + T_WeakPtr( ); + ~T_WeakPtr( ); + + // --------------------------------------------------------------------- + // Copy constructors + + T_WeakPtr( T_Self_ const& other ); + + template< typename Q , T_EnableForChild< T , Q > = true > + T_WeakPtr( T_WeakPtr< Q > const& other ); + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_WeakPtr( T_WeakPtr< Q > const& other ); + + // --------------------------------------------------------------------- + // Move constructors + + template< typename Q , T_EnableForChild< T , Q > = true > + T_WeakPtr( T_WeakPtr< Q >&& other ) noexcept; + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_WeakPtr( T_WeakPtr< Q >&& other ); + + // --------------------------------------------------------------------- + // Construct from shared pointer + + explicit T_WeakPtr( T_Shared_ const& shared ); + + template< typename Q , T_EnableForChild< T , Q > = true > + explicit T_WeakPtr( T_SharedPtr< Q > const& shared ); + + template< typename Q , T_EnableForParent< T , Q > = false > + explicit T_WeakPtr( T_SharedPtr< Q > const& shared ); + + explicit T_WeakPtr( T_Shared_&& shared ) = delete; + + // --------------------------------------------------------------------- + // Copy assignment operators + + T_Self_& operator= ( T_Self_ const& other ); + + template< typename Q , T_EnableForChild< T , Q > = true > + T_Self_& operator =( T_WeakPtr< Q > const& other ); + + template< typename Q , T_EnableForParent< T , Q > = false > + T_Self_& operator =( T_WeakPtr< Q > const& other ); + + // --------------------------------------------------------------------- + // Move assignment operators + + template< typename Q , T_EnableForChild< T , Q > = true > + T_Self_& operator =( T_WeakPtr< Q >&& other ); + + template< typename Q , T_EnableForParent< T , Q > = false > + T_Self_& operator =( T_WeakPtr< Q >&& other ); + + // --------------------------------------------------------------------- + // Assign from shared pointer + + T_Self_& operator= ( T_Shared_ const& shared ); + + template< typename Q , T_EnableForChild< T , Q > = true > + T_Self_& operator= ( T_SharedPtr< Q > const& shared ); + + template< typename Q , T_EnableForParent< T , Q > = false > + T_Self_& operator= ( T_SharedPtr< Q > const& shared ); + + T_Self_& operator= ( T_Shared_&& ) = delete; + + // --------------------------------------------------------------------- + + template< typename TP > + friend void swap( T_WeakPtr< TP >& lhs , T_WeakPtr< TP >& rhs ); + + bool operator== ( T_Self_ const& other ) const; + bool operator!= ( T_Self_ const& other ) const; + bool operator== ( T_Shared_ const& other ) const; + bool operator!= ( T_Shared_ const& other ) const; + + void clear( ); +}; + + +// NewShared< T >( ... ) - New T as shared pointer +template< + typename Type , + typename... ArgTypes + > +T_SharedPtr< Type > NewShared( ArgTypes&& ... arguments ); + +// ShareRawPointer< T >( pointer ) - Transfer a raw pointer to a shared pointer. +// The raw pointer will be set to nullptr. +template< typename Type > +T_SharedPtr< Type > ShareRawPointer( + Type*& pointer ) noexcept; + +template< + typename Type , typename Other , + T_EnableForChild< Type , Other > = true , + std::enable_if_t< !std::is_same< Type , Other >::value , bool > = true +> T_SharedPtr< Type > ShareRawPointer( + Other*& pointer ) noexcept; + +template< + typename Type , typename Other , + T_EnableForParent< Type , Other > = false +> T_SharedPtr< Type > ShareRawPointer( Other*& pointer ); + + +/*= HELPER MACROS ============================================================*/ + +#define M_CLASS_POINTERS( NAME ) \ + typedef T_ ## NAME* RP_ ## NAME; \ + typedef T_ ## NAME const* RPC_ ## NAME; \ + typedef T_OwnPtr< T_ ## NAME > OP_ ## NAME; \ + typedef T_SharedPtr< T_ ## NAME > SP_ ## NAME; \ + typedef T_WeakPtr< T_ ## NAME > WP_ ## NAME + +#define M_ABSTRACT_POINTERS( NAME ) \ + typedef A_ ## NAME* RP_ ## NAME; \ + typedef A_ ## NAME const* RPC_ ## NAME; \ + typedef T_OwnPtr< A_ ## NAME > OP_ ## NAME; \ + typedef T_SharedPtr< A_ ## NAME > SP_ ## NAME; \ + typedef T_WeakPtr< A_ ## NAME > WP_ ## NAME + +#define M_TEMPLATE_POINTERS( NAME ) \ + typedef NAME* RP; \ + typedef NAME const* RPC; \ + typedef T_OwnPtr< NAME > OP; \ + typedef T_SharedPtr< NAME > SP; \ + typedef T_WeakPtr< NAME > WP + + +} // namespace +#endif // _H_LW_LIB_POINTERS +#include diff --git a/include/ebcl/Registration.hh b/include/ebcl/Registration.hh new file mode 100644 index 0000000..aae6972 --- /dev/null +++ b/include/ebcl/Registration.hh @@ -0,0 +1,85 @@ +/******************************************************************************/ +/* REGISTRATION SUPPORT *******************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_REGISTRATION +#define _H_LW_LIB_REGISTRATION +#include +#include +namespace lw { + + +/*= REGISTRATION SUPPORT =====================================================*/ + +/* This class is meant to be used with any class that supports registration of + * items (for example the VFS to which VFS drivers are added). + * + * It must be returned from registration methods, and will serve as a way to + * unregister an item, either directly using the unregister() method, or + * automatically when the instance is destroyed (if automatic unregistration + * is active, which is the default). + */ +class T_RegisteredItem +{ + public: + using F_Unregister = std::function< void( void* ) >; + using SP_Unregister = T_SharedPtr< F_Unregister >; + + private: + using F_Destructor_ = std::function< void( void* ) >; + using WP_Unregister_ = T_WeakPtr< F_Unregister >; + + bool automatic_; + WP_Unregister_ unregisterFunction_; + void* data_; + F_Destructor_ dataDestructor_; + + public: + /* Initialise an empty item (indicates registration failure) */ + T_RegisteredItem( ) noexcept; + + /* Initialise a registered item */ + T_RegisteredItem( SP_Unregister& unregisterFunction , + void* data , + F_Destructor_ destructor ) noexcept; + template< typename T > + T_RegisteredItem( SP_Unregister& unregisterFunction , + T* data ) noexcept; + + /* Destructor. Will unregister the item if automatic mode is + * enabled */ + ~T_RegisteredItem( ); + + /* Copy is disabled */ + T_RegisteredItem( T_RegisteredItem const& ) = delete; + T_RegisteredItem& operator =( T_RegisteredItem const& ) = delete; + + /* Move and swap */ + T_RegisteredItem( T_RegisteredItem&& other ) noexcept; + T_RegisteredItem& operator =( T_RegisteredItem&& other ) noexcept; + friend M_DECLARE_SWAP( T_RegisteredItem ); + + /* Set whether this registrable will unregister automatically when + * it is deleted. */ + void automatic( bool v ) noexcept; + bool automatic( ) const noexcept; + + /* Unregister this item. May be called multiple times; the first + * call will unregister the item, other calls will be ignored. + */ + void unregister( ) noexcept; + + /* Registration check */ + operator bool( ) const noexcept; + + private: + void clear( ) noexcept; + void deleteData( ) noexcept; +}; +M_CLASS_POINTERS( RegisteredItem ); +M_DECLARE_SWAP( T_RegisteredItem ); + + +} // namespace +#endif // _H_LW_LIB_REGISTRATION +#include diff --git a/include/ebcl/SRDBinary.hh b/include/ebcl/SRDBinary.hh new file mode 100644 index 0000000..2fc8612 --- /dev/null +++ b/include/ebcl/SRDBinary.hh @@ -0,0 +1,78 @@ +/******************************************************************************/ +/* SRD - BINARY STORAGE *******************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDBINARY +#define _H_LW_LIB_SRDBINARY +#include +#include +namespace lw { + + +/*= WRITER ===================================================================*/ + +class T_SRDBinaryWriter : public A_SRDWriter +{ + private: + T_BinaryWriter writer_; + T_HashIndex wordsIndex_; + T_Array< T_String > words_; + T_StringBuilder comment_; + uint32_t depth_; + + void flushComment( ); + void writeWord( T_String const& word ); + + public: + T_SRDBinaryWriter( ) = delete; + T_SRDBinaryWriter( T_SRDBinaryWriter const& ) = delete; + T_SRDBinaryWriter( T_SRDBinaryWriter&& ) = delete; + + explicit T_SRDBinaryWriter( A_OutputStream& output ); + + T_SRDBinaryWriter& start( ) override; + + T_SRDBinaryWriter& startList( ) override; + T_SRDBinaryWriter& endList( ) override; + + T_SRDBinaryWriter& putText( T_String const& text ) override; + T_SRDBinaryWriter& putWord( T_String const& word ) override; + T_SRDBinaryWriter& putString( T_String const& string ) override; + + T_SRDBinaryWriter& putComment( T_String const& comment ) override; + + T_SRDBinaryWriter& putVariable( T_String const& variable ) override; + + T_SRDBinaryWriter& putBinary( T_Buffer< uint8_t > const& binary ) override; + + T_SRDBinaryWriter& putInteger( int64_t value ) override; + T_SRDBinaryWriter& putInt32( int32_t value ) override; + T_SRDBinaryWriter& putInt64( int64_t value ) override; + + T_SRDBinaryWriter& putFloat( double value ) override; + + T_SRDBinaryWriter& end( ) override; +}; + +void SRDBinaryWriteTo( A_OutputStream& output , T_SRDList const& data ); + + +/*= READER ===================================================================*/ + +class T_SRDBinaryReader : public A_SRDReader +{ + public: + T_SRDBinaryReader( ) = delete; + T_SRDBinaryReader( T_SRDBinaryReader const& ) = delete; + T_SRDBinaryReader( T_SRDBinaryReader&& ) = delete; + + T_SRDBinaryReader( A_SRDReaderTarget& target ); + void read( T_String const& name , A_InputStream& input ) override; +}; + +T_SRDList SRDBinaryReadFrom( T_String const& name , A_InputStream& input , bool structured = true ); + + +} // namespace +#include +#endif // _H_LW_LIB_SRDBINARY diff --git a/include/ebcl/SRDData.hh b/include/ebcl/SRDData.hh new file mode 100644 index 0000000..54fa63d --- /dev/null +++ b/include/ebcl/SRDData.hh @@ -0,0 +1,361 @@ +/******************************************************************************/ +/* SRD - DATA *****************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDDATA +#define _H_LW_LIB_SRDDATA +#include +#include +#include +namespace lw { + +class T_Logger; + +// Forward declarations +struct T_SRDLocationChaining; +M_CLASS_POINTERS( SRDLocationChaining ); +class T_SRDLocation; +M_CLASS_POINTERS( SRDLocation ); +class T_SRDToken; +M_CLASS_POINTERS( SRDToken ); + + +/*= SOURCE LOCATIONS =========================================================*/ + +// E_SRDLocationChaining - Token location chaining circumstances +enum class E_SRDLocationChaining { + INCLUDED , + LOADED , + CALLED , + GENERATED , + SUBSTITUTED , + EVALUATED , + EXPANDED +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDLocationChaining - Token location chaining structure +struct T_SRDLocationChaining +{ + E_SRDLocationChaining circumstances; + uint32_t depth; + SP_SRDLocation location; + + T_SRDLocationChaining( + E_SRDLocationChaining how , + uint32_t depth , + SP_SRDLocation const& to ) noexcept; + + bool isGenerated( ) const noexcept; +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDLocation - Token source location information +class T_SRDLocation +{ + private: + T_String source_; + uint32_t line_; + size_t character_; + T_Optional< T_SRDLocationChaining > chaining_; + + public: + // SRD locations are pooled + void* operator new( size_t count ) noexcept; + void operator delete( void* object ) noexcept; + + // --------------------------------------------------------------------- + + // Unknown location + T_SRDLocation( ) noexcept; + // Text input location + T_SRDLocation( T_String const& source , + uint32_t line , + size_t character ) noexcept; + // Binary input location + T_SRDLocation( T_String const& source , + size_t byte ) noexcept; + // Location of a token + explicit T_SRDLocation( + T_SRDToken const& token ) noexcept; + + T_SRDLocation( T_SRDLocation const& other ) noexcept; + T_SRDLocation& operator= ( T_SRDLocation const& other ) noexcept; + + T_SRDLocation( T_SRDLocation&& other ) noexcept; + T_SRDLocation& operator= ( T_SRDLocation&& other ) noexcept; + + friend M_DECLARE_SWAP( T_SRDLocation ); + + // --------------------------------------------------------------------- + + void chain( E_SRDLocationChaining how , + SP_SRDLocation const& to ) noexcept; + void chain( E_SRDLocationChaining how , + uint32_t depth , + SP_SRDLocation const& to ) noexcept; + void clearChain( ) noexcept; + + // --------------------------------------------------------------------- + + bool unknown( ) const noexcept; + T_String const& source( ) const noexcept; + bool binary( ) const noexcept; + uint32_t line( ) const noexcept; + size_t character( ) const noexcept; + size_t byte( ) const noexcept; + + bool isChained( ) const noexcept; + T_SRDLocationChaining const& chaining( ) const noexcept; +}; +M_DECLARE_SWAP( T_SRDLocation ); +M_LSHIFT_OP( T_StringBuilder , T_SRDLocation ) noexcept; + + +/*= ERRORS ===================================================================*/ + +// An error that occurred during SRD processing +class T_SRDError +{ + private: + T_String error_; + T_SRDLocation location_; + bool isDetails_; + + public: + T_SRDError( ) = delete; + + T_SRDError( T_String error , + T_SRDLocation location , + bool details = false ) noexcept; + + T_SRDError( T_SRDError const& other ) noexcept; + T_SRDError& operator= ( T_SRDError const& other ) noexcept; + + T_SRDError( T_SRDError&& other ) noexcept; + T_SRDError& operator= ( T_SRDError&& other ) noexcept; + + friend M_DECLARE_SWAP( T_SRDError ); + + // --------------------------------------------------------------------- + + T_String const& error( ) const noexcept; + T_SRDLocation const& location( ) const noexcept; + bool isDetails( ) const noexcept; +}; +M_DECLARE_SWAP( T_SRDError ); + +/*----------------------------------------------------------------------------*/ + +// List of errors +class T_SRDErrors +{ + public: + enum : uint32_t { MAX_ERRORS = 40 }; + + private: + T_AutoArray< T_SRDError , MAX_ERRORS / 2 > errors_; + uint32_t errCount_ = 0; + + public: + // --------------------------------------------------------------------- + + template< typename ... ArgTypes > + void add( char const* error , + ArgTypes&&... locationArgs ); + template< typename ... ArgTypes > + void add( T_String const& error , + ArgTypes&&... locationArgs ); + + template< typename ... ArgTypes > + void details( char const* message , + ArgTypes&&... locationArgs ); + template< typename ... ArgTypes > + void details( T_String const& message , + ArgTypes&&... locationArgs ); + + template< typename ... ArgTypes > + void add( InPlace , ArgTypes&&... args ); + void add( T_SRDError const& error ); + void add( T_SRDError&& error ); + + void addAll( T_SRDErrors const& errors ); + + // --------------------------------------------------------------------- + + uint32_t size( ) const noexcept; + T_SRDError const& operator[] ( uint32_t index ) const noexcept; + + void clear( ) noexcept; + + // --------------------------------------------------------------------- + + private: + void checkAdded( T_SRDError const& last ); +}; +M_CLASS_POINTERS( SRDErrors ); + +/*----------------------------------------------------------------------------*/ + +// SRD processing exception +class X_SRDErrors : public std::exception +{ + public: + const T_SRDErrors errors; + X_SRDErrors( T_SRDErrors const& errors ); + char const * what( ) const noexcept override; +#ifndef LW_MINLIB + void log( T_Logger& logger ) const; +#endif // LW_MINLIB +}; + + +/*= TOKENS ===================================================================*/ + +// E_SRDTokenType - Token types +enum class E_SRDTokenType { + LIST , // Complete list + START , // List start + END , // List end + WORD , // Word + VAR , // Variable + STRING , // Quoted string + BINARY , // Binary data + INT , // Integer (32 bits) + LONG , // Integer (64 bits) + FLOAT , // Floating point value + COMMENT , // Commented text + FLUSH , // Special token that causes a parser to flush +}; +T_StringBuilder& operator<< ( T_StringBuilder& sb , E_SRDTokenType tt ); + +// T_SRDList - Token lists +using T_SRDList = T_Array< T_SRDToken >; +M_CLASS_POINTERS( SRDList ); + +// T_SRDToken - Token data +class T_SRDToken +{ + private: + using T_BinData_ = T_SharedPtr< T_Buffer< uint8_t > >; + + E_SRDTokenType type_; // Token type + T_String text_; // Token's full string + OP_SRDList list_; // List of tokens (type == LIST) + int64_t longValue_; // 64-bit integer value + double floatValue_; // 64-bit floating point value + T_String stringValue_; // String value + T_BinData_ binary_; // Binary data + + SP_SRDLocation location_; + + T_SRDToken( ) = default; + T_SRDToken( const E_SRDTokenType type ) noexcept; + + public: + // --------------------------------------------------------------------- + + static bool IsWord( T_String const& string ); + + // --------------------------------------------------------------------- + + static T_SRDToken ListStart( ); + static T_SRDToken ListEnd( ); + + static T_SRDToken List( ); + static T_SRDToken List( T_SRDList const& list ); + static T_SRDToken List( T_SRDList&& list ); + + // Either a Word or a String, depending on the contents + static T_SRDToken AutoText( T_String text ); + static T_SRDToken Word( T_String word ); + static T_SRDToken String( T_String string ); + + static T_SRDToken Variable( T_String variable ); + static T_SRDToken Comment( T_String text ); + + // Binary data + static T_SRDToken Binary( + T_BinData_ const& data ) noexcept; + template< typename T > + static T_SRDToken Binary( + T const* data , + uint32_t count ) noexcept; + template< typename T > + static T_SRDToken Binary( + T_Buffer< T > const& data ) noexcept; + + // A Long or an Int, depending on the value + static T_SRDToken AutoInteger( int64_t value ); + static T_SRDToken Integer( int32_t value ); + static T_SRDToken Long( int64_t value ); + static T_SRDToken Float( double value ); + + // A special "flush" token + static T_SRDToken Flush( ) noexcept; + + // --------------------------------------------------------------------- + + T_SRDToken( T_SRDToken const& other ); + T_SRDToken( T_SRDToken&& other ) noexcept; + T_SRDToken& operator= ( T_SRDToken const& other ); + T_SRDToken& operator= ( T_SRDToken&& other ) noexcept; + + friend void swap( T_SRDToken& lhs , T_SRDToken& rhs ) noexcept; + + // --------------------------------------------------------------------- + + E_SRDTokenType type( ) const; + bool isText( ) const; + bool isNumeric( ) const; + bool isInteger( ) const; + + T_String const& text( ) const; + T_SRDList const& list( ) const; + T_SRDList& list( ); + int64_t longValue( ) const; + double floatValue( ) const; + T_String const& stringValue( ) const; + T_Buffer< uint8_t > const& binary( ) const; + + // --------------------------------------------------------------------- + + int compare( T_SRDToken const& other ) const; + bool operator ==( T_SRDToken const& other ) const; + bool operator !=( T_SRDToken const& other ) const; + bool operator >( T_SRDToken const& other ) const; + bool operator <( T_SRDToken const& other ) const; + bool operator >=( T_SRDToken const& other ) const; + bool operator <=( T_SRDToken const& other ) const; + + // --------------------------------------------------------------------- + + bool hasFullText( ) const; + T_String fullText( ) const; + T_SRDToken& setFullText( T_String text ); + T_SRDToken& generateFullText( ); + + // --------------------------------------------------------------------- + + bool hasLocation( ) const noexcept; + T_SRDLocation const& location( ) const noexcept; + T_SRDLocation& location( ) noexcept; + + T_SRDToken& location( T_String const& source , size_t byte ); + T_SRDToken& location( T_String const& source , uint32_t line , size_t character ); + T_SRDToken& location( T_SRDLocation const& other ); + T_SRDToken& location( T_SRDLocation&& other ); + T_SRDToken& copyLocationOf( T_SRDToken const& other ); +}; +void swap( T_SRDToken& lhs , T_SRDToken& rhs ) noexcept; +M_DECLARE_COMPARATOR( T_SRDToken ); + + +extern template class T_Array< T_SRDToken >; + + +} // namespace +#include +#endif // _H_LW_LIB_SRDDATA diff --git a/include/ebcl/SRDDefinitions.hh b/include/ebcl/SRDDefinitions.hh new file mode 100644 index 0000000..aa70321 --- /dev/null +++ b/include/ebcl/SRDDefinitions.hh @@ -0,0 +1,321 @@ +/******************************************************************************/ +/* SRD - PARSER DEFINITIONS ***************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDDEFINITIONS +#define _H_LW_LIB_SRDDEFINITIONS +#include +#include +namespace lw { + + +/*= DEFINITION DATA ==========================================================*/ + +// Full definition -> {contexts}, default context, {enums} +// Context -> name, {rules} +// Rule -> Accepted input, [context, [init], [exit]], [executor] +// Input def -> Word +// | Enum +// | Token type +// | Alternatives +// | Repetition +// Alternatives -> {input def} +// Repetition -> Input sequence, min count, max count + +// Forward declarations +struct T_SRDParserData; + +/*----------------------------------------------------------------------------*/ + +// F_SRDHandler - Parser callbacks +using F_SRDHandler = std::function< bool( T_SRDParserData const& ) >; + +/*----------------------------------------------------------------------------*/ + +// T_SRDEnum - an enumeration +// i.e. set of words with associated sequence of integers, that can be +// recognized as a single input token. +class T_SRDEnum +{ +private: + T_String name_; + T_HashIndex index_; + T_Array< T_String > words_; + +public: + T_SRDEnum( ) = delete; + + T_SRDEnum( T_String name ); + T_SRDEnum( T_SRDEnum const& ) = default; + T_SRDEnum( T_SRDEnum && ) = default; + + T_SRDEnum & operator <<( T_String && word ); + T_SRDEnum & operator <<( char const* word ); + + T_String const& name( ) const; + auto size( ) const; + + T_String const& operator[]( int index ) const; + T_String const& operator[]( uint32_t index ) const; + + uint32_t operator[]( T_String const& word ) const; + uint32_t operator[]( char const* word ) const; + + bool contains( T_String const& word ) const; + bool contains( char const* word ) const; +}; + +/*----------------------------------------------------------------------------*/ + +// E_SRDInputItem - Possible types for input definition elements +enum class E_SRDInputItem +{ + WORD , // A specific word + ENUM , // A word from an enumeration + TOKEN , // An arbitrary token + ALTERNATIVE , // A set of alternative rules + REPETITION , // Repetition of a sequence +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDInputItem - An element from an input definition +class T_SRDInputItem +{ +public: + struct T_ListHelper { + T_Array< T_SRDInputItem > items; + T_ListHelper( ); + T_ListHelper& operator <<( T_SRDInputItem item ); + }; + +private: + E_SRDInputItem type_; + T_String word_; + E_SRDTokenType token_; + T_Array< T_SRDInputItem > items_; + uint32_t min_ , max_; + +public: + // Construct an ALTERNATIVE input element + T_SRDInputItem( ); + // Construct either a WORD or an ENUM input element + T_SRDInputItem( T_String wordOrName , bool isWord ); + // Construct a WORD from a C string + T_SRDInputItem( char const* cString ); + // Construct a TOKEN input element + T_SRDInputItem( E_SRDTokenType type ); + // Construct a REPETITION input element + T_SRDInputItem( uint32_t min , uint32_t max ); + + friend void swap( T_SRDInputItem & lhs , T_SRDInputItem & rhs ) noexcept; + + T_SRDInputItem( T_SRDInputItem const& ) = default; + T_SRDInputItem( T_SRDInputItem && ) noexcept = default; + T_SRDInputItem & operator=( T_SRDInputItem const& ) = default; + T_SRDInputItem & operator=( T_SRDInputItem && ) = default; + + E_SRDInputItem type( ) const; + T_String const& word( ) const; + E_SRDTokenType token( ) const; + T_Array< T_SRDInputItem > items( ) const; + uint32_t min( ) const; + uint32_t max( ) const; + + T_SRDInputItem & operator <<( T_SRDInputItem item ); + T_SRDInputItem& operator <<( T_ListHelper list ); + + // This operator is not actually used. It's only here to make the template + // instantiation happy. It actually returns false. + bool operator ==( T_SRDInputItem const& other ) const; +}; +void swap( T_SRDInputItem & lhs , T_SRDInputItem & rhs ) noexcept; +T_StringBuilder & operator <<( T_StringBuilder & sb , T_SRDInputItem const& item ); + +T_Array< T_SRDInputItem >& operator <<( T_Array< T_SRDInputItem > & items , + T_SRDInputItem::T_ListHelper& list ); + +extern template class T_Array< T_SRDInputItem >; + +/*----------------------------------------------------------------------------*/ + +// T_SRDInputRule - A full rule from a context +class T_SRDInputRule +{ +public: + struct T_SetContextExecutor { + const bool exit; + const F_SRDHandler executor; + T_SetContextExecutor( bool exit , F_SRDHandler executor ); + }; + struct T_SetContext { + const T_String name; + T_SetContext( T_String string ); + }; + +private: + F_SRDHandler executor_; + T_Array< T_SRDInputItem > items_; + + T_String context_; + F_SRDHandler contextEnter_; + F_SRDHandler contextExit_; + +public: + T_SRDInputRule & operator <<( T_SRDInputItem item ); + T_SRDInputRule & operator <<( F_SRDHandler executor ); + T_SRDInputRule & operator <<( T_SetContextExecutor sce ); + T_SRDInputRule & operator <<( T_SetContext sc ); + T_SRDInputRule & operator <<( T_SRDInputItem::T_ListHelper sc ); + + F_SRDHandler const& handler( ) const; + T_Array< T_SRDInputItem > const& rule( ) const; + T_String const& context( ) const; + F_SRDHandler const& onEnter( ) const; + F_SRDHandler const& onExit( ) const; +}; +T_StringBuilder & operator <<( T_StringBuilder & sb , T_SRDInputRule const& item ); + +/*----------------------------------------------------------------------------*/ + +// T_SRDContext - A set of rules that will share common data +class T_SRDContext +{ +private: + T_String name_; + T_String parent_; + T_Array< T_SRDInputRule > rules_; + +public: + T_SRDContext( ) = delete; + + explicit T_SRDContext( T_String name , T_String parent = T_String( ) ); + + T_SRDContext & operator <<( T_SRDInputRule rule ); + + T_String const& name( ) const; + T_String const& parent( ) const; + T_Array< T_SRDInputRule > const& rules( ) const; + + T_StringBuilder & dump( T_StringBuilder & sb , T_String const& separator ); +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDParserDefs - All definitions for a parser +class T_SRDParserDefs +{ +public: + // SetHandler - Used to set start/end handlers + struct SetHandler + { + const F_SRDHandler handler; + const bool start; + SetHandler( F_SRDHandler const& handler , bool start ); + }; + +private: + T_String dfCtx_; + T_ObjectTable< T_String , T_SRDContext > ctx_; + T_ObjectTable< T_String , T_SRDEnum > enums_; + + F_SRDHandler onStart_; + F_SRDHandler onFinish_; + +public: + T_SRDParserDefs( ) = delete; + + explicit T_SRDParserDefs( T_String defaultContext ); + explicit T_SRDParserDefs( char const* defaultContext ); + + T_String const& defaultContext( ) const; + void defaultContext( T_String const& name ); + void defaultContext( char const* name ); + + T_SRDParserDefs & operator <<( SetHandler sh ); + F_SRDHandler const& onStart( ) const; + F_SRDHandler const& onFinish( ) const; + + uint32_t contexts( ) const; + T_SRDContext & operator[]( uint32_t idx ); + T_SRDContext const& operator[]( uint32_t idx ) const; + + uint32_t contextId( T_String const& name ) const; + + T_SRDContext & context( T_String const& name ); + T_SRDContext & context( char const* name ); + T_SRDContext & context( T_String const& name , T_String const& parent ); + T_SRDContext & context( char const* name , char const* parent ); + + uint32_t enums( ) const; + bool hasEnum( T_String const& name ) const; + T_SRDEnum & enumeration( char const* name ); + T_SRDEnum & enumeration( T_String const& name ); + T_SRDEnum const& enumeration( T_String const& name ) const; +}; +M_CLASS_POINTERS( SRDParserDefs ); + + +/*= PARSER DEFINITIONS HELPERS ===============================================*/ + +namespace SRD { + +T_SRDParserDefs::SetHandler OnStart( F_SRDHandler const& handler ); +T_SRDParserDefs::SetHandler OnFinish( F_SRDHandler const& handler ); + +T_SRDContext Context( T_String name ); +T_SRDContext Context( char const* name ); + +T_SRDContext Context( char const* name , char const* parent ); +T_SRDContext Context( T_String name , T_String parent ); + +T_SRDInputRule Rule( ); + +T_SRDEnum CreateEnum( T_String name ); +T_SRDEnum CreateEnum( char const* name ); + +T_SRDInputItem Word( T_String const& word ); +T_SRDInputItem Word( char const* word ); + +T_SRDInputItem Word( ); +T_SRDInputItem String( ); +T_SRDInputItem Binary( ); +T_SRDInputItem LStart( ); +T_SRDInputItem LEnd( ); +T_SRDInputItem Int32( ); +T_SRDInputItem Int64( ); +T_SRDInputItem Float( ); + +T_SRDInputItem Enum( T_String name ); +T_SRDInputItem Enum( char const* name ); + +T_SRDInputItem::T_ListHelper List( ); + +T_SRDInputItem Alt( ); + +T_SRDInputItem Sub( ); + +T_SRDInputItem Opt( ); +T_SRDInputItem Opt( T_SRDInputItem item ); + +T_SRDInputItem AtLeast( uint32_t n ); +T_SRDInputItem AtMost( uint32_t n ); +T_SRDInputItem Times( uint32_t n ); +T_SRDInputItem Between( uint32_t a , uint32_t b ); + +T_SRDInputItem Integer( ); +T_SRDInputItem Numeric( ); +T_SRDInputItem Text( ); + +T_SRDInputRule::T_SetContext EnterContext( T_String name ); +T_SRDInputRule::T_SetContext EnterContext( char const* name ); + +T_SRDInputRule::T_SetContextExecutor OnEnter( F_SRDHandler f ); +T_SRDInputRule::T_SetContextExecutor OnExit( F_SRDHandler f ); + +} // namespace SRD + + +} // namespace +#include +#endif // _H_LW_LIB_SRDDEFINITIONS diff --git a/include/ebcl/SRDIO.hh b/include/ebcl/SRDIO.hh new file mode 100644 index 0000000..d2b02ca --- /dev/null +++ b/include/ebcl/SRDIO.hh @@ -0,0 +1,187 @@ +/******************************************************************************/ +/* SRD - INPUT AND OUTPUT *****************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDIO +#define _H_LW_LIB_SRDIO +#include +namespace lw { + + +/*= INPUT / OUTPUT ABSTRACTIONS ==============================================*/ + +// A_SRDWriter - Abstract SRD writer +class A_SRDWriter +{ + protected: + A_SRDWriter( ) = default; + + public: + A_SRDWriter( A_SRDWriter const& ) = delete; + A_SRDWriter( A_SRDWriter&& ) = delete; + + virtual ~A_SRDWriter( ) = 0; + + virtual A_SRDWriter& start( ) = 0; + + virtual A_SRDWriter& startList( ) = 0; + virtual A_SRDWriter& endList( ) = 0; + + A_SRDWriter& putList( T_SRDList const& list ); + A_SRDWriter& putToken( T_SRDToken const& token ); + + virtual A_SRDWriter& putText( T_String const& text ) = 0; + virtual A_SRDWriter& putWord( T_String const& word ) = 0; + virtual A_SRDWriter& putString( T_String const& string ) = 0; + + virtual A_SRDWriter& putComment( T_String const& comment ) = 0; + + virtual A_SRDWriter& putVariable( T_String const& var ) = 0; + + virtual A_SRDWriter& putBinary( T_Buffer< uint8_t > const& binary ) = 0; + + virtual A_SRDWriter& putInteger( int64_t value ) = 0; + virtual A_SRDWriter& putInt32( int32_t value ) = 0; + virtual A_SRDWriter& putInt64( int64_t value ) = 0; + + virtual A_SRDWriter& putFloat( double value ) = 0; + + virtual A_SRDWriter& end( ) = 0; +}; +M_ABSTRACT_POINTERS( SRDWriter ); + +/*----------------------------------------------------------------------------*/ + +// A_SRDReaderTarget - Abstract token receiver for SRD readers +class A_SRDReaderTarget +{ + public: + virtual ~A_SRDReaderTarget( ) = 0; + virtual void start( T_SRDErrors& errors ) = 0; + virtual void push( T_SRDErrors& errors , T_SRDToken&& token ) = 0; + virtual void end( T_SRDErrors& errors ) = 0; +}; +M_ABSTRACT_POINTERS( SRDReaderTarget ); + +/*----------------------------------------------------------------------------*/ + +// A_SRDReader - Abstract token reader +class A_SRDReader +{ + protected: + A_SRDReaderTarget& target_; + + public: + A_SRDReader( A_SRDReaderTarget& target ); + + virtual ~A_SRDReader( ) = 0; + virtual void read( T_String const& name , A_InputStream& input ) = 0; +}; +M_ABSTRACT_POINTERS( SRDReader ); + +/*----------------------------------------------------------------------------*/ + +// X_SRDWriterError - SRD writer exception +class X_SRDWriterError : public std::exception +{ + private: + char const* msg_; + + public: + X_SRDWriterError( char const* msg ) noexcept; + + X_SRDWriterError( ) = delete; + X_SRDWriterError( X_SRDWriterError const& ) = default; + X_SRDWriterError( X_SRDWriterError&& ) = default; + + char const * what( ) const noexcept override; +}; + + +/*= HELPERS ==================================================================*/ + +// T_SRDReaderTargetHelper - Calls start() and end() using RAII +class T_SRDReaderTargetHelper +{ + private: + A_SRDReaderTarget& target_; + T_SRDErrors& errors_; + + public: + T_SRDReaderTargetHelper( A_SRDReaderTarget& target , T_SRDErrors& errors ); + ~T_SRDReaderTargetHelper( ); +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDListFixer - A stage that adds missing EOL's and discards extraneous +// EOL's, emitting errors as it does so. +class T_SRDListFixer final : public A_SRDReaderTarget +{ + private: + A_SRDReaderTarget& output_; + T_AutoArray< T_SRDLocation , 32 > listStarts_; + + public: + explicit T_SRDListFixer( A_SRDReaderTarget& output ) noexcept; + + void start( T_SRDErrors& errors ) override; + void push( T_SRDErrors& errors , + T_SRDToken&& token ) override; + void end( T_SRDErrors& errors ) override; +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDMemoryTarget - Stores the reader's output as a T_SRDList +class T_SRDMemoryTarget : public A_SRDReaderTarget +{ + private: + const bool structured_; + bool clearFlushToken_; + T_SRDToken list_; + RP_SRDToken current_; + T_Array< RP_SRDToken > stack_; + + public: + // If structured is set, the generated token list will include sub-lists + // as E_SRDTokenType::LIST tokens; if it isn't, the list will feature + // only start and end of list tokens. + explicit T_SRDMemoryTarget( bool structured = true ); + + // Set/get flush token handling + void clearFlushToken( bool clearIt ) noexcept; + bool clearFlushToken( ) const noexcept; + + void start( T_SRDErrors& errors ) override; + void push( T_SRDErrors& errors , T_SRDToken&& token ) override; + void end( T_SRDErrors& errors ) override; + + // Get the list + T_SRDList const& list( ) const; + // Does the list only contains complete items? + bool complete( ) const; +}; +M_CLASS_POINTERS( SRDMemoryTarget ); + +/*----------------------------------------------------------------------------*/ + +// T_SRDWriterTarget - Forwards token that are read to a writer +class T_SRDWriterTarget : public A_SRDReaderTarget +{ + private: + A_SRDWriter& writer_; + + public: + explicit T_SRDWriterTarget( A_SRDWriter& writer ); + + void start( T_SRDErrors& errors ) override; + void push( T_SRDErrors& errors , T_SRDToken&& token ) override; + void end( T_SRDErrors& errors ) override; +}; +M_CLASS_POINTERS( SRDWriterTarget ); + + +} // namespace +#include +#endif // _H_LW_LIB_SRDIO diff --git a/include/ebcl/SRDPPCommands.hh b/include/ebcl/SRDPPCommands.hh new file mode 100644 index 0000000..c66beb8 --- /dev/null +++ b/include/ebcl/SRDPPCommands.hh @@ -0,0 +1,101 @@ +/******************************************************************************/ +/* SRD - PREPROCESSOR COMMANDS ************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDPPCOMMANDS +#define _H_LW_LIB_SRDPPCOMMANDS +#include +namespace lw { + + +M_SRDPP_COMMAND_DECL( Add ); M_SRDPP_COMMAND_INIT( Add , "add" ); +M_SRDPP_COMMAND_DECL( And ); M_SRDPP_COMMAND_INIT( And , "and" ); + +M_SRDPP_COMMAND_DECL( Bless ); M_SRDPP_COMMAND_INIT( Bless , "bless" ); +M_SRDPP_COMMAND_DECL( Break ); M_SRDPP_COMMAND_INIT( Break , "break" ); +M_SRDPP_COMMAND_DECL( BwAnd ); M_SRDPP_COMMAND_INIT( BwAnd , "bw-and" ); +M_SRDPP_COMMAND_DECL( BwNot ); M_SRDPP_COMMAND_INIT( BwNot , "bw-not" ); +M_SRDPP_COMMAND_DECL( BwOr ); M_SRDPP_COMMAND_INIT( BwOr , "bw-or" ); +M_SRDPP_COMMAND_DECL( BwXor ); M_SRDPP_COMMAND_INIT( BwXor , "bw-xor" ); + +M_SRDPP_COMMAND_DECL( Call ); M_SRDPP_COMMAND_INIT( Call , "call" ); +M_SRDPP_COMMAND_DECL( CastString ); M_SRDPP_COMMAND_INIT( CastString , "to-string" ); +M_SRDPP_COMMAND_DECL( CastWord ); M_SRDPP_COMMAND_INIT( CastWord , "to-word" ); +M_SRDPP_COMMAND_DECL( CastInt ); M_SRDPP_COMMAND_INIT( CastInt , "to-integer" ); +M_SRDPP_COMMAND_DECL( CastLong ); M_SRDPP_COMMAND_INIT( CastLong , "to-long" ); +M_SRDPP_COMMAND_DECL( CastBestInt ); M_SRDPP_COMMAND_INIT( CastBestInt , "to-best-integer" ); +M_SRDPP_COMMAND_DECL( CastReal ); M_SRDPP_COMMAND_INIT( CastReal , "to-real" ); +M_SRDPP_COMMAND_DECL( CastVar ); M_SRDPP_COMMAND_INIT( CastVar , "to-variable" ); +M_SRDPP_COMMAND_DECL( CastList ); M_SRDPP_COMMAND_INIT( CastList , "to-list" ); +M_SRDPP_COMMAND_DECL( ClearScope ); M_SRDPP_COMMAND_INIT_N( ClearScope , "clear-scope" , 0 ); +M_SRDPP_COMMAND_DECL( Cmp ); M_SRDPP_COMMAND_INIT( Cmp , "cmp" ); +M_SRDPP_COMMAND_DECL( CmpEq ); M_SRDPP_COMMAND_INIT( CmpEq , "eq" ); +M_SRDPP_COMMAND_DECL( CmpNe ); M_SRDPP_COMMAND_INIT( CmpNe , "ne" ); +M_SRDPP_COMMAND_DECL( CmpLt ); M_SRDPP_COMMAND_INIT( CmpLt , "lt" ); +M_SRDPP_COMMAND_DECL( CmpGt ); M_SRDPP_COMMAND_INIT( CmpGt , "gt" ); +M_SRDPP_COMMAND_DECL( CmpLe ); M_SRDPP_COMMAND_INIT( CmpLe , "le" ); +M_SRDPP_COMMAND_DECL( CmpGe ); M_SRDPP_COMMAND_INIT( CmpGe , "ge" ); +M_SRDPP_COMMAND_DECL( Concat ); M_SRDPP_COMMAND_INIT( Concat , "concat" ); + +M_SRDPP_COMMAND_DECL( Div ); M_SRDPP_COMMAND_INIT( Div , "div" ); + +M_SRDPP_COMMAND_DECL( EndsWith ); M_SRDPP_COMMAND_INIT( EndsWith , "ends-with" ); +M_SRDPP_COMMAND_DECL( Eval ); M_SRDPP_COMMAND_INIT( Eval , "eval" ); +M_SRDPP_COMMAND_DECL( Error ); M_SRDPP_COMMAND_INIT( Error , "error" ); + +M_SRDPP_COMMAND_DECL( FromSource ); M_SRDPP_COMMAND_INIT( FromSource , "from-source" ); +M_SRDPP_COMMAND_DECL( FromSRB ); M_SRDPP_COMMAND_INIT( FromSRB , "from-srb" ); + +M_SRDPP_COMMAND_DECL( Get ); M_SRDPP_COMMAND_INIT( Get , "get" ); + +M_SRDPP_COMMAND_DECL( If ); M_SRDPP_COMMAND_INIT_N( If , "if" , 1 ); +M_SRDPP_COMMAND_DECL( Ignore ); M_SRDPP_COMMAND_INIT_N( Ignore , "ignore" , 0 ); +M_SRDPP_COMMAND_DECL( IsBlessed ); M_SRDPP_COMMAND_INIT( IsBlessed , "is-blessed" ); +M_SRDPP_COMMAND_DECL( IsMacro ); M_SRDPP_COMMAND_INIT( IsMacro , "is-macro" ); +M_SRDPP_COMMAND_DECL( IsSet ); M_SRDPP_COMMAND_INIT( IsSet , "is-set" ); + +M_SRDPP_COMMAND_DECL( Length ); M_SRDPP_COMMAND_INIT( Length , "length" ); +M_SRDPP_COMMAND_DECL( ListMacros ); M_SRDPP_COMMAND_INIT_N( ListMacros , "ls-macros" , 0 ); +M_SRDPP_COMMAND_DECL( ListVariables ); M_SRDPP_COMMAND_INIT_N( ListVariables , "ls-variables" , 0 ); + +M_SRDPP_COMMAND_DECL( Mod ); M_SRDPP_COMMAND_INIT( Mod , "mod" ); +M_SRDPP_COMMAND_DECL( Mul ); M_SRDPP_COMMAND_INIT( Mul , "mul" ); + +M_SRDPP_COMMAND_DECL( Neg ); M_SRDPP_COMMAND_INIT( Neg , "neg" ); +M_SRDPP_COMMAND_DECL( Not ); M_SRDPP_COMMAND_INIT( Not , "not" ); + +M_SRDPP_COMMAND_DECL( Or ); M_SRDPP_COMMAND_INIT( Or , "or" ); +M_SRDPP_COMMAND_DECL( Output ); M_SRDPP_COMMAND_INIT( Output , "output" ); + +M_SRDPP_COMMAND_DECL( Raw ); M_SRDPP_COMMAND_INIT_N( Raw , "raw" , 0 ); +M_SRDPP_COMMAND_DECL( Rethrow ); M_SRDPP_COMMAND_INIT( Rethrow , "rethrow" ); + +M_SRDPP_COMMAND_DECL( Scope ); M_SRDPP_COMMAND_INIT_N( Scope , "scope" , 0 ); +M_SRDPP_COMMAND_DECL( Set ); M_SRDPP_COMMAND_INIT( Set , "set" ); +M_SRDPP_COMMAND_DECL( SetMacro ); M_SRDPP_COMMAND_INIT( SetMacro , "set-macro" ); +M_SRDPP_COMMAND_DECL( StartsWith ); M_SRDPP_COMMAND_INIT( StartsWith , "starts-with" ); +M_SRDPP_COMMAND_DECL( StrFind ); M_SRDPP_COMMAND_INIT( StrFind , "str-find" ); +M_SRDPP_COMMAND_DECL( StrSplit ); M_SRDPP_COMMAND_INIT( StrSplit , "str-split" ); +M_SRDPP_COMMAND_DECL( Sub ); M_SRDPP_COMMAND_INIT( Sub , "sub" ); +M_SRDPP_COMMAND_DECL( Substr ); M_SRDPP_COMMAND_INIT( Substr , "substr" ); + +M_SRDPP_COMMAND_DECL( ToSource ); M_SRDPP_COMMAND_INIT( ToSource , "to-source" ); +M_SRDPP_COMMAND_DECL( Try ); M_SRDPP_COMMAND_INIT_N( Try , "try" , 0 ); +M_SRDPP_COMMAND_DECL( TypeOf ); M_SRDPP_COMMAND_INIT( TypeOf , "type-of" ); + +M_SRDPP_COMMAND_DECL( Unset ); M_SRDPP_COMMAND_INIT( Unset , "unset" ); +M_SRDPP_COMMAND_DECL( UnsetMacro ); M_SRDPP_COMMAND_INIT( UnsetMacro , "unset-macro" ); +M_SRDPP_COMMAND_DECL( Unwrap ); M_SRDPP_COMMAND_INIT( Unwrap , "unwrap" ); + +M_SRDPP_COMMAND_DECL_OBJ( VFSList , T_VFS& , vfs_ ); +M_SRDPP_COMMAND_INIT_OBJ( VFSList , "vfs-list" , T_VFS& , vfs_ ); +M_SRDPP_COMMAND_DECL_OBJ( VFSLoad , T_VFS& , vfs_ ); +M_SRDPP_COMMAND_INIT_OBJ( VFSLoad , "vfs-load" , T_VFS& , vfs_ ); +M_SRDPP_COMMAND_DECL_OBJ( VFSType , T_VFS& , vfs_ ); +M_SRDPP_COMMAND_INIT_OBJ( VFSType , "vfs-type" , T_VFS& , vfs_ ); + +M_SRDPP_COMMAND_DECL( Xor ); M_SRDPP_COMMAND_INIT( Xor , "xor" ); + + +} // namespace lw +#endif // _H_LW_LIB_SRDPPCOMMANDS diff --git a/include/ebcl/SRDParser.hh b/include/ebcl/SRDParser.hh new file mode 100644 index 0000000..6a3d037 --- /dev/null +++ b/include/ebcl/SRDParser.hh @@ -0,0 +1,90 @@ +/******************************************************************************/ +/* SRD PARSER AND PREPROCESSOR ************************************************/ +/******************************************************************************/ + +#pragma once +#include +#include +namespace lw { + + +// T_SRDParserData - Run-time data passed to the handlers when parsing +struct T_SRDParserData +{ + // Parser configuration + T_SRDParserConfig const& config; + // Error output + T_SRDErrors & errors; + + // Input list (if any) for the current rule + RPC_SRDList input; + + // Name of the current context + T_String currentContext; + // Current context's data + RP_Variant currentData; + + // Target context when entering or exiting + T_String targetContext; + // Data for the target context; must be initialised at entrance + RP_Variant targetData; + + explicit T_SRDParserData( + T_SRDParserConfig const& parser , + T_SRDErrors & errors ); +}; +M_CLASS_POINTERS( SRDParserData ); + +/*----------------------------------------------------------------------------*/ + +// E_SRDFlushMode - How the parser flushes its command list +enum class E_SRDFlushMode { + MANUAL , // The parser never flushes the command list, it + // must be done manually by calling flush() + END , // Flush the command list after parsing has been + // completed. This is the default. + SENTENCE // Flush after each sentence (start() and end() + // will also cause flushes) +}; + + +// T_SRDParser - The parser's main class +class T_SRDParser : public A_SRDReaderTarget , public A_PrivateImplementation +{ +public: + T_SRDParser( ) = delete; + + T_SRDParser( T_SRDParserConfig const& cfg ); + + void start( T_SRDErrors & errors ) override; + void push( T_SRDErrors & errors , T_SRDToken && token ) override; + void end( T_SRDErrors & errors ) override; + + // Execute all currently queued commands + void flush( ); + + // Set/get the flush mode + void flushMode( E_SRDFlushMode mode ) noexcept; + E_SRDFlushMode flushMode( ) const noexcept; + + // Set/get flush token handling + void handleFlushToken( bool handleIt ) noexcept; + bool handleFlushToken( ) const noexcept; + + template< typename T > + T const& getData( ) const; + +private: + T_Variant const& getExecStackTop( ) const; + +}; +M_CLASS_POINTERS( SRDParser ); + +template< typename T > +inline T const& T_SRDParser::getData( ) const +{ + return getExecStackTop( ).value< T >( ); +} + + +} // namespace diff --git a/include/ebcl/SRDParserConfig.hh b/include/ebcl/SRDParserConfig.hh new file mode 100644 index 0000000..3e55026 --- /dev/null +++ b/include/ebcl/SRDParserConfig.hh @@ -0,0 +1,99 @@ +/******************************************************************************/ +/* SRD - PARSER CONFIGURATION *************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDPARSERCONFIG +#define _H_LW_LIB_SRDPARSERCONFIG +#include +#include +namespace lw { + + +/*= PROCESSED PARSER CONFIGURATION ===========================================*/ + +// E_SRDTransitionType - Type of a parser state transition +enum class E_SRDTransitionType { + WORD , + ENUM , + TOKEN +}; + +// T_SRDTransition - A parser transition (condition + next state) +struct T_SRDTransition +{ + // Match type + E_SRDTransitionType type; + + // Match value: word or enum index, or token type. + union { + uint32_t index; + E_SRDTokenType tokenType; + }; + + // Next state + uint32_t next; + + T_SRDTransition( E_SRDTokenType tokenType , uint32_t next ); + T_SRDTransition( E_SRDTransitionType type , uint32_t index , uint32_t next ); +}; + +/*----------------------------------------------------------------------------*/ + +// T_SRDParserConfig - Parser configuration +struct T_SRDParserConfig +{ + enum : uint32_t { INVALID_END = 0xffffffff }; + + F_SRDHandler onStart; + F_SRDHandler onFinish; + + T_Array< T_SRDContext > contexts; + T_ObjectTable< T_String , T_SRDEnum > enums; + + // Index of words used in the rules + T_HashIndex wordsIndex; + T_Array< T_String > words; + + // Start indices for the contexts' automatons + T_Array< uint32_t > startStates; + + // End states. Either 0xffffffff for states that are not end states, + // or the rule ID for valid end states. + T_Array< uint32_t > endStates; + + // State transitions + T_MultiArray< T_SRDTransition > transitions; + + // Rule contexts + T_MultiArray< uint32_t > ruleContexts; + + T_SRDParserConfig( ) = delete; + T_SRDParserConfig( T_SRDParserDefs const& defs ); + + // Try to find a transition matching the token + bool transition( uint32_t& state , T_SRDToken const& token ) const; + + // Find the value of an enumeration member + T_Optional< uint32_t > enumValue( char const* name , T_String const& member ) const noexcept; + T_Optional< uint32_t > enumValue( T_String const& name , T_String const& member ) const noexcept; +}; +M_CLASS_POINTERS( SRDParserConfig ); + +/*----------------------------------------------------------------------------*/ + +// X_SRDParserConfig - Exception indicating that definitions were incorrect +class X_SRDParserConfig : public std::exception +{ + private: + T_Buffer< char > errorString_; + + public: + explicit X_SRDParserConfig( T_StringBuilder const& error ); + explicit X_SRDParserConfig( T_String const& error ); + char const * what( ) const noexcept override; +}; + + +} // namespace +#include +#endif // _H_LW_LIB_SRDPARSERCONFIG diff --git a/include/ebcl/SRDText.hh b/include/ebcl/SRDText.hh new file mode 100644 index 0000000..ac22677 --- /dev/null +++ b/include/ebcl/SRDText.hh @@ -0,0 +1,93 @@ +/******************************************************************************/ +/* SRD - TEXT STORAGE *********************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_SRDTEXT +#define _H_LW_LIB_SRDTEXT +#include +namespace lw { + + +/*= READER ===================================================================*/ + +// Streaming lexer for SRD text +class T_SRDLexer : public A_PrivateImplementation +{ + public: + T_SRDLexer( ) = delete; + T_SRDLexer( T_SRDLexer const& ) = delete; + T_SRDLexer( T_SRDLexer&& ) = delete; + + T_SRDLexer( T_String const& name , T_SRDErrors& errors , A_SRDReaderTarget& target ); + + void processCharacter( T_Character c ); + void processEnd( ); +}; +M_CLASS_POINTERS( SRDLexer ); + +/*----------------------------------------------------------------------------*/ + +// Text reader - wraps the lexer inside a SRD reader +class T_SRDTextReader : public A_SRDReader +{ + public: + T_SRDTextReader( ) = delete; + T_SRDTextReader( T_SRDTextReader const& ) = delete; + T_SRDTextReader( T_SRDTextReader&& ) = delete; + + T_SRDTextReader( A_SRDReaderTarget& target ); + void read( T_String const& name , A_InputStream& input ) override; +}; +M_CLASS_POINTERS( SRDTextReader ); + +/*----------------------------------------------------------------------------*/ + +T_SRDList SRDFromText( T_String const& name , T_String const& string , bool structured = true ); +T_SRDList SRDFromText( T_String const& name , char const* string , bool structured = true ); + + +/*= WRITER ===================================================================*/ + +// T_SRDTextWriter - Writer for the text form of SRD +class T_SRDTextWriter : public A_SRDWriter , public A_PrivateImplementation +{ + public: + T_SRDTextWriter( ) = delete; + T_SRDTextWriter( T_SRDTextWriter const& ) = delete; + T_SRDTextWriter( T_SRDTextWriter&& ) = delete; + + explicit T_SRDTextWriter( A_OutputStream& output ); + + T_SRDTextWriter& start( ) override; + + T_SRDTextWriter& startList( ) override; + T_SRDTextWriter& endList( ) override; + + T_SRDTextWriter& putText( T_String const& text ) override; + T_SRDTextWriter& putWord( T_String const& word ) override; + T_SRDTextWriter& putString( T_String const& string ) override; + + T_SRDTextWriter& putComment( T_String const& comment ) override; + + T_SRDTextWriter& putVariable( T_String const& variable ) override; + + T_SRDTextWriter& putBinary( T_Buffer< uint8_t > const& binary ) override; + + T_SRDTextWriter& putInteger( int64_t value ) override; + T_SRDTextWriter& putInt32( int32_t value ) override; + T_SRDTextWriter& putInt64( int64_t value ) override; + + T_SRDTextWriter& putFloat( double value ) override; + + T_SRDTextWriter& end( ) override; +}; +M_CLASS_POINTERS( SRDTextWriter ); + +/*----------------------------------------------------------------------------*/ + +void SRDWriteAsText( A_OutputStream& output , T_SRDList const& data ); + + +} // namespace +#include +#endif // _H_LW_LIB_SRDTEXT diff --git a/include/ebcl/Streams.hh b/include/ebcl/Streams.hh new file mode 100644 index 0000000..16bc543 --- /dev/null +++ b/include/ebcl/Streams.hh @@ -0,0 +1,110 @@ +/******************************************************************************/ +/* STREAMS ********************************************************************/ +/******************************************************************************/ + +#pragma once +#include +#include +#include +#include +namespace lw { + + +/*= STREAM ERRORS ============================================================*/ + +enum class E_StreamError { + NOT_SUPPORTED , // Operation not supported + INVALID_POSITION , + END , // End of stream + BAD_DATA , // Invalid data read from the stream + SYSTEM_ERROR , + UNAVAILABLE , // Stream is no longer available +}; + + +class X_StreamError : public std::exception +{ + private: + E_StreamError error_; + int sysError_; + + public: + explicit X_StreamError( E_StreamError e ); + explicit X_StreamError( int error ); + + X_StreamError( ) = delete; + X_StreamError( X_StreamError const& ) = default; + X_StreamError( X_StreamError&& ) noexcept = default; + virtual X_StreamError& operator= ( X_StreamError const& ) = default; + virtual X_StreamError& operator= ( X_StreamError&& ) = default; + + E_StreamError code( ) const; + int systemError( ) const; + + char const * what( ) const noexcept; +}; + + +/*= BASE ABSTRACT CLASSES FOR STREAMS ========================================*/ + +class A_Stream +{ + private: + const bool isInput_; + const bool knownSize_; + + protected: + size_t size_; + size_t position_; + + A_Stream( bool isInput , size_t position ) noexcept; + A_Stream( bool isInput , size_t position , size_t size ) noexcept; + + public: + A_Stream( ) = delete; + A_Stream( A_Stream const& ) = delete; + A_Stream( A_Stream&& ) = delete; + virtual ~A_Stream( ) = 0; + + bool isInput( ) const; + bool isSizeKnown( ) const; + size_t position( ) const; + size_t size( ) const; +}; + + +M_ABSTRACT_POINTERS( Stream ); + +/*----------------------------------------------------------------------------*/ + +class A_InputStream : public A_Stream +{ + protected: + explicit A_InputStream( size_t position ) noexcept; + A_InputStream( size_t position , size_t size ) noexcept; + + public: + virtual size_t read( void* data , size_t size ) = 0; +}; + + +M_ABSTRACT_POINTERS( InputStream ); + +/*----------------------------------------------------------------------------*/ + +class A_OutputStream : public A_Stream +{ + protected: + A_OutputStream( size_t position ) noexcept; + A_OutputStream( size_t position , size_t size ) noexcept; + + public: + virtual size_t write( void const* data , size_t size ) = 0; + virtual void flush( ); +}; + + +M_ABSTRACT_POINTERS( OutputStream ); + +} // namespace +#include diff --git a/include/ebcl/Strings.hh b/include/ebcl/Strings.hh new file mode 100644 index 0000000..b6f8ddd --- /dev/null +++ b/include/ebcl/Strings.hh @@ -0,0 +1,457 @@ +/******************************************************************************/ +/* STRINGS AND RELATED UTILITIES **********************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_STRINGS +#define _H_LW_LIB_STRINGS +#include +#include +#include +#include +namespace lw { + + +/*= UTF-8 UTILITY FUNCTIONS ==================================================*/ + +// Is the specified C string valid UTF-8 ? +bool UTF8IsValid( char const* string ); + +// Get the length (in characters) of the specified UTF-8 0-terminated string +uint32_t UTF8Length( char const* string ); + +// Get the size (in bytes) of the specified UTF-8 0-terminated string +uint32_t UTF8Size( char const* string ); + +// Combined function that does all the above +bool UTF8Info( char const* string , uint32_t& size , uint32_t& length ); + +// Check if the specified data is a valid UTF-8 string, and compute its length +// (in characters). +bool UTF8BufferInfo( char const* data , uint32_t size , uint32_t& length ); + +// Get the codepoint from a sequence of UTF-8 bytes. Sets "bytes" to the amount +// of bytes read from the input. +uint32_t UTF8GetCodepoint( char const* data , uint32_t& bytes ); + +// Similar to the above, without the amount of bytes output. +uint32_t UTF8GetCodepoint( char const* data ); + +// Write an UTF-8 encoded codepoint to a string, returns the amount of bytes +// that were written, or 0 if there wasn't enough space. +uint32_t UTF8PutCodepoint( char* output , uint32_t available , uint32_t codepoint ); + +// Get the memory offset of a codepoint in an UTF-8 sequence based on its index. +uint32_t UTF8GetMemoryOffset( char const* input , uint32_t index ); + +// Convert an UTF-8 sequence into an unsigned integer. +uint64_t UTF8ToUnsignedInteger( char const* input , uint32_t size , + bool * ok = nullptr , int base = 10 , bool useSep = false , + uint32_t separator = ' ' ); + +// Convert an UTF-8 sequence into a signed integer. +int64_t UTF8ToInteger( char const* input , uint32_t size , + bool * ok = nullptr , int base = 10 , bool useSep = false , + uint32_t separator = ' ' ); + +// Convert an UTF-8 sequence into a double precision floating point number. The +// sequence will be checked, converted into a C string and passed to strtod() +// for actual conversion. +double UTF8ToDouble( char const* input , uint32_t size , + bool * ok = nullptr , uint32_t decimalPoint = '.' , + bool useSep = false , uint32_t separator = ' ' ); + + +/*= UNICODE CHARACTERS =======================================================*/ + +struct T_Character +{ + const uint32_t codepoint; + + T_Character( ) noexcept; + T_Character( T_Character const& other ) noexcept; + M_WITH_INT( T ) T_Character( T codepoint ) noexcept; + + // --------------------------------------------------------------------- + + bool isValid( ) const; + bool isAscii( ) const; + bool isControl( ) const; + bool isUppercase( ) const; + bool isLowercase( ) const; + bool isAlpha( ) const; + bool isNumeric( ) const; + bool isAlphanumeric( ) const; + bool isWhitespace( ) const; + + // --------------------------------------------------------------------- + + bool operator== ( T_Character const& other ) const; + bool operator!= ( T_Character const& other ) const; + bool operator< ( T_Character const& other ) const; + bool operator> ( T_Character const& other ) const; + bool operator<= ( T_Character const& other ) const; + bool operator>= ( T_Character const& other ) const; + + M_WITH_INT( T ) bool operator== ( T other ) const; + M_WITH_INT( T ) bool operator!= ( T other ) const; + M_WITH_INT( T ) bool operator< ( T other ) const; + M_WITH_INT( T ) bool operator<= ( T other ) const; + M_WITH_INT( T ) bool operator> ( T other ) const; + M_WITH_INT( T ) bool operator>= ( T other ) const; + + operator uint32_t ( ) const; + + // --------------------------------------------------------------------- + + uint32_t writeTo( char* output , uint32_t avail ) const; + + // --------------------------------------------------------------------- + + T_Character toUpper( ) const; + T_Character toLower( ) const; +}; + + +M_CLASS_POINTERS( Character ); +M_DECLARE_OBJECT_READER( T_Character ); +M_DECLARE_OBJECT_WRITER( T_Character ); + + +/*= IMMUTABLE UTF8 STRINGS ===================================================*/ + +/* + * NOTE: the *objects* are NOT immutable. The strings they contain, however, + * are. + */ + +class T_StringBuilder; +class T_StringIterator; + +// T_StringData - Abstract base for the various types of string storage. +class A_StringData +{ + protected: + char* data_; + uint32_t size_; + bool valid_; + uint32_t length_; + + A_StringData( ) = default; + A_StringData( A_StringData const& ) = delete; + A_StringData( A_StringData&& ) = delete; + + public: + virtual ~A_StringData( ) = 0; + + // Is valid UTF-8? + bool valid( ) const; + // Get a pointer to the data + char const * data( ) const; + // Length in characters + uint32_t length( ) const; + // Size in bytes + uint32_t size( ) const; +}; +M_ABSTRACT_POINTERS( StringData ); + +/*----------------------------------------------------------------------------*/ + +// T_String - Main UTF-8 string class +class T_String +{ + private: + RP_StringData data_; + + public: + // Construct an empty string. No overhead whatsoever, just an assignment + T_String( ) noexcept; + + // This constructor will try to: + // - use the empty string if initial is null or "" + // - use a pooled string if one matches initial + // - create a dynamic string otherwise. + // Because of this, it is relatively slow and should be avoided in + // general. + explicit T_String( char const* initial ); + + T_String( T_StringBuilder&& sb ); + T_String( T_StringBuilder const& sb ); + + // Construct a dynamic string, either using the provided memory + // or duplicating it. Doesn't use the pools at all. + T_String( char const* data , uint32_t size , bool nodup = false ); + + T_String( T_String const& source ); + T_String( T_String&& source ) noexcept; + + ~T_String( ); + + // --------------------------------------------------------------------- + + // Get a pooled string. Faster than constructing then calling + // addToPool( ) if the string is already pooled. If it isn't, it will + // be added as a dynamic string. + static T_String Pooled( char const* string ); + static T_String Pooled( char const* data , uint32_t size ); + + // --------------------------------------------------------------------- + + T_String& operator= ( T_String&& string ) noexcept; + T_String& operator= ( T_String const& string ); + + T_String& operator= ( T_StringBuilder&& sb ); + T_String& operator= ( T_StringBuilder const& sb ); + + friend void swap( T_String& lhs , T_String& rhs ) noexcept; + + // --------------------------------------------------------------------- + + // Adds the string to the pool if it isn't pooled already. If the pool + // already contains a pooled version of this string, use it instead. + T_String& addToPool( ); + + // Attempts to use the pooled version of a string if it exists. If it + // doesn't, keep using the current version. + T_String& usePool( ); + + // --------------------------------------------------------------------- + + bool valid( ) const; + uint32_t size( ) const; + uint32_t length( ) const; + char const * data( ) const; + operator bool ( ) const; + bool operator! ( ) const; + + // --------------------------------------------------------------------- + + // Return the character at the specified index + T_Character operator[] ( uint32_t index ) const; + + // Return a substring from the left side of the string + T_String left( uint32_t count ) const; + // Return a substring from the right side of the string + T_String right( uint32_t count ) const; + // Return a substring from the specified offset + T_String substr( uint32_t offset , uint32_t count = UINT32_MAX ) const; + // Return the substring between the two specified offsets + T_String range( uint32_t start , uint32_t end ) const; + + // Remove whitespace from the start and end of the string + T_String trim( ) const noexcept; + + // --------------------------------------------------------------------- + + bool equals( T_String const& other ) const; + bool equals( char const* string ) const; + + int32_t compare( T_String const& other ) const; + int32_t compareIgnoreCase( T_String const& other ) const; + + bool startsWith( T_String const& other ) const; + bool endsWith( T_String const& other ) const; + + // Finds a sub-string. Returns -1 if it isn't found. + int32_t find( T_String const& other , uint32_t from = 0 ) const; + // Finds a character. Returns -1 if it isn't found. + int32_t find( T_Character character , uint32_t from = 0 ) const; + + bool operator== ( T_String const& other ) const; + bool operator!= ( T_String const& other ) const; + bool operator< ( T_String const& other ) const; + bool operator> ( T_String const& other ) const; + bool operator>= ( T_String const& other ) const; + bool operator<= ( T_String const& other ) const; + + bool operator== ( char const* string ) const; + bool operator!= ( char const* string ) const; + + // --------------------------------------------------------------------- + + T_String replace( T_Character initial , T_Character replacement ) const; + T_String replace( T_String const& initial , T_String const& replacement ) const; + + // --------------------------------------------------------------------- + + uint64_t toUnsignedInteger( bool * ok = nullptr , int base = 10 , + bool useSep = false , T_Character separator = ' ' ) const; + int64_t toInteger( bool * ok = nullptr , int base = 10 , bool useSep = false , + T_Character separator = ' ' ) const; + double toDouble( bool * ok = nullptr , T_Character decimalPoint = '.' , + bool useSep = false , T_Character separator = ' ' ) const; + + // --------------------------------------------------------------------- + + T_StringIterator getIterator( uint32_t offset ) const; + operator T_StringIterator( ) const; + + // Converts the string to an array of bytes suitable for use with the + // operating system's functions (e.g. UTF-8 C string on Linux, or + // UTF-16 strings on Windows) + T_Buffer< char > toOSString( ) const; +}; +M_CLASS_POINTERS( String ); +M_DECLARE_HASH( T_String ); +M_DECLARE_COMPARATOR( T_String ); +M_DECLARE_OBJECT_READER( T_String ); +M_DECLARE_OBJECT_WRITER( T_String ); + +void swap( T_String& lhs , T_String& rhs ) noexcept; + +extern template class T_Array< T_String >; +extern template class T_MultiArray< T_String >; + + +/*= STRING ITERATORS =========================================================*/ + +class T_StringIterator final +{ + friend class T_String; + + private: + RP_StringData data_; + uint32_t pos_; + uint32_t index_; + uint32_t codepoint_; + uint32_t bytes_; + + T_StringIterator( RP_StringData data , uint32_t index ); + + public: + T_StringIterator( ) = delete; + T_StringIterator( T_StringIterator const& other ); + T_StringIterator( T_StringIterator&& other ) noexcept; + + ~T_StringIterator( ); + + T_StringIterator& operator= ( T_StringIterator const& other ); + T_StringIterator& operator= ( T_StringIterator&& other ) noexcept; + + friend void swap( T_StringIterator& lhs , T_StringIterator& rhs ) noexcept; + + // --------------------------------------------------------------------- + + bool next( ); + + uint32_t index( ) const; + bool atEnd( ) const; + + T_Character character( ) const; + operator T_Character ( ) const; +}; + + +M_CLASS_POINTERS( StringIterator ); +void swap( T_StringIterator& lhs , T_StringIterator& rhs ) noexcept; + + +/*= STRING BUILDERS ==========================================================*/ + +class T_StringBuilder +{ + public: + enum : uint32_t { C_GROWTH = 32 }; + + private: + char* data_; + uint32_t capacity_; + uint32_t size_; + uint32_t length_; + + friend class T_String; + + public: + T_StringBuilder( ) noexcept; + + T_StringBuilder( T_StringBuilder const& other ); + T_StringBuilder( T_StringBuilder&& other ) noexcept; + + T_StringBuilder( char const* data , uint32_t size ); + explicit T_StringBuilder( T_String const& string ); + explicit T_StringBuilder( char const* string ); + + ~T_StringBuilder( ); + + T_StringBuilder& operator =( T_StringBuilder const& other ); + T_StringBuilder& operator =( T_StringBuilder&& other ) noexcept; + + friend void swap( T_StringBuilder& lhs , T_StringBuilder& rhs ); + + // --------------------------------------------------------------------- + + char const * data( ) const; + uint32_t capacity( ) const; + uint32_t size( ) const; + uint32_t length( ) const; + operator bool ( ) const; + bool operator! ( ) const; + + // --------------------------------------------------------------------- + + T_StringBuilder& ensureCapacity( uint32_t minCap ); + T_StringBuilder& clear( ); + T_StringBuilder& free( ); + + // --------------------------------------------------------------------- + + T_StringBuilder& append( T_StringBuilder const& other ); + T_StringBuilder& append( T_StringBuilder&& other ); + T_StringBuilder& append( T_String const& string ); + T_StringBuilder& append( char const* string , uint32_t size ); + T_StringBuilder& append( char character ); + T_StringBuilder& append( T_Character character ); + + T_StringBuilder& appendNumeric( int64_t value , int base = 10 , bool useSep = false , + T_Character sep = ' ' , int sepEvery = 3 ); + + T_StringBuilder& appendNumeric( uint64_t value , int base = 10 , bool useSep = false , + T_Character sep = ' ' , int sepEvery = 3 ); + + T_StringBuilder& appendDouble( double value , uint32_t precision = 6 , bool trailingZeros = false ); + + // --------------------------------------------------------------------- + + bool operator== ( T_StringBuilder const& other ) const; + bool operator!= ( T_StringBuilder const& other ) const; + + bool operator== ( T_String const& string ) const; + bool operator!= ( T_String const& string ) const; + + bool operator== ( char const* string ) const; + bool operator!= ( char const* string ) const; + + // --------------------------------------------------------------------- + + uint64_t toUnsignedInteger( bool * ok = nullptr , int base = 10 , + bool useSep = false , T_Character separator = ' ' ) const; + int64_t toInteger( bool * ok = nullptr , int base = 10 , bool useSep = false , + T_Character separator = ' ' ) const; + double toDouble( bool * ok = nullptr , T_Character decimalPoint = '.' , + bool useSep = false , T_Character separator = ' ' ) const; +}; + + +M_CLASS_POINTERS( StringBuilder ); +M_DECLARE_OBJECT_WRITER( T_StringBuilder ); +void swap( T_StringBuilder& lhs , T_StringBuilder& rhs ); + +// Operator << +M_LSHIFT_OP( T_StringBuilder , T_StringBuilder const& ); +M_LSHIFT_OP( T_StringBuilder , T_StringBuilder && ); +M_LSHIFT_OP( T_StringBuilder , T_String const& ); +M_LSHIFT_OP( T_StringBuilder , char const* ); +M_LSHIFT_OP( T_StringBuilder , char ); +M_LSHIFT_OP( T_StringBuilder , T_Character ); +M_LSHIFT_OP( T_StringBuilder , int16_t ); +M_LSHIFT_OP( T_StringBuilder , int32_t ); +M_LSHIFT_OP( T_StringBuilder , int64_t ); +M_LSHIFT_OP( T_StringBuilder , uint16_t ); +M_LSHIFT_OP( T_StringBuilder , uint32_t ); +M_LSHIFT_OP( T_StringBuilder , uint64_t ); +M_LSHIFT_OP( T_StringBuilder , float ); +M_LSHIFT_OP( T_StringBuilder , double ); + + +} // namespace +#endif // _H_LW_LIB_STRINGS +#include diff --git a/include/ebcl/Threading.hh b/include/ebcl/Threading.hh new file mode 100644 index 0000000..de93e8b --- /dev/null +++ b/include/ebcl/Threading.hh @@ -0,0 +1,150 @@ +/******************************************************************************/ +/* THREADING ******************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_THREADING +#define _H_LW_LIB_THREADING + +#include +#include +namespace lw { + + +/*= ALIASES ==================================================================*/ + +using T_Mutex = std::mutex; +using T_ScopeLock = std::lock_guard< std::mutex >; +using T_ExclusiveLock = std::unique_lock< std::mutex >; +using T_Condition = std::condition_variable; +using T_Thread = std::thread; + +M_CLASS_POINTERS( Mutex ); +M_CLASS_POINTERS( ExclusiveLock ); +M_CLASS_POINTERS( Condition ); +M_CLASS_POINTERS( Thread ); + + +/*= READ/WRITE LOCKING =======================================================*/ + +// A mutex that supports multiple readers and one writer. Based on the +// implementation from G++ 6's standard library. +class T_ReadWriteMutex +{ + private: + static constexpr uint32_t C_WLOCKED_ = 1 << 31; + static constexpr uint32_t C_READERS_ = ~C_WLOCKED_; + + T_Mutex mutex_; + T_Condition readerBlock_; + T_Condition writerBlock_; + uint32_t state_; + + public: + T_ReadWriteMutex( ); + T_ReadWriteMutex( T_ReadWriteMutex const& ) = delete; + T_ReadWriteMutex( T_ReadWriteMutex&& ) = default; + ~T_ReadWriteMutex( ); + + T_ReadWriteMutex& operator=( T_ReadWriteMutex const& ) = delete; + T_ReadWriteMutex& operator=( T_ReadWriteMutex&& ) = default; + + // --------------------------------------------------------------------- + + // Write lock + void lock( ); + bool try_lock( ); + void unlock( ); + + // --------------------------------------------------------------------- + + // Read lock + void lock_shared( ); + bool try_lock_shared( ); + void unlock_shared( ); + + // --------------------------------------------------------------------- + + void upgradeLock( ); +}; +M_CLASS_POINTERS( ReadWriteMutex ); + + +// Write lock / scoped write lock aliases +using T_WriteLock = std::unique_lock< T_ReadWriteMutex >; +using T_ScopeWriteLock = std::lock_guard< T_ReadWriteMutex >; +M_CLASS_POINTERS( WriteLock ); + + +// Read lock based on std::shared_lock, but that can be +// upgraded to a write lock without releasing +class T_ReadLock : public std::shared_lock< T_ReadWriteMutex > +{ +public: + T_ReadLock( ) noexcept; + T_ReadLock( T_ReadLock&& other ); + explicit T_ReadLock( T_ReadWriteMutex& m ); + T_ReadLock( T_ReadWriteMutex& m , std::defer_lock_t t ); + T_ReadLock( T_ReadWriteMutex& m , std::try_to_lock_t t ); + T_ReadLock( T_ReadWriteMutex& m , std::adopt_lock_t t ); + + T_WriteLock upgrade( ); +}; +M_CLASS_POINTERS( ReadLock ); + + +/*= RING BUFFER ==============================================================*/ + +template< typename ElementType > +class T_RingBuffer +{ + private: + typedef T_RingBuffer< ElementType > T_Self; + + uint32_t expand_; + ElementType * data_; + uint32_t allocated_; + uint32_t used_; + uint32_t readPos_; + + public: + M_TEMPLATE_POINTERS( T_Self ); + + explicit T_RingBuffer( uint32_t expand = 64 ); + + T_RingBuffer( T_Self const& other ); + T_RingBuffer( T_Self&& other ) noexcept; + + ~T_RingBuffer( ); + + T_Self& operator=( T_Self const& other ); + T_Self& operator=( T_Self&& other ) noexcept; + + template< typename T > + friend void swap( T_RingBuffer< T >& lhs , T_RingBuffer< T >& rhs ) noexcept; + + void free( ); + + uint32_t size( ) const; + uint32_t capacity( ) const; + uint32_t growth( ) const; + + bool readNext( ElementType& output ); + bool readAll( T_Array< ElementType >& output ); + + void put( ElementType const& input ); + void put( ElementType&& input ); + + template< typename... ArgTypes > + void putNew( ArgTypes&&... arguments ); + +private: + void expand( ); +}; + +template< typename T > +void swap( T_RingBuffer< T >& lhs , T_RingBuffer< T >& rhs ) noexcept; + + +} +#endif // _H_LW_LIB_THREADING +#include diff --git a/include/ebcl/Types.hh b/include/ebcl/Types.hh new file mode 100644 index 0000000..c80c292 --- /dev/null +++ b/include/ebcl/Types.hh @@ -0,0 +1,498 @@ +/******************************************************************************/ +/* CONTAINER TYPES ************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_TYPES +#define _H_LW_LIB_TYPES +#include +#include +namespace lw { + + +/*= VARIANT TYPE =============================================================*/ + +// Forward declaration of the main variant type +template< size_t InPlaceSize = sizeof( void* ) > + class T_VariantExt; + +/*----------------------------------------------------------------------------*/ + +/* Helper class for the variant type. Contains the handlers for both in-place + * and heap-allocated storage. + */ +struct T_VariantHelper +{ + // Copy handler - Can be used to copy objects, will abort if the + // type doesn't support copy construction. + template< + typename Type , + bool CanCopy = std::is_copy_constructible< Type >::value + > struct T_CopyHandler; + + template< typename Type > struct T_CopyHandler< Type , true > + { static void copy( void* , Type const& ); }; + template< typename Type > struct T_CopyHandler< Type , false > + { static void copy( void* , Type const& ); }; + + // Move handler - Can be used to move objects, will abort if the + // type doesn't support move construction. + template< + typename Type , + bool CanMove = std::is_move_constructible< Type >::value + > struct T_MoveHandler; + + template< typename Type > struct T_MoveHandler< Type , true > + { static void move( void* , Type& ); }; + template< typename Type > struct T_MoveHandler< Type , false > + { static void move( void* , Type& ); }; + + // E_HandlerMode - Whether a handler uses in-place storage or + // heap allocation + enum class E_HandlerMode { + IN_PLACE , + HEAP_ALLOC + }; + + // T_HandlerInfo - Information about the handler, used when assigning + // between variants with different in-place sizes. + struct T_HandlerInfo + { + size_t size , align; + E_HandlerMode mode; + }; + + // E_HandlerOp - An operation that can be executed by the data handler + enum class E_HandlerOp { + ALLOC , // Allocate item storage + // Arg0: N/A + // Arg1: data storage + ACCESS , // Access the item + // Arg0: data storage + // Arg1: pointer to the item + DESTRUCT , // Call the item's destructor + // Arg0: N/A + // Arg1: data storage + TYPEID , // Return type information + // Arg0: N/A + // Arg1: type info + COPY , // Create a copy of the item + // Arg0: source pointer + // Arg1: destination pointer + MOVE , // Move the item + // Arg0: source pointer + // Arg1: destination pointer + HINFO , // Return information about the handler + // Arg0: N/A + // Arg1: handler info + MKHDL , // Initialise another handler for the same type + // Input: E_HandlerMode + // Output: handler pointer + }; + + // F_Handler - Handler function pointer + typedef void (*F_Handler)( + E_HandlerOp op , + void const* source , + void* argument ); + + // T_InPlaceHandler - Handler for items that can be stored in-place + template< typename Type > + struct T_InPlaceHandler + { + template< typename Raw > + static void init( void* storage , + F_Handler& handler , + Raw const& value ); + template< typename Raw > + static void init( void* storage , + F_Handler& handler , + Raw&& value ); + template< typename... Arguments > + static void init( Construct< Type > , + void* storage , + F_Handler& handler , + Arguments&&... arguments ); + + static void action( E_HandlerOp op , + void const* source , + void* target ); + }; + + // T_HeapHandler - Handler for items that require memory allocation + template< typename Type > + struct T_HeapHandler + { + template< typename Raw > + static void init( void* storage , + F_Handler& handler , + Raw const& value ); + template< typename Raw > + static void init( void* storage , + F_Handler& handler , + Raw&& value ); + template< typename... Arguments > + static void init( Construct< Type > , + void* storage , + F_Handler& handler , + Arguments&&... arguments ); + + static void action( E_HandlerOp op , + void const* source , + void* target ); + }; + + // T_BestHandlerFor - Handler for some item type, based on whether it + // requires heap allocation or not. + template< typename Type , size_t Size > + using T_BestHandlerFor = std::conditional_t< + std::is_move_constructible< Type >::value + && sizeof( Type ) <= Size + && alignof( Type ) <= alignof( void* ) , + T_InPlaceHandler< Type > , + T_HeapHandler< Type > >; + + // T_IsVariant - Templated horror to check whether something is a + // variant of arbitrary size. + template< typename T > + struct T_IsVariant + { + static constexpr bool value = false; + }; + template< size_t S > + struct T_IsVariant< T_VariantExt< S > > + { + static constexpr bool value = true; + }; + + // T_CanFit - Templated horror that compares two variant's storage sizes. + template< + size_t A , size_t B + > struct T_CanFit + { + using T_A = T_VariantExt< A >; + using T_B = T_VariantExt< B >; + static constexpr bool value = T_A::StorageSize <= T_B::StorageSize; + }; +}; + +/*----------------------------------------------------------------------------*/ + +template< size_t InPlaceSize > +class T_VariantExt +{ + template< size_t > + friend class T_VariantExt; + + public: + using T_Self = T_VariantExt< InPlaceSize >; + static constexpr size_t StorageSize + = std::max( sizeof( void* ) , InPlaceSize ); + + M_TEMPLATE_POINTERS( T_VariantExt ); + + private: + // T_RawOther_ - Raw type, provided it's not a T_VariantExt + template< + typename T , + typename Raw = std::decay_t< T > + > using T_RawOther_ = std::enable_if_t< + !T_VariantHelper::T_IsVariant< Raw >::value , Raw + >; + + // T_DataStorage_ - Contains the data or pointer to the data + union T_DataStorage_ + { + void* pointer; + std::aligned_storage_t< StorageSize , alignof( void* ) > inPlace; + + T_DataStorage_( ) = default; + T_DataStorage_( T_DataStorage_ const& ) = delete; + T_DataStorage_& operator =( T_DataStorage_ const& ) = delete; + }; + + // Handler function and storage area + T_VariantHelper::F_Handler handler_; + T_DataStorage_ storage_; + + public: + T_VariantExt( ) noexcept; + ~T_VariantExt( ); + + // --------------------------------------------------------------------- + + // Constructors from other T_VariantExt instances + T_VariantExt( T_Self const& other ); + T_VariantExt( T_Self&& other ); + template< size_t S > + T_VariantExt( T_VariantExt< S > const& other ); + template< size_t S > + T_VariantExt( T_VariantExt< S >&& other ); + + // Assignment from other T_VariantExt instances + T_Self& operator=( T_Self const& source ); + T_Self& operator=( T_Self&& source ); + template< size_t S > + T_Self& operator=( T_VariantExt< S > const& source ); + template< size_t S > + T_Self& operator=( T_VariantExt< S >&& source ); + + // --------------------------------------------------------------------- + + // Constructors from other types + template< + typename T , + typename Raw = T_RawOther_< T > , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > = true + > T_VariantExt( T&& value ); + template< + typename T , + typename Raw = T_RawOther_< T > , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > = false + > T_VariantExt( T&& value ); + + // Assignment from other types + template< + typename T , + typename Raw = T_RawOther_< T > , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > = true + > T_Self& operator= ( T&& value ); + template< + typename T , + typename Raw = T_RawOther_< T > , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > = false + > T_Self& operator= ( T&& value ); + + // In-place construction for another type + template< typename Type , typename... Arguments > + void setNew( Arguments&&... arguments ); + + // --------------------------------------------------------------------- + + template< size_t A , size_t B > + friend void swap( + T_VariantExt< A >& lhs , + T_VariantExt< B >& rhs ) noexcept; + + void clear( ) noexcept; + + // --------------------------------------------------------------------- + + std::type_info const& typeInfo( ) const; + template< typename T > + T const& value( ) const; + template< typename T > + T& value( ); + + operator bool( ) const noexcept; + bool operator !( ) const noexcept; + + // --------------------------------------------------------------------- + + private: + // Copy implementation + template< size_t S > + void copyFrom_( T_VariantExt< S > const& other ); + + // Different move implementations based on source size + template< + size_t S , + std::enable_if_t< T_VariantHelper::T_CanFit< S , InPlaceSize >::value , bool > = true + > void moveFrom_( T_VariantExt< S >&& source ); + template< + size_t S , + std::enable_if_t< !T_VariantHelper::T_CanFit< S , InPlaceSize >::value , bool > = false + > void moveFrom_( T_VariantExt< S >&& source ); +}; + +using T_Variant = T_VariantExt< >; +M_CLASS_POINTERS( Variant ); + + +/*= OPTIONAL TYPE ============================================================*/ + +template< typename Type > +class T_Optional +{ + private: + using T_Self_ = T_Optional< Type >; + + bool present_; + std::aligned_storage_t< sizeof( Type ) , alignof( Type ) > storage_; + + public: + M_TEMPLATE_POINTERS( T_Optional ); + + // Construct as empty + constexpr T_Optional( ) noexcept; + // Copy/move constructors + T_Optional( T_Self_ const& other ); + T_Optional( T_Self_&& other ) noexcept; + // Delete the value if necessary + ~T_Optional( ) noexcept; + + // Copy/move assignment + T_Self_& operator =( T_Self_ const& other ); + T_Self_& operator =( T_Self_ && other ) noexcept; + + template< typename T > + friend M_DECLARE_SWAP( T_Optional< T > ); + + // Initialise or assign from values + T_Optional( Type const& value ); + T_Optional( Type&& value ) noexcept; + T_Self_& operator =( Type const& value ); + T_Self_& operator =( Type&& value ) noexcept; + + // Initialise or set to new value + template< typename... Arguments > + T_Optional( Construct< Type > , Arguments&&... arguments ); + template< typename... Arguments > + void setNew( Arguments&&... arguments ); + // Clear current value + void clear( ) noexcept; + + // Is there a value? + constexpr bool present( ) const noexcept; + // Pointer to value or nullptr if no value + constexpr Type const* target( ) const noexcept; + constexpr Type* target( ) noexcept; + // Reference to value or std::bad_cast if no value + operator Type const&( ) const; + operator Type& ( ); +}; + +template< typename Type > +M_DECLARE_SWAP( T_Optional< Type > ); + + +/*= UNION TYPE ===============================================================*/ + +template< typename... Types > +class T_Union +{ + private: + static_assert( MetaAnd< CanCopyOrMove< Types >... >::value , + "types must be either copiable or movable" ); + static_assert( MetaLength< Types... >::value > 0 , + "empty unions are not allowed" ); + + using T_Self_ = T_Union< Types... >; + using T_Storage_ = std::aligned_union_t< 0 , Types... >; + + // Supported_< T > - Checks whether the type is in the union's type list + template< typename T > + using Supported_ = MetaContains< T , std::decay_t< Types >... >; + // IndexOf_< T > - Finds the type's index in the union's list + template< typename T > + using IndexOf_ = MetaIndexOf< T , std::decay_t< Types >... >; + // GetType_< I > - Finds the type at the specified index in the union's list + template< unsigned I > + using GetType_ = T_MetaGet< I , std::decay_t< Types >... >; + // RawType_< T > - Raw type of one of the union's supported types + template< typename T , typename R = std::decay_t< T > > + using RawType_ = std::enable_if_t< Supported_< R >::value , R >; + + uint32_t type_; + T_Storage_ storage_; + + public: + M_TEMPLATE_POINTERS( T_Union ); + + // Default constructor - only available if the first type in the union's + // type list is default constructible. + template< + typename T0 = GetType_< 0 > , + typename std::enable_if_t< std::is_default_constructible< T0 >::value , bool > = true + > T_Union( ); + + // Copy and move constructors - only available if all the types in the + // union are copy- or move-constructible, respectively. + T_Union( T_Self_ const& other ); + T_Union( T_Self_&& other ) noexcept; + + ~T_Union( ); + + // Swap - only available if all the types in the union are + // move-constructible + template< typename... TL > + friend M_DECLARE_SWAP( T_Union< TL... > ); + + // Copy and move assignment - only available if all the types in the + // union are copy- or move-constructible, respectively. + T_Self_& operator =( T_Self_ const& ); + T_Self_& operator =( T_Self_ && ) noexcept; + + template< typename T , typename... ArgTypes > + T_Union( Construct< T > , ArgTypes&&... arguments ); + + // Construct from an instance of one of the union's types. Move and copy + // are both supported. + template< + typename T , + typename Raw = RawType_< T > , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > = true + > T_Union( T&& ); + template< + typename T , + typename Raw = RawType_< T > , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > = false + > T_Union( T&& ); + + // Assign from an instance of one of the union's types. Move and copy + // are both supported. + template< + typename T , + typename Raw = RawType_< T > , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > = true + > T_Self_& operator =( T&& ); + template< + typename T , + typename Raw = RawType_< T > , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > = false + > T_Self_& operator =( T&& ); + + // Checks whether the instance contains an instance of the specified + // type + template< typename T > + constexpr bool hasType( ) const noexcept; + // Returns the type information for the instance's current contents + std::type_info const& typeInfo( ) const noexcept; + + // Obtains a pointer to the contents; may be nullptr if the union + // does not contain an instance of that type at the time + template< typename T > + constexpr T const* target( ) const noexcept; + template< typename T > + constexpr T* target( ) noexcept; + + // Obtains a reference to the contents. Will throw std::bad_cast + // if the contents' type doesn't match. + template< typename T > + T const& value( ) const; + template< typename T > + T& value( ); + + private: + // Destroying contents + void destroy( ); + template< typename T > + static void destroyImpl( T_Storage_* storage ); + + // Copying contents + void copy( T_Storage_* to ) const; + template< typename T > + static void copyImpl( T_Storage_ const* from , T_Storage_* to ); + + // Moving contents + void move( T_Storage_* to ); + template< typename T > + static void moveImpl( T_Storage_* from , T_Storage_* to ); +}; + +template< typename... Types > + M_DECLARE_SWAP( T_Union< Types... > ); + + +} // namespace +#endif // _H_LW_LIB_TYPES +#include diff --git a/include/ebcl/Utilities.hh b/include/ebcl/Utilities.hh new file mode 100644 index 0000000..0126e54 --- /dev/null +++ b/include/ebcl/Utilities.hh @@ -0,0 +1,393 @@ +/******************************************************************************/ +/* VARIOUS UTILITIES **********************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_UTILITIES +#define _H_LW_LIB_UTILITIES +#include +namespace lw { + + +// IsPowerOf2( v ) - Is some integer a power of 2 ? +template< typename T > +static constexpr bool IsPowerOf2( T value ); + + +/*= TEMPLATED HORRORS ========================================================*/ + +// MetaOr< ... > - Logical OR for templates +template< typename... > + struct MetaOr : std::false_type + { }; +template< typename T > + struct MetaOr< T > : T + { }; +template< typename T , typename... Rest > + struct MetaOr< T , Rest... > + : std::conditional_t< bool( T::value ) , T , MetaOr< Rest... > > + { }; + +/*----------------------------------------------------------------------------*/ + +// MetaAnd< ... > - Logical AND for templates +template< typename... > + struct MetaAnd : std::true_type + { }; +template< typename T > + struct MetaAnd< T > : T + { }; +template< typename T , typename... Rest > + struct MetaAnd< T , Rest... > + : std::conditional_t< bool( T::value ) , MetaAnd< Rest... > , T > + { }; + +/*----------------------------------------------------------------------------*/ + +// MetaNot< ... > - Logical NOT +template< typename T > + struct MetaNot : std::integral_constant< bool , !bool( T::value ) > + { }; + +/*----------------------------------------------------------------------------*/ + +// MetaGet< N , List... > - Get the Nth element in a list +template< unsigned , typename... > + struct MetaGet { }; +template< typename T , typename... Rest > + struct MetaGet< 0 , T , Rest... > + { using type = T; }; +template< unsigned N , typename First , typename... Rest > + struct MetaGet< N , First , Rest... > + : MetaGet< N - 1 , Rest... > + { }; +template< unsigned N , typename... List > + using T_MetaGet = typename MetaGet< N , List... >::type; + +/*----------------------------------------------------------------------------*/ + +// MetaLength< List... > - Get the length of a list +template< typename... > + struct MetaLength + : std::integral_constant< unsigned , 0 > + { }; +template< typename T > + struct MetaLength< T > + : std::integral_constant< unsigned , 1 > + { }; +template< typename First , typename... Rest > + struct MetaLength< First , Rest... > + : std::integral_constant< unsigned , MetaLength< Rest... >::value + 1 > + { }; + +/*----------------------------------------------------------------------------*/ + +// MetaContains< Type , List... > - Checks whether Type is in List +template< typename Type , typename... List > + using MetaContains = MetaOr< std::is_same< Type , List >... >; + +/*----------------------------------------------------------------------------*/ + +// MetaIndexOf< Type , List... > - Gets the index of Type in List. If Type is +// not in the list, the length of the list is returned. +template< typename Type , typename... List > + struct MetaIndexOf { }; +template< typename Type > + struct MetaIndexOf< Type > + : std::integral_constant< unsigned , 0 > + { }; +template< typename Type , typename First , typename... Rest > + struct MetaIndexOf< Type , First , Rest... > + : std::conditional_t< std::is_same< Type , First >::value , + std::integral_constant< unsigned , 0 > , + std::integral_constant< unsigned , + MetaIndexOf< Type , Rest... >::value + 1 > > + { }; + +/*----------------------------------------------------------------------------*/ + +// CanCopyOrMove< Type > - Check whether a type can be copy- or move-constructed. +template< typename T > + using CanCopyOrMove = MetaOr< + std::is_copy_constructible< T > , + std::is_move_constructible< T > >; + +/*----------------------------------------------------------------------------*/ + +// CanCopyConsAll< List... > - Checks that all listed types are copy-constructible +template< typename... List > + using CanCopyConsAll = MetaOr< std::is_copy_constructible< List >... >; + +// CanMoveConsAll< List... > - Checks that all listed types are move-constructible +template< typename... List > + using CanMoveConsAll = MetaOr< std::is_move_constructible< List >... >; + +// CanCopyAssAll< List... > - Checks that all listed types are copy-constructible +template< typename... List > + using CanCopyAssAll = MetaOr< std::is_copy_assignable< List >... >; + +// CanMoveAssAll< List... > - Checks that all listed types are move-constructible +template< typename... List > + using CanMoveAssAll = MetaOr< std::is_move_assignable< List >... >; + +/*----------------------------------------------------------------------------*/ + +// EnableForChild< P , C > - Enable if P is a parent class of C, or if P and C +// are the same. +template< + typename Parent , + typename Child +> +using EnableForChild = + std::enable_if< + std::is_base_of< Parent , Child >::value , + bool + >; + +template< typename C , typename P > +using T_EnableForChild = typename EnableForChild::type; + +// EnableForParent< C , P > - Enable if P is a parent class of C, and if P and +// C are distinct. +template< + typename Child , + typename Parent +> +using EnableForParent = + std::enable_if< + std::is_base_of< Parent , Child >::value + && !std::is_same< Parent , Child >::value , + bool + >; + +template< typename C , typename P > +using T_EnableForParent = typename EnableForParent::type; + +/*----------------------------------------------------------------------------*/ + +// Remove const and topmost reference +template< typename T > +using RemoveConstRef = std::remove_const< std::remove_reference_t< T > >; + +template< typename T > +using T_RemoveConstRef = typename RemoveConstRef< T >::type; + +/*----------------------------------------------------------------------------*/ + +// T_EnableIfTypesMatch - Make sure that the A type actually matches the V type. +template< + typename V , typename A , + typename RA = T_RemoveConstRef< A > +> using EnableIfTypesMatch = + std::enable_if< std::is_same< RA , V >::value , bool >; + +template< + typename V , typename A +> using T_EnableIfTypesMatch = typename EnableIfTypesMatch< V , A >::type; + +/*----------------------------------------------------------------------------*/ + +/* In-place construction tag */ +template< typename Type > +struct Construct +{ + explicit constexpr Construct( ) = default; + static constexpr Construct< Type > v = Construct< Type >( ); +}; + +using InPlace = Construct< void >; + +/*----------------------------------------------------------------------------*/ + +template< typename T > + struct T_CFunc { }; + +template< typename RT , typename... AT > + struct T_CFunc< RT( AT... ) > + { + typedef RT (* type )( AT... ); + }; + +template< typename T > + using F_CFunc = typename T_CFunc< T >::type; + + +/*= MACRO HORRORS ============================================================*/ + +// M_LSHIFT_OP( Type , Target ) - Declare a left-shift operator using the +// specified types +#define M_LSHIFT_OP( Type , Target ) \ + Type & operator<< ( Type& obj , Target value ) + +// M_WITH_INT( Name ) - Declare a template with a type that needs to be some +// sort of integer. +#define M_WITH_INT( Name ) \ + template< \ + typename Name , \ + typename = std::enable_if_t< std::is_integral< Name >::value > \ + > + +// M_DECLARE_SWAP( Type ) - Declare the swap function for a given type +#define M_DECLARE_SWAP( Type ) \ + void swap( Type& lhs , Type& rhs ) noexcept + +// M_DEFINE_SWAP( Type ) - Define the swap function for a given type +#define M_DEFINE_SWAP( Type ) \ + void lw::swap( Type& lhs , Type& rhs ) noexcept + + +/*= ENDIAN DETECTION =========================================================*/ +/* + * Unfortunately there's no standard way to do this so we depend on some + * GCC-specific stuff. + */ + +#ifndef __BYTE_ORDER__ +# error "We need the __BYTE_ORDER__ macro to exist :(" +#endif + +enum class E_Endian { + LITTLE = __ORDER_LITTLE_ENDIAN__ , + BIG = __ORDER_BIG_ENDIAN__ , + NATIVE = __BYTE_ORDER__ +}; + + +static constexpr bool IsBigEndian( ); +static constexpr bool IsLittleEndian( ); + + +/*= ENDIAN CONVERSION ========================================================*/ + +template< + typename T , + E_Endian E = E_Endian::NATIVE , + typename = std::enable_if_t< + std::is_integral< T >::value + || std::is_floating_point< T >::value + > + > +struct T_LittleEndian +{ + static T convert( T value ); +}; + + +template< + typename T , + E_Endian E = E_Endian::NATIVE , + typename = std::enable_if_t< + std::is_integral< T >::value + || std::is_floating_point< T >::value + > + > +struct T_BigEndian +{ + static T convert( T value ); +}; + + +/*= COMPARATORS AND SORTING ==================================================*/ + +// F_Comparator< T > - T_Comparator function type +template< typename T > +using F_Comparator = std::function< int( T const& a , T const& b ) >; + +// T_Comparator< T > - Default comparison function implementation +template< typename T > +struct T_Comparator +{ + static int compare( T const& a , T const& b ); +}; + + +// Helper macros to declare and define specialised hash functions. +#define M_DECLARE_COMPARATOR( Type ) \ + template< > \ + struct T_Comparator< Type > \ + { \ + static int compare( Type const& , Type const& ); \ + } + + +#define M_DEFINE_COMPARATOR( Type ) \ + int T_Comparator< Type >::compare( Type const & a , Type const & b ) + +// Instantiate some commonly used comparators +extern template struct T_Comparator< uint32_t >; + +// Sort - Sort an array +template< typename T > +void Sort( T* array , uint32_t items , F_Comparator< T > comparator = T_Comparator< T >::compare ); + + +/*= HASHING ==================================================================*/ + +// HashData( data , size ) - General hash function (Jenkin's one-at-a-time) +uint32_t HashData( uint8_t const* data , uint32_t size ); + +// Hash function template struct. Meant to be specialized. By default it uses +// the function above. +template< typename T , int S = sizeof( T ) > +struct T_HashFunction +{ + static uint32_t hash( T const& item ); +}; + + +// ComputeHash( T ) - The actual hash function. +template< typename T > +uint32_t ComputeHash( T const& item ); + +// Helper macros to declare and define specialised hash functions. +#define M_DECLARE_HASH( Type ) \ + template< > \ + struct T_HashFunction< Type > \ + { \ + static uint32_t hash( Type const& ); \ + } + + +#define M_DEFINE_HASH( Type ) \ + uint32_t T_HashFunction< Type >::hash( Type const & item ) + + +/*= PRIVATE IMPLEMENTATION BASE ==============================================*/ + +/* This class should be used as a base class for any non-performance-critical + * class that contains a lot of private stuff. The idea is to limit the amount + * of stuff exposed through the API, which in turns makes binary compatibility + * somewhat easier. + */ +class A_PrivateImplementation +{ + private: + using T_Destructor_ = std::function< void( void* ) >; + void* p_; + T_Destructor_ piDestructor_; + + protected: + A_PrivateImplementation( ) = delete; + A_PrivateImplementation( A_PrivateImplementation const& ) = delete; + A_PrivateImplementation& operator=( A_PrivateImplementation const& ) = delete; + + A_PrivateImplementation( void* p , T_Destructor_ destructor ); + template< typename T > + explicit A_PrivateImplementation( T* p ); + A_PrivateImplementation( A_PrivateImplementation&& ) noexcept; + A_PrivateImplementation& operator=( A_PrivateImplementation&& ) noexcept; + + public: + virtual ~A_PrivateImplementation( ); + + protected: + template< typename T > + T& p( ) const; + + private: + void callPrivateDestructor( ) noexcept; +}; + + +} // namespace +#endif // _H_LW_LIB_UTILITIES +#include diff --git a/include/ebcl/Version.hh b/include/ebcl/Version.hh new file mode 100644 index 0000000..9f19f80 --- /dev/null +++ b/include/ebcl/Version.hh @@ -0,0 +1,19 @@ +/******************************************************************************/ +/* VERSION NUMBERS ************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_VERSION +#define _H_LW_LIB_VERSION +#include +namespace lw { + + +static constexpr inline uint32_t LibVersion( ) + { return 0; } + +static constexpr inline uint32_t LibRevision( ) + { return 0; } + + +} +#endif // _H_LW_LIB_VERSION diff --git a/include/ebcl/Version.hh.in b/include/ebcl/Version.hh.in new file mode 100644 index 0000000..06b6645 --- /dev/null +++ b/include/ebcl/Version.hh.in @@ -0,0 +1,19 @@ +/******************************************************************************/ +/* VERSION NUMBERS ************************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_VERSION +#define _H_LW_LIB_VERSION +#include +namespace lw { + + +static constexpr inline uint32_t LibVersion( ) + { return __VERSION__; } + +static constexpr inline uint32_t LibRevision( ) + { return __REVISION__; } + + +} +#endif // _H_LW_LIB_VERSION diff --git a/include/ebcl/inline/Alloc.hh b/include/ebcl/inline/Alloc.hh new file mode 100644 index 0000000..43b7d64 --- /dev/null +++ b/include/ebcl/inline/Alloc.hh @@ -0,0 +1,376 @@ +/******************************************************************************/ +/* ALLOCATORS - INLINE CODE ***************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_ALLOC +#define _H_LW_LIB_INLINE_ALLOC +#include +namespace lw { + + +/*= T_PoolHelper::T_List =====================================================*/ + +template< typename T , size_t P > +inline T_PoolHelper::T_List< T , P >::T_List( + T_List< T , P >*& n ) noexcept + : next( n ) , free( P ) +{ + for ( size_t i = 0 ; i < P ; i ++ ) { + storage[ i ].list = this; + } + for ( size_t i = 0 ; i < BitmapSize ; i ++ ) { + bitmap[ i ] = 0; + } + n = this; +} + +template< typename T , size_t P > +inline T* T_PoolHelper::T_List< T , P >::findUnused( ) noexcept +{ + size_t i = 0; + while ( bitmap[ i ] == 255 ) { + assert( i < BitmapSize ); + i ++; + } + + size_t j = 0; + while ( ( bitmap[ i ] & ( 1 << j ) ) != 0 ) { + j ++; + } + assert( i * 8 + j < P ); + + bitmap[ i ] = bitmap[ i ] | ( 1 << j ); + free --; + return &storage[ i * 8 + j ]; +} + +template< typename T , size_t P > +inline size_t T_PoolHelper::T_List< T , P >::indexOf( + T const* const item ) noexcept +{ + assert( item >= &storage[ 0 ] ); + const size_t offset( ( (char*) item ) - ( (char*) &storage[ 0 ] ) ); + assert( offset % sizeof( T ) == 0 ); + const size_t index( offset / sizeof( T ) ); + assert( index < P ); + return index; +} + +template< typename T , size_t P > +inline void T_PoolHelper::T_List< T , P >::destroy( + T_List< T , P >*& head ) noexcept +{ + while ( head != nullptr ) { + T_Self* const n( head->next ); + delete head; + head = n; + } +} + + +/*= T_PoolHelper::T_Allocator ================================================*/ + +template< typename T , size_t P , size_t M > +inline T_PoolHelper::T_Allocator< T , P , M >::T_Allocator( ) noexcept + : free_( nullptr ) , partial_( nullptr ) , full_( nullptr ) , + nFree_( 1 ) +{ + new T_List_( free_ ); +} + +template< typename T , size_t P , size_t M > +inline T_PoolHelper::T_Allocator< T , P , M >::~T_Allocator( ) +{ + T_List_::destroy( free_ ); + T_List_::destroy( partial_ ); + T_List_::destroy( full_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , size_t P , size_t M > +inline T* T_PoolHelper::T_Allocator< T , P , M >::allocate( ) noexcept +{ + if ( partial_ == nullptr ) { + if ( free_ == nullptr ) { + new T_List_( partial_ ); + } else { + assert( nFree_ > 0 ); + partial_ = free_; + free_ = free_->next; + partial_->next = nullptr; + nFree_ --; + } + } + + T* const data( partial_->findUnused( ) ); + if ( partial_->free == 0 ) { + T_List_* const nf( partial_ ); + partial_ = nf->next; + nf->next = full_; + full_ = nf; + } + return data; +} + +template< typename T , size_t P , size_t M > +void T_PoolHelper::T_Allocator< T , P , M >::free( + T* const item ) noexcept +{ + T_List_* const list( reinterpret_cast< T_List_* >( item->list ) ); + const size_t index( list->indexOf( item ) ); + + list->bitmap[ index / 8 ] = list->bitmap[ index / 8 ] & ~( 1 << ( index & 7 ) ); + list->free ++; + + if ( list->free == 1 ) { + // Full list is now partial + moveList( list , full_ , partial_ ); + } else if ( list->free == P ) { + // Partial list is now free + if ( nFree_ == M ) { + T_List_* p( nullptr ); + moveList( list , partial_ , p ); + delete list; + } else { + moveList( list , partial_ , free_ ); + nFree_ ++; + } + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , size_t P , size_t M > +size_t T_PoolHelper::T_Allocator< T , P , M >::countFreeLists( ) const noexcept +{ + return nFree_; +} + +template< typename T , size_t P , size_t M > +size_t T_PoolHelper::T_Allocator< T , P , M >::countPartialLists( ) const noexcept +{ + return countLists( partial_ ); +} + +template< typename T , size_t P , size_t M > +size_t T_PoolHelper::T_Allocator< T , P , M >::countFullLists( ) const noexcept +{ + return countLists( full_ ); +} + +template< typename T , size_t P , size_t M > +void T_PoolHelper::T_Allocator< T , P , M >::getUsage( + size_t& total , + size_t& free , + size_t& used ) const noexcept +{ + free = countFreeLists( ) * P; + used = countFullLists( ) * P; + total = free + used; + + T_List_ const* p( partial_ ); + while ( p != nullptr ) { + free += p->free; + used += P - p->free; + total += P; + p = p->next; + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , size_t P , size_t M > +void T_PoolHelper::T_Allocator< T , P , M >::moveList( + T_List_* const list , + T_List_*& from , + T_List_*& to ) noexcept +{ + T_List_** ptr( &from ); + while ( *ptr != list ) { + ptr = &( (*ptr)->next ); + assert( *ptr != nullptr ); + } + *ptr = list->next; + list->next = to; + to = list; +} + +template< typename T , size_t P , size_t M > +size_t T_PoolHelper::T_Allocator< T , P , M >::countLists( + T_List_ const* head ) noexcept +{ + size_t nLists( 0 ); + while ( head != nullptr ) { + nLists ++; + head = head->next; + } + return nLists; +} + + +/*= T_PoolAllocator ==========================================================*/ + +template< size_t OS , size_t OA , size_t P , size_t M > +inline void* T_PoolAllocator< OS , OA , P , M >::allocate( + const size_t requested ) noexcept +{ +#ifdef LW_CFG_NO_ALLOCATORS + return ::operator new( requested ); +#else + assert( requested <= OS ); + return reinterpret_cast< char* >( alloc_.allocate( ) ); +#endif +} + +template< size_t OS , size_t OA , size_t P , size_t M > +void T_PoolAllocator< OS , OA , P , M >::free( + void* const item ) noexcept +{ +#ifdef LW_CFG_NO_ALLOCATORS + ::operator delete( item ); +#else + alloc_.free( reinterpret_cast< T_Storage_* >( item ) ); +#endif +} + +/*----------------------------------------------------------------------------*/ + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_PoolAllocator< OS , OA , P , M >::countFreeLists( ) const noexcept +{ + return alloc_.countFreeLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_PoolAllocator< OS , OA , P , M >::countPartialLists( ) const noexcept +{ + return alloc_.countPartialLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_PoolAllocator< OS , OA , P , M >::countFullLists( ) const noexcept +{ + return alloc_.countFullLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +void T_PoolAllocator< OS , OA , P , M >::getUsage( + size_t& total , + size_t& free , + size_t& used ) const noexcept +{ + return alloc_.getUsage( total , free , used ); +} + + +/*= T_ThreadedPoolAllocator ==================================================*/ + +template< size_t OS , size_t OA , size_t P , size_t M > +inline void* T_ThreadedPoolAllocator< OS , OA , P , M >::allocate( + const size_t requested ) noexcept +{ +#ifdef LW_CFG_NO_ALLOCATORS + return ::operator new( requested ); +#else + assert( requested <= OS ); + + T_Storage_* const alloc( ([this](){ + T_Storage_* const fs( takeFromStack( ) ); + return fs ? fs : alloc_.allocate( ); + })() ); + alloc->extra.pool = this; + return reinterpret_cast< char* >( alloc ); +#endif +} + +template< size_t OS , size_t OA , size_t P , size_t M > +void T_ThreadedPoolAllocator< OS , OA , P , M >::free( + void* const item ) noexcept +{ +#ifdef LW_CFG_NO_ALLOCATORS + ::operator delete( item ); +#else + T_Storage_* const storage( reinterpret_cast< T_Storage_* >( item ) ); + if ( storage->extra.pool == this ) { + alloc_.free( storage ); + } else { + storage->extra.pool->addToStack( storage ); + } + + T_Storage_* const fs( takeFromStack( ) ); + if ( fs ) { + alloc_.free( fs ); + } +#endif +} + +/*----------------------------------------------------------------------------*/ + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_ThreadedPoolAllocator< OS , OA , P , M >::countFreeLists( ) const noexcept +{ + return alloc_.countFreeLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_ThreadedPoolAllocator< OS , OA , P , M >::countPartialLists( ) const noexcept +{ + return alloc_.countPartialLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +size_t T_ThreadedPoolAllocator< OS , OA , P , M >::countFullLists( ) const noexcept +{ + return alloc_.countFullLists( ); +} + +template< size_t OS , size_t OA , size_t P , size_t M > +void T_ThreadedPoolAllocator< OS , OA , P , M >::getUsage( + size_t& total , + size_t& free , + size_t& used ) const noexcept +{ + return alloc_.getUsage( total , free , used ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t OS , size_t OA , size_t P , size_t M > +typename T_ThreadedPoolAllocator< OS , OA , P , M >::T_Storage_* +T_ThreadedPoolAllocator< OS , OA , P , M >::takeFromStack( ) noexcept +{ + T_FreeHead_ o( freeHead_.load( std::memory_order_relaxed ) ); + if ( o.head == nullptr ) { + return nullptr; + } + + T_FreeHead_ next; + do { + next.aba = o.aba + 1; + next.head = o.head->extra.next; + } while ( !freeHead_.compare_exchange_weak( o , next , + std::memory_order_acq_rel , + std::memory_order_acquire ) ); + return o.head; +} + +template< size_t OS , size_t OA , size_t P , size_t M > +void T_ThreadedPoolAllocator< OS , OA , P , M >::addToStack( + T_Storage_* storage ) noexcept +{ + assert( storage->extra.pool == this ); + T_FreeHead_ o( freeHead_.load( std::memory_order_relaxed ) ); + T_FreeHead_ r; + do { + storage->extra.next = o.head; + r.aba = o.aba + 1; + r.head = storage; + } while ( !freeHead_.compare_exchange_weak( o , r , + std::memory_order_acq_rel , + std::memory_order_acquire ) ); +} + + +} +#endif //_H_LW_LIB_INLINE_ALLOC diff --git a/include/ebcl/inline/Arrays.hh b/include/ebcl/inline/Arrays.hh new file mode 100644 index 0000000..3327f43 --- /dev/null +++ b/include/ebcl/inline/Arrays.hh @@ -0,0 +1,1594 @@ +/******************************************************************************/ +/* ARRAYS - INLINE CODE *******************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_ARRAYS +#define _H_LW_LIB_INLINE_ARRAYS +#include +namespace lw { + + +/*= T_Array ==================================================================*/ + +template< typename T > +inline T_Array< T >::T_Array( ) noexcept + : T_Array( DEFAULT_GROWTH( ) ) +{ } + +template< typename T > +inline T_Array< T >::T_Array( + const uint32_t growth ) noexcept + : data_( nullptr ) , capacity_( 0 ) , size_( 0 ) , growth_( growth ) +{ + assert( growth > 0 ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >::T_Array( + T_Array< T > const& source ) noexcept + : capacity_( source.capacity_ ) , size_( source.size_ ) , + growth_( source.growth_ ) +{ + if ( capacity_ != 0 ) { + data_ = ( T* )::operator new ( capacity_ * sizeof( T ) ); + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + ::new ( ( char* ) &( data_[ i ] ) ) T( source[ i ] ); + } + } else { + memcpy( data_ , source.data_ , source.size_ * sizeof( T ) ); + } + } else { + data_ = nullptr; + } +} + +template< typename T > +inline T_Array< T >& T_Array< T >::operator= ( + T_Array< T > const& other ) noexcept +{ + free( ); + capacity_ = other.capacity_; + data_ = ( T* )::operator new ( sizeof( T ) * capacity_ ); + size_ = other.size_; + growth_ = other.growth_; + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + ::new ( ( char* ) &( data_[ i ] ) )T( other[ i ] ); + } + } else if ( size_ ) { + memcpy( data_ , other.data_ , size_ * sizeof( T ) ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >::T_Array( + T_Array< T >&& source ) noexcept + : T_Array( 1 ) +{ + swap( *this , source ); + source.clear( ); +} + +template< typename T > +inline T_Array< T >& T_Array< T >::operator=( + T_Array< T >&& other ) noexcept +{ + swap( *this , other ); + other.clear( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_Array< T >& lhs , + T_Array< T >& rhs ) noexcept +{ + using std::swap; + swap( lhs.data_ , rhs.data_ ); + swap( lhs.capacity_ , rhs.capacity_ ); + swap( lhs.size_ , rhs.size_ ); + swap( lhs.growth_ , rhs.growth_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >::~T_Array( ) +{ + free( ); +} + +template< typename T > +inline T_Array< T >& T_Array< T >::clear( ) noexcept +{ + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + data_[ i ].~T( ); + } + } + size_ = 0; + return *this; +} + +template< typename T > +inline T_Array< T >& T_Array< T >::free( ) noexcept +{ + clear( ); + ::operator delete ( (void*) data_ ); + data_ = nullptr; + capacity_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline uint32_t T_Array< T >::capacity( ) const noexcept +{ + return capacity_; +} + +template< typename T > +inline uint32_t T_Array< T >::size( ) const noexcept +{ + return size_; +} + +template< typename T > +inline uint32_t T_Array< T >::growth( ) const noexcept +{ + return growth_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >& T_Array< T >::ensureCapacity( + const uint32_t capacity ) noexcept +{ + if ( capacity_ < capacity ) { + const uint32_t mod( capacity % growth_ ); + const uint32_t nCap( capacity + ( mod == 0 ? 0 : ( growth_ - mod ) ) ); + T* const nData = ( T* )::operator new ( sizeof( T ) * nCap ); + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + ::new ( reinterpret_cast< char* >( &nData[ i ] ) ) + T( std::move( data_[ i ] ) ); + data_[ i ].~T( ); + } + } else { + memcpy( nData , data_ , size_ * sizeof( T ) ); + } + ::operator delete ( (void*) data_ ); + capacity_ = nCap; + data_ = nData; + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T& T_Array< T >::operator[] ( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + return data_[ index ]; +} + +template< typename T > +inline T const& T_Array< T >::operator[] ( + const uint32_t index ) const noexcept +{ + assert( index < size_ ); + return data_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline int32_t T_Array< T >::indexOf( + T const& item ) const noexcept +{ + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + if ( data_[ i ] == item ) { + return i; + } + } + return -1; +} + +template< typename T > +inline bool T_Array< T >::contains( + T const& item ) const noexcept +{ + return indexOf( item ) != -1; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline uint32_t T_Array< T >::add( + T const& item ) noexcept +{ + ensureCapacity( size_ + 1 ); + ::new ( ( char* ) &( data_[ size_ ] ) ) T( item ); + return size_ ++; +} + +template< typename T > +inline uint32_t T_Array< T >::add( + T&& item ) noexcept +{ + ensureCapacity( size_ + 1 ); + ::new ( ( char* ) &( data_[ size_ ] ) ) T( std::move( item ) ); + return size_ ++; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< typename... Args > +inline T& T_Array< T >::addNew( + Args&& ... args ) +{ + ensureCapacity( size_ + 1 ); + ::new ( ( char* ) &( data_[ size_ ] ) ) T( std::forward< Args >( args ) ... ); + return data_[ size_ ++ ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >& T_Array< T >::addAll( + T_Array< T > const& other ) noexcept +{ + ensureCapacity( size_ + other.size_ ); + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < other.size_ ; i ++ ) { + ::new ( ( char* ) &( data_[ size_ + i ] ) ) T( other.data_[ i ] ); + } + } else if ( other.size_ ) { + memcpy( data_ + size_ , other.data_ , sizeof( T ) * other.size_ ); + } + size_ += other.size_; + return *this; +} + +template< typename T > +inline T_Array< T >& T_Array< T >::addAll( + T_Array< T >&& other ) noexcept +{ + ensureCapacity( size_ + other.size_ ); + if ( std::is_class< T >( ) ) { + for ( uint32_t i = 0 ; i < other.size_ ; i ++ ) { + ::new ( ( char* ) &( data_[ size_ + i ] ) ) T( std::move( other.data_[ i ] ) ); + } + } else if ( other.size_ ) { + memcpy( data_ + size_ , other.data_ , sizeof( T ) * other.size_ ); + } + size_ += other.size_; + other.size_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T >& T_Array< T >::operator<< ( + T const& item ) noexcept +{ + add( item ); + return *this; +} + +template< typename T > +inline T_Array< T >& T_Array< T >::operator<< ( + T&& item ) noexcept +{ + add( std::move( item ) ); + return *this; +} + +template< typename T > +inline T_Array< T >& T_Array< T >::operator<< ( + T_Array< T > const& other ) noexcept +{ + addAll( other ); + return *this; +} + +template< typename T > +inline T_Array< T >& T_Array< T >::operator<< ( + T_Array< T >&& other ) noexcept +{ + addAll( std::move( other ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_Array< T >::insert( + const uint32_t index , + T const& item ) noexcept +{ + if ( std::is_class< T >( ) ) { + using std::swap; + const auto last( add( item ) ); + for ( uint32_t i = index ; i < last ; i ++ ) { + swap( data_[ i ] , data_[ last ] ); + } + } else { + ensureCapacity( size_ + 1 ); + if ( index < size_ ) { + memmove( &data_[ index + 1 ] , &data_[ index ] , + sizeof( T ) * ( size_ - index ) ); + } + data_[ index ] = item; + size_ ++; + } +} + +template< typename T > +inline void T_Array< T >::insert( + const uint32_t index , + T&& item ) noexcept +{ + if ( std::is_class< T >( ) ) { + using std::swap; + const auto last( add( std::move( item ) ) ); + for ( uint32_t i = index ; i < last ; i ++ ) { + swap( data_[ i ] , data_[ last ] ); + } + } else { + insert( index , (T const&) item ); + } +} + +template< typename T > +template< typename... Args > +inline T& T_Array< T >::insertNew( + const uint32_t index , + Args&& ... args ) +{ + using std::swap; + addNew( std::forward< Args >( args ) ... ); + const auto last( size_ - 1 ); + for ( uint32_t i = index ; i < last ; i ++ ) { + swap( data_[ i ] , data_[ last ] ); + } + return data_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_Array< T >::remove( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + if ( index != size_ - 1 ) { + if ( std::is_class< T >( ) ) { + for ( auto i = index ; i < size_ - 1 ; i ++ ) { + data_[ i ] = std::move( data_[ i + 1 ] ); + } + } else { + memmove( &data_[ index ] , &data_[ index + 1 ] , + ( size_ - ( index + 1 ) ) * sizeof( T ) ); + } + } + data_[ size_ - 1 ].~T( ); + size_ --; +} + +template< typename T > +inline void T_Array< T >::removeSwap( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + if ( index != size_ - 1 ) { + data_[ index ] = std::move( data_[ size_ - 1 ] ); + } + data_[ size_ - 1 ].~T( ); + size_ --; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_Array< T >::sort( + F_Comparator< T > cmp ) noexcept +{ + sort( 0 , size_ , std::move( cmp ) ); +} + +template< typename T > +inline void T_Array< T >::sort( + const uint32_t first , + const uint32_t items , + F_Comparator< T > cmp ) noexcept +{ + if ( items != 0 ) { + assert( first < size_ ); + assert( first + items <= size_ ); + Sort( &data_[ first ] , items , std::move( cmp ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Array< T > T_Array< T >::copyRange( + const uint32_t first , + const uint32_t last ) const noexcept +{ + if ( first >= size_ || last < first ) { + return T_Array< T >( 1 ); + } + + // FIXME use memcpy when !std::is_class< T > + const auto l( std::min( last , size_ - 1 ) ); + T_Array< T > output( growth_ ); + output.ensureCapacity( l - first + 1 ); + for ( uint32_t i = first ; i <= l ; i ++ ) { + output.add( data_[ i ] ); + } + return output; +} + +template< typename T > +inline T_Array< T > T_Array< T >::moveRange( + const uint32_t first , + const uint32_t last ) noexcept +{ + if ( first >= size_ || last < first ) { + return T_Array< T >( 1 ); + } + + // FIXME use memcpy when !std::is_class< T > + const auto l( std::min( last , size_ - 1 ) ); + T_Array< T > output( growth_ ); + output.ensureCapacity( l - first + 1 ); + for ( uint32_t i = first ; i <= l ; i ++ ) { + output.add( std::move( data_[ i ] ) ); + } + return output; +} + + +/*= T_StaticArray ============================================================*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >::T_StaticArray( ) noexcept + : size_( 0 ) +{ } + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >::T_StaticArray( + T_Self const& source ) noexcept + : size_( source.size_ ) +{ + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < size_ ; i ++ ) { + ::new ( (char*) &storage_[ i ] ) T( source[ i ] ); + } + } else { + std::memcpy( &storage_ , &source.storage_ , + size_ * sizeof( T_Storage_ ) ); + } +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator= ( + T_Self const& source ) noexcept +{ + clear( ); + size_ = source.size_; + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < size_ ; i ++ ) { + ::new ( (char*) &storage_[ i ] ) T( source[ i ] ); + } + } else { + std::memcpy( &storage_ , &source.storage_ , + size_ * sizeof( T_Storage_ ) ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >::T_StaticArray( + T_Self&& source ) noexcept + : size_( source.size_ ) +{ + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < size_ ; i ++ ) { + ::new ( (char*) &storage_[ i ] ) T( std::move( source[ i ] ) ); + } + } else { + std::memcpy( &storage_ , &source.storage_ , + size_ * sizeof( T_Storage_ ) ); + } + source.clear( ); +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator= ( + T_Self&& source ) noexcept +{ + clear( ); + size_ = source.size_; + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < size_ ; i ++ ) { + ::new ( (char*) &storage_[ i ] ) T( std::move( source[ i ] ) ); + } + } else { + std::memcpy( &storage_ , &source.storage_ , + size_ * sizeof( T_Storage_ ) ); + } + source.clear( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >::~T_StaticArray( ) noexcept +{ + clear( ); +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::clear( ) noexcept +{ + for ( auto i = 0u ; i < size_ ; i ++ ) { + reinterpret_cast< T* >( &storage_[ i ] )->T::~T( ); + } + size_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +void swap( + T_StaticArray< T , S >& lhs , + T_StaticArray< T , S >& rhs ) noexcept +{ + using T_ = T_StaticArray< T , S >; + + if ( lhs.size( ) && rhs.size( ) ) { + T_ temp( std::move( rhs ) ); + rhs = std::move( lhs ); + lhs = std::move( temp ); + } else if ( lhs.size( ) || rhs.size( ) ) { + T_& empty( lhs.size( ) ? rhs : lhs ); + T_& notEmpty( lhs.size( ) ? lhs : rhs ); + empty = std::move( notEmpty ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline constexpr uint32_t T_StaticArray< T , S >::capacity( ) const noexcept +{ + return S; +} + +template< typename T , uint32_t S > +inline uint32_t T_StaticArray< T , S >::size( ) const noexcept +{ + return size_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T& T_StaticArray< T , S >::operator[] ( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + return *reinterpret_cast< T* >( &storage_[ index ] ); +} + +template< typename T , uint32_t S > +inline T const& T_StaticArray< T , S >::operator[] ( + const uint32_t index ) const noexcept +{ + assert( index < size_ ); + return *reinterpret_cast< T const* >( &storage_[ index ] ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline int32_t T_StaticArray< T , S >::indexOf( + T const& item ) const noexcept +{ + for ( auto i = 0u ; i < size_ ; i ++ ) { + if ( (*this)[ i ] == item ) { + return i; + } + } + return -1; +} + +template< typename T , uint32_t S > +inline bool T_StaticArray< T , S >::contains( + T const& item ) const noexcept +{ + return indexOf( item ) != -1; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline uint32_t T_StaticArray< T , S >::add( + T&& item ) noexcept +{ + assert( size_ < S ); + const auto index( size_ ++ ); + ::new ( (char*) &storage_[ index ] ) T( std::move( item ) ); + return index; +} + +template< typename T , uint32_t S > +inline uint32_t T_StaticArray< T , S >::add( + T const& item ) noexcept +{ + assert( size_ < S ); + const auto index( size_ ++ ); + ::new ( (char*) &storage_[ index ] ) T( item ); + return index; +} + +template< typename T , uint32_t S > +template< typename... Args > +inline T& T_StaticArray< T , S >::addNew( + Args&& ... args ) +{ + assert( size_ < S ); + const auto index( size_ ++ ); + ::new ( (char*) &storage_[ index ] ) T( std::forward< Args >( args ) ... ); + return (*this)[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +T_StaticArray< T , S >& T_StaticArray< T , S >::addAll( + T_Self const& other ) noexcept +{ + assert( size_ + other.size_ <= S ); + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < other.size_ ; i ++ , size_ ++ ) { + ::new ( (char*) &storage_[ size_ ] ) T( other[ i ] ); + } + } else { + memcpy( &storage_[ size_ ] , &other.storage_[ 0 ] , + other.size_ * sizeof( T_Storage_ ) ); + size_ += other.size_; + } + return *this; +} + +template< typename T , uint32_t S > +T_StaticArray< T , S >& T_StaticArray< T , S >::addAll( + T_Self&& other ) noexcept +{ + assert( size_ + other.size_ <= S ); + if ( std::is_class< T >( ) ) { + for ( auto i = 0u ; i < other.size_ ; i ++ , size_ ++ ) { + ::new ( (char*) &storage_[ size_ ] ) T( std::move( other[ i ] ) ); + } + } else { + memcpy( &storage_[ size_ ] , &other.storage_[ 0 ] , + other.size_ * sizeof( T_Storage_ ) ); + size_ += other.size_; + } + other.clear( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator<< ( + T const& item ) noexcept +{ + add( item ); + return *this; +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator<< ( + T&& item ) noexcept +{ + add( std::move( item ) ); + return *this; +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator<< ( + T_Self const& other ) noexcept +{ + addAll( other ); + return *this; +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S >& T_StaticArray< T , S >::operator<< ( + T_Self&& other ) noexcept +{ + addAll( std::move( other ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::insert( + const uint32_t index , + T&& item ) noexcept +{ + assert( size_ < S ); + if ( std::is_class< T >( ) ) { + const auto last( add( std::move( item ) ) ); + for ( uint32_t i = index ; i < last ; i ++ ) { + using std::swap; + swap( (*this)[ i ] , (*this)[ last ] ); + } + } else { + insert( index , (T const&) item ); + } +} + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::insert( + const uint32_t index , + T const& item ) noexcept +{ + assert( size_ < S ); + if ( std::is_class< T >( ) ) { + const auto last( add( item ) ); + for ( uint32_t i = index ; i < last ; i ++ ) { + using std::swap; + swap( (*this)[ i ] , (*this)[ last ] ); + } + } else { + if ( index < size_ ) { + memmove( &storage_[ index + 1 ] , &storage_[ index ] , + sizeof( T_Storage_ ) * ( size_ - index ) ); + } + *reinterpret_cast< T* >( &storage_[ index ] ) = item; + size_ ++; + } +} + +template< typename T , uint32_t S > +template< typename... Args > +inline T& T_StaticArray< T , S >::insertNew( + const uint32_t index , + Args&& ... args ) +{ + addNew( std::forward< Args >( args ) ... ); + const auto last( size_ - 1 ); + for ( uint32_t i = index ; i < last ; i ++ ) { + swap( (*this)[ i ] , (*this)[ last ] ); + } + return (*this)[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::remove( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + if ( index != size_ - 1 ) { + if ( std::is_class< T >( ) ) { + for ( auto i = index ; i < size_ - 1 ; i ++ ) { + (*this)[ i ] = std::move( (*this)[ i + 1 ] ); + } + } else { + memmove( &storage_[ index ] , &storage_[ index + 1 ] , + ( size_ - ( index + 1 ) ) * sizeof( T_Storage_ ) ); + } + } + (*this)[ size_ - 1 ].~T( ); + size_ --; +} + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::removeSwap( + const uint32_t index ) noexcept +{ + assert( index < size_ ); + if ( index != size_ - 1 ) { + (*this)[ index ] = std::move( (*this)[ size_ - 1 ] ); + } + (*this)[ size_ - 1 ].~T( ); + size_ --; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::sort( + const F_Comparator< T > cmp ) noexcept +{ + sort( 0 , size_ , cmp ); +} + +template< typename T , uint32_t S > +inline void T_StaticArray< T , S >::sort( + const uint32_t first , + const uint32_t items , + const F_Comparator< T > cmp ) noexcept +{ + if ( items != 0 ) { + assert( first < size_ ); + assert( first + items <= size_ ); + Sort( &(*this)[ first ] , items , cmp ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S > +inline T_StaticArray< T , S > T_StaticArray< T , S >::copyRange( + const uint32_t first , + const uint32_t last ) const noexcept +{ + if ( first >= size_ || last < first ) { + return T_StaticArray< T , S >( ); + } + + const auto l( std::min( last , size_ - 1 ) ); + T_StaticArray< T , S > output; + // FIXME use memcpy when !std::is_class< T > + for ( uint32_t i = first ; i <= l ; i ++ ) { + output.add( (*this)[ i ] ); + } + return output; +} + +template< typename T , uint32_t S > +inline T_StaticArray< T , S > T_StaticArray< T , S >::moveRange( + const uint32_t first , + const uint32_t last ) noexcept +{ + if ( first >= size_ || last < first ) { + return T_StaticArray< T , S >( ); + } + + const auto l( std::min( last , size_ - 1 ) ); + T_StaticArray< T , S > output; + for ( uint32_t i = first ; i <= l ; i ++ ) { + output.add( std::move( (*this)[ i ] ) ); + } + return output; +} + + +/*= T_MultiArray ============================================================-*/ + +template< typename T > +inline void swap( T_MultiArray< T >& lhs , T_MultiArray< T >& rhs ) +{ + using std::swap; + swap( lhs.meta_ , rhs.meta_ ); + swap( lhs.values_ , rhs.values_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_MultiArray< T >::next( ) +{ + const auto sz( meta_.size( ) ); + const auto first( ( sz == 0 ) ? 0 : ( meta_[ sz - 2 ] + meta_[ sz - 1 ] ) ); + meta_ << first << 0; +} + +template< typename T > +inline void T_MultiArray< T >::add( T value ) +{ + if ( meta_.size( ) == 0 ) { + meta_ << 0 << 1; + } else { + meta_[ meta_.size( ) - 1 ] ++; + } + values_ << value; +} + +template< typename T > +template< + typename Q , + typename... Args , + typename > +inline Q& T_MultiArray< T >::addNew( Args&& ... args ) +{ + if ( meta_.size( ) == 0 ) { + meta_ << 0 << 1; + } else { + meta_[ meta_.size( ) - 1 ] ++; + } + return values_.addNew( std::forward< Args >( args ) ... ); +} + +template< typename T > +inline void T_MultiArray< T >::copyFrom( T_Array< T > const& source ) +{ + values_.addAll( source ); + if ( meta_.size( ) == 0 ) { + meta_ << 0 << source.size( ); + } else { + meta_[ meta_.size( ) - 1 ] += source.size( ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline uint32_t T_MultiArray< T >::size( ) const +{ + return meta_.size( ) >> 1; +} + +template< typename T > +inline uint32_t T_MultiArray< T >::firstOf( uint32_t item ) const +{ + return meta_[ item * 2 ]; +} + +template< typename T > +inline uint32_t T_MultiArray< T >::sizeOf( uint32_t item ) const +{ + return meta_[ item * 2 + 1 ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T const& T_MultiArray< T >::get( uint32_t item , uint32_t sub ) const +{ + assert( sub < meta_[ item * 2 + 1 ] ); + return values_[ meta_[ item * 2 ] + sub ]; +} + +template< typename T > +inline T& T_MultiArray< T >::get( uint32_t item , uint32_t sub ) +{ + assert( sub < meta_[ item * 2 + 1 ] ); + return values_[ meta_[ item * 2 ] + sub ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T const& T_MultiArray< T >::operator[] ( uint32_t index ) const +{ + return values_[ index ]; +} + +template< typename T > +inline T& T_MultiArray< T >::operator[] ( uint32_t index ) +{ + return values_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline bool T_MultiArray< T >::contains( uint32_t index , T const& value ) const +{ + const auto first( firstOf( index ) ); + const auto end( first + sizeOf( index ) ); + for ( uint32_t i = first ; i < end ; i ++ ) { + if ( values_[ i ] == value ) { + return true; + } + } + return false; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_MultiArray< T >::clear( ) +{ + meta_.clear( ); + values_.clear( ); +} + +template< typename T > +inline void T_MultiArray< T >::free( ) +{ + meta_.free( ); + values_.free( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_MultiArray< T >::copyFrom( uint32_t index ) +{ + copyFrom( *this , index ); +} + +template< typename T > +inline void T_MultiArray< T >::copyFrom( + T_MultiArray< T > const& other , uint32_t index ) +{ + const auto size( other.sizeOf( index ) ); + if ( size == 0 ) { + return; + } + + const auto first( other.firstOf( index ) ); + T const* ptr( &( other[ first ] ) ); + for ( uint32_t i = 0 ; i < size ; i ++ , ptr ++ ) { + values_ << *ptr; + } + if ( meta_.size( ) == 0 ) { + meta_ << 0 << size; + } else { + meta_[ meta_.size( ) - 1 ] += size; + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_MultiArray< T >::copyUnique( uint32_t index ) +{ + copyUnique( *this , index ); +} + +template< typename T > +inline void T_MultiArray< T >::copyUnique( T_MultiArray< T > const& other , uint32_t index ) +{ + const auto oSize( other.sizeOf( index ) ); + if ( oSize == 0 ) { + return; + } + + const auto ms( meta_.size( ) ); + const auto tSize( ms == 0 ? 0 : meta_[ ms - 1 ] ); + const auto tFirst( ms == 0 ? 0 : meta_[ ms - 2 ] ); + + const auto oFirst( other.firstOf( index ) ); + T const* ptr( &( other[ oFirst ] ) ); + + uint32_t added = 0; + for ( uint32_t i = 0 ; i < oSize ; i ++ , ptr ++ ) { + bool found = false; + if ( tSize != 0 ) { + T const* pCheck( &values_[ tFirst ] ); + for ( uint32_t j = 0 ; j < tSize ; j ++ , pCheck ++ ) { + if ( *pCheck == *ptr ) { + found = true; + break; + } + } + } + if ( !found ) { + values_ << *ptr; + added ++; + } + } + if ( ms == 0 ) { + meta_ << 0 << added; + } else { + meta_[ ms - 1 ] += added; + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_MultiArray< T >::sort( uint32_t index , F_Comparator< T > cmp ) +{ + values_.sort( meta_[ index * 2 ] , meta_[ index * 2 + 1 ] , cmp ); +} + + +/*= T_AutoArray ==============================================================*/ + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >::T_AutoArray( ) noexcept + : array_( ) +{ } + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >::T_AutoArray( + T_Self const& source ) noexcept + : array_( source.array_ ) +{ } + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator =( + T_Self const& source ) noexcept +{ + array_ = source.array_; + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >::T_AutoArray( + T_Self&& source ) noexcept + : array_( std::move( source.array_ ) ) +{ } + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator =( + T_Self&& source ) noexcept +{ + array_ = std::move( source.array_ ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline void swap( + T_AutoArray< T , S , G >& lhs , + T_AutoArray< T , S , G >& rhs ) noexcept +{ + swap( lhs.array_ , rhs.array_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::clear( ) noexcept +{ + if ( isStatic( ) ) { + static_( ).clear( ); + } else { + dynamic_( ).clear( ); + } + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::free( ) noexcept +{ + array_ = T_Static_( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline uint32_t T_AutoArray< T , S , G >::capacity( ) const noexcept +{ + return isStatic( ) ? S : dynamic_( ).capacity( ); +} + +template< typename T , uint32_t S , uint32_t G > +inline uint32_t T_AutoArray< T , S , G >::size( ) const noexcept +{ + return isStatic( ) ? static_( ).size( ) : dynamic_( ).size( ); +} + +template< typename T , uint32_t S , uint32_t G > +inline constexpr uint32_t T_AutoArray< T , S , G >::growth( ) const noexcept +{ + return G; +} + +template< typename T , uint32_t S , uint32_t G > +inline bool T_AutoArray< T , S , G >::isStatic( ) const noexcept +{ + return array_.template hasType< T_Static_ >( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::ensureCapacity( + const uint32_t capacity ) noexcept +{ + if ( capacity > S ) { + if ( isStatic( ) ) { + convertToDynamic_( capacity ); + } else { + dynamic_( ).ensureCapacity( capacity ); + } + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline T& T_AutoArray< T , S , G >::operator[] ( + const uint32_t index ) noexcept +{ + return isStatic( ) ? static_( )[ index ] : dynamic_( )[ index ]; +} + +template< typename T , uint32_t S , uint32_t G > +inline T const& T_AutoArray< T , S , G >::operator[] ( + const uint32_t index ) const noexcept +{ + return isStatic( ) ? static_( )[ index ] : dynamic_( )[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline int32_t T_AutoArray< T , S , G >::indexOf( + T const& item ) const noexcept +{ + return isStatic( ) + ? static_( ).indexOf( item ) + : dynamic_( ).indexOf( item ); +} + +template< typename T , uint32_t S , uint32_t G > +inline bool T_AutoArray< T , S , G >::contains( + T const& item ) const noexcept +{ + return indexOf( item ) != -1; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline uint32_t T_AutoArray< T , S , G >::add( + T const& item ) noexcept +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) ? static_( ).add( item ) : dynamic_( ).add( item ); +} + +template< typename T , uint32_t S , uint32_t G > +inline uint32_t T_AutoArray< T , S , G >::add( + T&& item ) noexcept +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) + ? static_( ).add( std::move( item ) ) + : dynamic_( ).add( std::move( item ) ); +} + +template< typename T , uint32_t S , uint32_t G > +template< typename... Args > +inline T& T_AutoArray< T , S , G >::addNew( + Args&& ... args ) +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) + ? static_( ).addNew( std::forward< Args >( args ) ... ) + : dynamic_( ).addNew( std::forward< Args >( args ) ... ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::addAll( + T_Self const& other ) noexcept +{ + const auto osz( other.size( ) ); + if ( !osz ) { + return *this; + } + + // Try using container's native addAll() if we can + const auto inisz( size( ) ); + const bool tst( isStatic( ) ); + const bool ost( other.isStatic( ) ); + if ( tst == ost && ( !tst || inisz + osz <= S ) ) { + if ( tst ) { + static_( ).addAll( other.static_( ) ); + } else { + dynamic_( ).addAll( other.dynamic_( ) ); + } + return *this; + } + + // Otherwise we need to add manually. + ensureCapacity( inisz + osz ); + if ( isStatic( ) ) { + for ( uint32_t i = 0 ; i < osz ; i ++ ) { + static_( ).add( other[ i ] ); + } + } else { + for ( uint32_t i = 0 ; i < osz ; i ++ ) { + dynamic_( ).add( other[ i ] ); + } + } + + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +inline T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::addAll( + T_Self&& other ) noexcept +{ + const auto osz( other.size( ) ); + if ( !osz ) { + return *this; + } + + // Try using container's native addAll() if we can + const auto inisz( size( ) ); + const bool tst( isStatic( ) ); + const bool ost( other.isStatic( ) ); + if ( tst == ost && ( !tst || inisz + osz <= S ) ) { + if ( tst ) { + static_( ).addAll( std::move( other.static_( ) ) ); + } else { + dynamic_( ).addAll( std::move( other.dynamic_( ) ) ); + } + return *this; + } + + // Otherwise we need to add manually. + ensureCapacity( inisz + osz ); + if ( isStatic( ) ) { + for ( uint32_t i = 0 ; i < osz ; i ++ ) { + static_( ).add( std::move( other[ i ] ) ); + } + } else { + for ( uint32_t i = 0 ; i < osz ; i ++ ) { + dynamic_( ).add( std::move( other[ i ] ) ); + } + } + other.free( ); + + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline void T_AutoArray< T , S , G >::insert( + const uint32_t index , + T const& item ) noexcept +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) + ? static_( ).insert( index , item ) + : dynamic_( ).insert( index , item ); +} + +template< typename T , uint32_t S , uint32_t G > +inline void T_AutoArray< T , S , G >::insert( + const uint32_t index , + T&& item ) noexcept +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) + ? static_( ).insert( index , std::move( item ) ) + : dynamic_( ).insert( index , std::move( item ) ); +} + +template< typename T , uint32_t S , uint32_t G > +template< typename... Args > +inline T& T_AutoArray< T , S , G >::insertNew( + const uint32_t index , + Args&& ... args ) +{ + ensureCapacity( size( ) + 1 ); + return isStatic( ) + ? static_( ).insertNew( index , std::forward< Args >( args ) ... ) + : dynamic_( ).insertNew( index , std::forward< Args >( args ) ... ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline void T_AutoArray< T , S , G >::remove( + const uint32_t index ) noexcept +{ + return isStatic( ) + ? static_( ).remove( index ) + : dynamic_( ).remove( index ); +} + +template< typename T , uint32_t S , uint32_t G > +inline void T_AutoArray< T , S , G >::removeSwap( + const uint32_t index ) noexcept +{ + return isStatic( ) + ? static_( ).removeSwap( index ) + : dynamic_( ).removeSwap( index ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +void T_AutoArray< T , S , G >::sort( + F_Comparator< T > cmp ) noexcept +{ + sort( 0 , size( ) , std::move( cmp ) ); +} + +template< typename T , uint32_t S , uint32_t G > +void T_AutoArray< T , S , G >::sort( + const uint32_t first , + const uint32_t items , + F_Comparator< T > cmp ) noexcept +{ + if ( isStatic( ) ) { + static_( ).sort( first , items , std::move( cmp ) ); + } else { + dynamic_( ).sort( first , items , std::move( cmp ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G > T_AutoArray< T , S , G >::copyRange( + const uint32_t first , + const uint32_t last ) const noexcept +{ + // XXX: this uses manual calls to underlying add() methods; it could + // be optimised, but I'm not sure it's necessary to do so. + + auto const sz( size( ) ); + if ( first >= sz || last < first ) { + return T_Self( ); + } + + const auto l( std::min( last , sz - 1 ) ); + T_Self nd; + nd.ensureCapacity( l - first + 1 ); + if ( isStatic( ) ) { + T_Static_ const& src( static_( ) ); + T_Static_& dst( nd.static_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( src[ i ] ); + } + } else if ( nd.isStatic( ) ) { + T_Dynamic_ const& src( dynamic_( ) ); + T_Static_& dst( nd.static_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( src[ i ] ); + } + } else { + T_Dynamic_ const& src( dynamic_( ) ); + T_Dynamic_& dst( nd.dynamic_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( src[ i ] ); + } + } + return nd; +} + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G > T_AutoArray< T , S , G >::moveRange( + const uint32_t first , + const uint32_t last ) noexcept +{ + // XXX: this uses manual calls to underlying add() methods; it could + // be optimised, but I'm not sure it's necessary to do so. + + auto const sz( size( ) ); + if ( first >= sz || last < first ) { + return T_Self( ); + } + + const auto l( std::min( last , sz - 1 ) ); + T_Self nd; + nd.ensureCapacity( l - first + 1 ); + if ( isStatic( ) ) { + T_Static_& src( static_( ) ); + T_Static_& dst( nd.static_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( std::move( src[ i ] ) ); + } + } else if ( nd.isStatic( ) ) { + T_Dynamic_& src( dynamic_( ) ); + T_Static_& dst( nd.static_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( std::move( src[ i ] ) ); + } + } else { + T_Dynamic_& src( dynamic_( ) ); + T_Dynamic_& dst( nd.dynamic_( ) ); + for ( auto i = first ; i <= l ; i ++ ) { + dst.add( std::move( src[ i ] ) ); + } + } + return nd; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +void T_AutoArray< T , S , G >::convertToDynamic_( + const uint32_t capacity ) noexcept +{ + assert( isStatic( ) ); + T_Static_& s( static_( ) ); + T_Dynamic_ d{ G }; + d.ensureCapacity( std::max( s.capacity( ) , capacity ) ); + auto const sz( s.size( ) ); + for ( auto i = 0u ; i < sz ; i ++ ) { + d.add( std::move( s[ i ] ) ); + } + array_ = std::move( d ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator<< ( + T const& item ) noexcept +{ + add( item ); + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator<< ( + T&& item ) noexcept +{ + add( std::move( item ) ); + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator<< ( + T_Self const& other ) noexcept +{ + addAll( other ); + return *this; +} + +template< typename T , uint32_t S , uint32_t G > +T_AutoArray< T , S , G >& T_AutoArray< T , S , G >::operator<< ( + T_Self&& other ) noexcept +{ + addAll( std::move( other ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , uint32_t S , uint32_t G > +inline T_StaticArray< T , S >& T_AutoArray< T , S , G >::static_( ) noexcept +{ + return array_.template value< T_Static_ >( ); +} + +template< typename T , uint32_t S , uint32_t G > +inline T_StaticArray< T , S > const& T_AutoArray< T , S , G >::static_( ) const noexcept +{ + return array_.template value< T_Static_ >( ); +} + +template< typename T , uint32_t S , uint32_t G > +inline T_Array< T >& T_AutoArray< T , S , G >::dynamic_( ) noexcept +{ + return array_.template value< T_Dynamic_ >( ); +} + +template< typename T , uint32_t S , uint32_t G > +inline T_Array< T > const& T_AutoArray< T , S , G >::dynamic_( ) const noexcept +{ + return array_.template value< T_Dynamic_ >( ); +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_ARRAYS diff --git a/include/ebcl/inline/BinaryStreams.hh b/include/ebcl/inline/BinaryStreams.hh new file mode 100644 index 0000000..a3dfbea --- /dev/null +++ b/include/ebcl/inline/BinaryStreams.hh @@ -0,0 +1,145 @@ +/******************************************************************************/ +/* BINARY READER/WRITER FOR STREAMS - INLINE CODE *****************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= T_BinaryReader ===========================================================*/ + +inline T_BinaryReader::T_BinaryReader( A_InputStream& stream , E_Endian endian ) noexcept + : stream_( stream ) , endian_( endian ) +{ } + +inline A_InputStream& T_BinaryReader::stream( ) const +{ + return stream_; +} + +inline E_Endian T_BinaryReader::endian( ) const +{ + return endian_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T T_BinaryReader::readNumericNative( ) const +{ + static_assert( std::is_integral< T >( ) || std::is_floating_point< T >( ) , + "numeric type expected" ); + T value = 0; + auto r = stream_.read( &value , sizeof( T ) ); + if ( r != sizeof( T ) ) { + throw X_StreamError( E_StreamError::BAD_DATA ); + } + return value; +} + +template< typename T > +inline T T_BinaryReader::readNumericLittleEndian( ) const +{ + return T_LittleEndian< T >::convert( readNumericNative< T >( ) ); +} + +template< typename T > +inline T T_BinaryReader::readNumericBigEndian( ) const +{ + return T_BigEndian< T >::convert( readNumericNative< T >( ) ); +} + +template< typename T > +inline T T_BinaryReader::read( ) const +{ + return T_Reader_< T >::read( *this ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T T_BinaryReader::T_Reader_< T , true >::read( T_BinaryReader const& reader ) +{ + if ( reader.endian_ == E_Endian::BIG ) { + return reader.readNumericBigEndian< T >( ); + } else { + return reader.readNumericLittleEndian< T >( ); + } +} + +template< typename T > +inline T T_BinaryReader::T_Reader_< T , false >::read( T_BinaryReader const& reader ) +{ + typedef T_ObjectReader< T > Reader; + return Reader::read( reader ); +} + + +/*= T_BinaryWriter ===========================================================*/ + +inline T_BinaryWriter::T_BinaryWriter( A_OutputStream& stream , E_Endian endian ) noexcept + : stream_( stream ) , endian_( endian ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline A_OutputStream& T_BinaryWriter::stream( ) const +{ + return stream_; +} + +inline E_Endian T_BinaryWriter::endian( ) const +{ + return endian_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_BinaryWriter::writeNumericNative( T value ) const +{ + static_assert( std::is_integral< T >( ) || std::is_floating_point< T >( ) , + "numeric type expected" ); + stream_.write( &value , sizeof( T ) ); +} + +template< typename T > +inline void T_BinaryWriter::writeNumericLittleEndian( T const& value ) const +{ + writeNumericNative( T_LittleEndian< T >::convert( value ) ); +} + +template< typename T > +inline void T_BinaryWriter::writeNumericBigEndian( T const& value ) const +{ + writeNumericNative( T_BigEndian< T >::convert( value ) ); +} + +template< typename T > +inline void T_BinaryWriter::write( T const& value ) const +{ + T_Writer_< T >::write( *this , value ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_BinaryWriter::T_Writer_< T , true >::write( T_BinaryWriter const& writer , T value ) +{ + if ( writer.endian_ == E_Endian::BIG ) { + writer.writeNumericBigEndian( value ); + } else { + writer.writeNumericLittleEndian( value ); + } +} + +template< typename T > +inline void T_BinaryWriter::T_Writer_< T , false >::write( T_BinaryWriter const& writer , T const& value ) +{ + typedef T_ObjectWriter< T > Writer; + Writer::write( writer , value ); +} + + +} // namespace diff --git a/include/ebcl/inline/Buffers.hh b/include/ebcl/inline/Buffers.hh new file mode 100644 index 0000000..d40328a --- /dev/null +++ b/include/ebcl/inline/Buffers.hh @@ -0,0 +1,263 @@ +/******************************************************************************/ +/* BUFFERS - INLINE CODE ******************************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= T_BufferBase =============================================================*/ + +inline T_BufferBase::T_BufferBase( uint8_t* buffer ) noexcept + : data_( buffer ) +{ } + +inline T_BufferBase::T_BufferBase( size_t size ) + : data_( ( uint8_t* ) malloc( size ) ) +{ + if ( size != 0 && data_ == nullptr ) { + throw std::bad_alloc( ); + } +} + +inline T_BufferBase::T_BufferBase( T_BufferBase&& source ) noexcept + : T_BufferBase( nullptr ) +{ + swap( *this , source ); +} + +inline T_BufferBase::~T_BufferBase( ) +{ + free( data_ ); +} + +/*----------------------------------------------------------------------------*/ + +inline void swap( T_BufferBase& lhs , T_BufferBase& rhs ) +{ + using std::swap; + swap( lhs.data_ , rhs.data_ ); +} + +/*----------------------------------------------------------------------------*/ + +inline uint8_t* T_BufferBase::data( ) const +{ + return data_; +} + + +/*= T_FixedBuffer ============================================================*/ + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >::T_FixedBuffer( ) + : T_BufferBase( BYTES_ ) +{ } + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >::T_FixedBuffer( + T_FixedBuffer< S , T >&& source ) noexcept + : T_BufferBase( std::move( source ) ) +{ } + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >::T_FixedBuffer( + T_FixedBuffer< S , T > const& source ) + : T_BufferBase( BYTES_ ) +{ + *this = source; +} + +/*----------------------------------------------------------------------------*/ + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >& T_FixedBuffer< S , T >::operator= ( T_FixedBuffer< S , T > const& other ) +{ + memcpy( data_ , other.data_ , BYTES_ ); + return *this; +} + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >& T_FixedBuffer< S , T >::operator= ( T_FixedBuffer< S , T >&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< size_t S , typename T > +inline void swap( T_FixedBuffer< S , T >& lhs , T_FixedBuffer< S , T >& rhs ) noexcept +{ + swap( ( T_BufferBase& ) lhs , ( T_BufferBase& ) rhs ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t S , typename T > +inline size_t T_FixedBuffer< S , T >::bytes( ) const +{ + return BYTES_; +} + +template< size_t S , typename T > +inline T& T_FixedBuffer< S , T >::operator[] ( size_t index ) +{ + assert( index < S ); + return *reinterpret_cast< T* >( data_ + index * sizeof( T ) ); +} + +template< size_t S , typename T > +inline T const& T_FixedBuffer< S , T >::operator[] ( size_t index ) const +{ + assert( index < S ); + return *reinterpret_cast< T* >( data_ + index * sizeof( T ) ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t S , typename T > +inline T_FixedBuffer< S , T >& T_FixedBuffer< S , T >::setAll( T const& value ) +{ + for ( size_t i = 0 ; i < S ; i ++ ) { + memcpy( &(*this)[ i ] , &value , sizeof( T ) ); + } + return *this; +} + + +/*= T_Buffer =================================================================*/ + +template< typename T > +inline T_Buffer< T >::T_Buffer( ) noexcept + : T_BufferBase( nullptr ) , size_( 0 ) +{ } + +template< typename T > +inline T_Buffer< T >::T_Buffer( size_t size ) + : T_BufferBase( size * sizeof( T ) ) , size_( size ) +{ } + +template< typename T > +inline T_Buffer< T >::T_Buffer( T const* data , size_t size ) + : T_BufferBase( size * sizeof( T ) ) , size_( size ) +{ + memcpy( data_ , data , bytes( ) ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Buffer< T >::T_Buffer( T_Buffer< T > const& other ) + : T_Buffer( reinterpret_cast< T const* >( other.data_ ) , other.size( ) ) +{ } + +template< typename T > +inline T_Buffer< T >::T_Buffer( T_Buffer< T >&& other ) noexcept + : T_Buffer( ) +{ + swap( *this , other ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Buffer< T >& T_Buffer< T >::operator= ( T_Buffer< T > const& other ) +{ + if ( size_ != other.size_ ) { + free( data_ ); + size_ = other.size_; + if ( size_ == 0 ) { + data_ = nullptr; + } else { + data_ = ( uint8_t* ) malloc( bytes( ) ); + if ( data_ == nullptr ) { + throw std::bad_alloc( ); + } + } + } + memcpy( data_ , other.data_ , bytes( ) ); + return *this; +} + +template< typename T > +inline T_Buffer< T >& T_Buffer< T >::operator= ( T_Buffer< T >&& other ) +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_Buffer< T >& lhs , T_Buffer< T >& rhs ) noexcept +{ + using std::swap; + swap( ( T_BufferBase& ) lhs , ( T_BufferBase& ) rhs ); + swap( lhs.size_ , rhs.size_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline size_t T_Buffer< T >::size( ) const +{ + return size_; +} + +template< typename T > +inline size_t T_Buffer< T >::bytes( ) const +{ + return size_ * sizeof( T ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T& T_Buffer< T >::operator[] ( size_t index ) +{ + assert( index < size_ ); + return *reinterpret_cast< T* >( data_ + index * sizeof( T ) ); +} + +template< typename T > +inline T const& T_Buffer< T >::operator[] ( size_t index ) const +{ + assert( index < size_ ); + return *reinterpret_cast< T* >( data_ + index * sizeof( T ) ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Buffer< T >& T_Buffer< T >::resize( size_t newSize ) +{ + if ( newSize != size_ ) { + size_ = newSize; + data_ = ( uint8_t* ) realloc( data_ , bytes( ) ); + if ( bytes( ) != 0 && data_ == nullptr ) { + throw std::bad_alloc( ); + } + } + return *this; +} + +template< typename T > +inline T_Buffer< T >& T_Buffer< T >::setAll( T const& value ) +{ + for ( size_t i = 0 ; i < size_ ; i ++ ) { + memcpy( &(*this)[ i ] , &value , sizeof( T ) ); + } + return *this; +} + +template< typename T > +inline T_Buffer< T >& T_Buffer< T >::copyAll( T const* data ) +{ + memcpy( data_ , data , bytes( ) ); + return *this; +} + + +} // namespace diff --git a/include/ebcl/inline/DynLib.hh b/include/ebcl/inline/DynLib.hh new file mode 100644 index 0000000..81d61da --- /dev/null +++ b/include/ebcl/inline/DynLib.hh @@ -0,0 +1,30 @@ +/******************************************************************************/ +/* DYNAMIC LIBRARIES - INLINE CODE ********************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_DYNLIB +#define _H_LW_LIB_INLINE_DYNLIB + +#include +namespace lw { + + +/*= T_DynLib =================================================================*/ + +template< typename T > +inline T* T_DynLib::getPointer( + char const* const name ) const noexcept +{ + return reinterpret_cast< T* >( getRawSymbol( name ) ); +} + +template< typename T > +inline std::function< T > T_DynLib::getFunction( + char const* const name ) const noexcept +{ + return std::function< T >{ (F_CFunc< T >) getRawSymbol( name ) }; +} + + +} +#endif // _H_LW_LIB_INLINE_DYNLIB diff --git a/include/ebcl/inline/Files.hh b/include/ebcl/inline/Files.hh new file mode 100644 index 0000000..e4fdf83 --- /dev/null +++ b/include/ebcl/inline/Files.hh @@ -0,0 +1,99 @@ +/******************************************************************************/ +/* FILES - INLINE CODE ********************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_FILES +#define _H_LW_LIB_INLINE_FILES +#include +namespace lw { + + +/*= T_File ===================================================================*/ + +inline T_File::~T_File( ) +{ + close( ); +} + +/*---------------------------------------------------------------------------*/ + +inline void swap( T_File& lhs , T_File& rhs ) noexcept +{ + using std::swap; + swap( lhs.path_ , rhs.path_ ); + swap( lhs.mode_ , rhs.mode_ ); + swap( lhs.file_ , rhs.file_ ); + swap( lhs.size_ , rhs.size_ ); + swap( lhs.pos_ , rhs.pos_ ); +} + +/*---------------------------------------------------------------------------*/ + +inline T_String const& T_File::path( ) const noexcept +{ + return path_; +} + +inline E_FileMode T_File::mode( ) const noexcept +{ + return mode_; +} + +inline bool T_File::isOpen( ) const noexcept +{ + return file_ != nullptr; +} + +inline size_t T_File::size( ) const noexcept +{ + return isOpen( ) ? size_ : 0; +} + +inline size_t T_File::position( ) const noexcept +{ + return isOpen( ) ? pos_ : 0; +} + + +/*= T_FileInputStream ========================================================*/ + +inline void swap( T_FileInputStream& lhs , T_FileInputStream& rhs ) noexcept +{ + lhs.swap( rhs ); +} + +/*---------------------------------------------------------------------------*/ + +inline T_File& T_FileInputStream::file( ) const noexcept +{ + return fileRaw_ ? *fileRaw_ : *fileOwned_; +} + +inline size_t T_FileInputStream::offset( ) const noexcept +{ + return start_; +} + + +/*= T_FileOutputStream =======================================================*/ + +inline void swap( T_FileOutputStream& lhs , T_FileOutputStream& rhs ) noexcept +{ + lhs.swap( rhs ); +} + +/*---------------------------------------------------------------------------*/ + +inline T_File& T_FileOutputStream::file( ) const noexcept +{ + return fileRaw_ ? *fileRaw_ : *fileOwned_; +} + +inline size_t T_FileOutputStream::offset( ) const noexcept +{ + return start_; +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_FILES diff --git a/include/ebcl/inline/HashIndex.hh b/include/ebcl/inline/HashIndex.hh new file mode 100644 index 0000000..a9f2211 --- /dev/null +++ b/include/ebcl/inline/HashIndex.hh @@ -0,0 +1,29 @@ +/******************************************************************************/ +/* HASH INDEX - INLINE CODE ***************************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= T_HashIndex ==============================================================*/ + +inline T_HashIndex::~T_HashIndex( ) +{ + free( ); +} + +inline uint32_t T_HashIndex::first( uint32_t key ) const +{ + return hash_[ key & hashMask_ & lookupMask_ ]; +} + +inline uint32_t T_HashIndex::next( uint32_t index ) const +{ + assert( index < indexSize_ ); + return index_[ index & lookupMask_ ]; +} + + +} diff --git a/include/ebcl/inline/HashTables.hh b/include/ebcl/inline/HashTables.hh new file mode 100644 index 0000000..fb8fef7 --- /dev/null +++ b/include/ebcl/inline/HashTables.hh @@ -0,0 +1,421 @@ +/******************************************************************************/ +/* HASH TABLES - INLINE CODE **************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_HASHTABLES +#define _H_LW_LIB_INLINE_HASHTABLES +#include +namespace lw { + + +/*= T_DefaultKeyMatch ========================================================*/ + +template< typename K > +inline bool T_DefaultKeyMatch< K >::keysMatch( K const& a , K const& b ) +{ + return a == b; +} + + +/*= T_KeyValueTable ==========================================================*/ + +template< typename K , typename V > +inline T_KeyValueTable< K , V >::T_KeyValueTable( + uint32_t initialSize , uint32_t hashSize , uint32_t growth , + F_KeyMatch< K > match ) + : match_( match ) , index_( hashSize , initialSize , growth ) , + keys_( growth ) , values_( growth ) +{ } + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename T > +void swap( T_KeyValueTable< K , T >& lhs , T_KeyValueTable< K , T >& rhs ) +{ + using std::swap; + swap( lhs.match_ , rhs.match_ ); + swap( lhs.index_ , rhs.index_ ); + swap( lhs.keys_ , rhs.keys_ ); + swap( lhs.values_ , rhs.values_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +template< + typename A , typename B , + T_EnableIfTypesMatch< K , A > , + T_EnableIfTypesMatch< V , B > +> inline bool T_KeyValueTable< K , V >::add( + A&& k , + B&& v ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx != T_HashIndex::INVALID_INDEX ) { + return false; + } + keys_.add( std::forward< A >( k ) ); + values_.add( std::forward< B >( v ) ); + index_.add( hash ); + return true; +} + +template< typename K , typename V > +template< typename B , T_EnableIfTypesMatch< V , B > > +inline bool T_KeyValueTable< K , V >::update( + K const& k , + B&& v ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + values_[ idx ] = std::forward< B >( v ); + return true; +} + +template< typename K , typename V > +template< + typename A , typename B , + T_EnableIfTypesMatch< K , A > , + T_EnableIfTypesMatch< V , B > +> inline void T_KeyValueTable< K , V >::set( + A&& k , + B&& v ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + keys_.add( std::forward< A >( k ) ); + values_.add( std::forward< B >( v ) ); + index_.add( hash ); + } else { + values_[ idx ] = std::forward< B >( v ); + } +} + +template< typename K , typename V > +inline bool T_KeyValueTable< K , V >::remove( K const& k ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + keys_.removeSwap( idx ); + values_.removeSwap( idx ); + index_.remove( idx ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline void T_KeyValueTable< K , V >::clear( ) +{ + values_.clear( ); + keys_.clear( ); + index_.clear( ); +} + +template< typename K , typename V > +inline void T_KeyValueTable< K , V >::free( ) +{ + values_.free( ); + keys_.free( ); + index_.free( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline uint32_t T_KeyValueTable< K , V >::size( ) const +{ + return keys_.size( ); +} + +template< typename K , typename V > +T_Array< K > const& T_KeyValueTable< K , V >::keys( ) const +{ + return keys_; +} + +template< typename K , typename V > +T_Array< V > const& T_KeyValueTable< K , V >::values( ) const +{ + return values_; +} + +template< typename K , typename V > +inline uint32_t T_KeyValueTable< K , V >::indexOf( K const& k ) const +{ + return find( k , ComputeHash( k ) ); +} + +template< typename K , typename V > +inline bool T_KeyValueTable< K , V >::contains( K const& k ) const +{ + const uint32_t hash = ComputeHash( k ); + return find( k , hash ) != T_HashIndex::INVALID_INDEX; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline V const* T_KeyValueTable< K , V >::get( K const& k ) const +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } + return &values_[ idx ]; +} + +template< typename K , typename V > +inline V* T_KeyValueTable< K , V >::get( K const& k ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } + return &values_[ idx ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline V& T_KeyValueTable< K , V >::operator[] ( uint32_t index ) +{ + return values_[ index ]; +} + +template< typename K , typename V > +inline V const& T_KeyValueTable< K , V >::operator[] ( uint32_t index ) const +{ + return values_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline uint32_t T_KeyValueTable< K , V >::find( K const& k , uint32_t hash ) const +{ + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( match_( keys_[ idx ] , k ) ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + + +/*= T_ObjectTable ============================================================*/ + +template< typename K , typename V > +inline T_ObjectTable< K , V >::T_ObjectTable( + T_ObjectTable< K , V >::F_GetKey keyGetter , + uint32_t initialSize , uint32_t hashSize , uint32_t growth , + T_ObjectTable< K , V >::F_Match match ) + : match_( match ) , keyGetter_( keyGetter ) , + index_( hashSize , initialSize , growth ) , + values_( growth ) +{ } + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename T > +void swap( T_ObjectTable< K , T >& lhs , T_ObjectTable< K , T >& rhs ) +{ + using std::swap; + swap( lhs.keyGetter_ , rhs.keyGetter_ ); + swap( lhs.match_ , rhs.match_ ); + swap( lhs.index_ , rhs.index_ ); + swap( lhs.values_ , rhs.values_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +template< typename A , T_EnableIfTypesMatch< V , A > > +inline bool T_ObjectTable< K , V >::add( + A&& v ) +{ + K k = keyGetter_( v ); + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx != T_HashIndex::INVALID_INDEX ) { + return false; + } + values_.add( std::forward< A >( v ) ); + index_.add( hash ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +template< typename A , T_EnableIfTypesMatch< V , A > > +inline bool T_ObjectTable< K , V >::update( + A&& v ) +{ + K k = keyGetter_( v ); + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + values_[ idx ] = std::forward< A >( v ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +template< typename A , T_EnableIfTypesMatch< V , A > > +inline void T_ObjectTable< K , V >::set( + A&& v ) +{ + K k = keyGetter_( v ); + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + values_.add( std::forward< A >( v ) ); + index_.add( hash ); + } else { + values_[ idx ] = std::forward< A >( v ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline bool T_ObjectTable< K , V >::remove( K const& k ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + values_.removeSwap( idx ); + index_.remove( idx ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline void T_ObjectTable< K , V >::clear( ) +{ + values_.clear( ); + index_.clear( ); +} + +template< typename K , typename V > +inline void T_ObjectTable< K , V >::free( ) +{ + values_.free( ); + index_.free( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline uint32_t T_ObjectTable< K , V >::size( ) const +{ + return values_.size( ); +} + +template< typename K , typename V > +inline uint32_t T_ObjectTable< K , V >::indexOf( K const& k ) const +{ + return find( k , ComputeHash( k ) ); +} + +template< typename K , typename V > +inline bool T_ObjectTable< K , V >::contains( K const& k ) const +{ + const uint32_t hash = ComputeHash( k ); + return find( k , hash ) != T_HashIndex::INVALID_INDEX; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline T_Array< K > T_ObjectTable< K , V >::keys( ) const +{ + const auto sz( size( ) ); + T_Array< K > k( sz ? sz : 1 ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + k.add( keyGetter_( values_[ i ] ) ); + } + return k; +} + +template< typename K , typename V > +inline T_Array< V > const& T_ObjectTable< K , V >::values( ) const +{ + return values_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline V const* T_ObjectTable< K , V >::get( K const& k ) const +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } + return &values_[ idx ]; +} + +template< typename K , typename V > +inline V* T_ObjectTable< K , V >::get( K const& k ) +{ + const uint32_t hash = ComputeHash( k ); + uint32_t idx = find( k , hash ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } + return &values_[ idx ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline V& T_ObjectTable< K , V >::operator[] ( uint32_t index ) +{ + return values_[ index ]; +} + +template< typename K , typename V > +inline V const& T_ObjectTable< K , V >::operator[] ( uint32_t index ) const +{ + return values_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +template< typename K , typename V > +inline uint32_t T_ObjectTable< K , V >::find( K const& k , uint32_t hash ) const +{ + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( match_( keyGetter_( values_[ idx ] ) , k ) ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_HASHTABLES diff --git a/include/ebcl/inline/MemoryStreams.hh b/include/ebcl/inline/MemoryStreams.hh new file mode 100644 index 0000000..f6b1adc --- /dev/null +++ b/include/ebcl/inline/MemoryStreams.hh @@ -0,0 +1,43 @@ +/******************************************************************************/ +/* MEMORY STREAMS - INLINE CODE ***********************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= T_MemoryInputStream ======================================================*/ + +template< int S , typename T > +inline T_MemoryInputStream::T_MemoryInputStream( T_FixedBuffer< S , T > const& buffer ) + : T_MemoryInputStream( buffer.data( ) , buffer.bytes( ) ) +{ } + +template< typename T > +inline T_MemoryInputStream::T_MemoryInputStream( T_Buffer< T > const& buffer ) + : T_MemoryInputStream( buffer.data( ) , buffer.bytes( ) ) +{ } + + +/*= T_MemoryOutputStream =====================================================*/ + +template< int S , typename T > +inline T_MemoryOutputStream::T_MemoryOutputStream( T_FixedBuffer< S , T >& buffer ) + : T_MemoryOutputStream( buffer.data( ) , buffer.bytes( ) ) +{ } + +template< typename T > +inline T_MemoryOutputStream::T_MemoryOutputStream( T_Buffer< T >& buffer ) + : T_MemoryOutputStream( buffer.data( ) , buffer.bytes( ) , + [&buffer] ( uint8_t* , size_t reqSize ) -> uint8_t* + { + const size_t mod( reqSize % sizeof( T ) ); + const size_t nItems( reqSize / sizeof( T ) + ( mod ? 1 : 0 ) ); + buffer.resize( nItems ); + return buffer.data( ); + } ) +{ } + + +} // namespace diff --git a/include/ebcl/inline/Messages.hh b/include/ebcl/inline/Messages.hh new file mode 100644 index 0000000..a77a05f --- /dev/null +++ b/include/ebcl/inline/Messages.hh @@ -0,0 +1,154 @@ +/******************************************************************************/ +/* UI<=>GAME MESSAGES - INLINE CODE *******************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_MESSAGES +#define _H_LW_LIB_INLINE_MESSAGES +#include +namespace lw { + + +/*= T_ProgressInfoPart =======================================================*/ + +inline T_ProgressInfoPart::T_ProgressInfoPart( T_String text , + uint32_t progress , uint32_t total ) noexcept + : text_( std::move( text ) ) , progress_( progress ) , total_( total ) +{ } + +inline T_ProgressInfoPart::T_ProgressInfoPart( T_ProgressInfoPart&& other ) noexcept + : text_( std::move( other.text_ ) ) , progress_( other.progress_ ) , + total_( other.total_ ) +{ } + +inline T_ProgressInfoPart::T_ProgressInfoPart( T_ProgressInfoPart const& other ) + : text_( other.text_ ) , progress_( other.progress_ ) , total_( other.total_ ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_ProgressInfoPart::text( ) const noexcept +{ + return text_; +} + +inline uint32_t T_ProgressInfoPart::progress( ) const noexcept +{ + return progress_; +} + +inline uint32_t T_ProgressInfoPart::total( ) const noexcept +{ + return total_; +} + + +/*= T_ProgressInfo ===========================================================*/ + +inline T_ProgressInfo::T_ProgressInfo( T_String text , uint32_t progress , uint32_t total ) noexcept + : main_( std::move( text ) , progress , total ) , sub_( ) +{ } + +inline T_ProgressInfo::T_ProgressInfo( T_ProgressInfoPart main ) noexcept + : main_( std::move( main ) ) +{ } + +inline T_ProgressInfo::T_ProgressInfo( + T_ProgressInfoPart main , + T_ProgressInfoPart sub ) noexcept + : main_( std::move( main ) ) , + sub_( std::move( sub ) ) +{ } + +inline T_ProgressInfo::T_ProgressInfo( T_ProgressInfoPart main , + T_String sText , uint32_t sProgress , uint32_t sTotal ) noexcept + : main_( std::move( main ) ) , + sub_( T_ProgressInfoPart{ std::move( sText ) , sProgress , sTotal } ) +{ } + +inline T_ProgressInfo::T_ProgressInfo( T_String text , uint32_t progress , uint32_t total , + T_String sText , uint32_t sProgress , uint32_t sTotal ) noexcept + : main_( std::move( text ) , progress , total ) , + sub_( T_ProgressInfoPart{ std::move( sText ) , sProgress , sTotal } ) +{ } + +inline T_ProgressInfo::T_ProgressInfo( T_ProgressInfo&& other ) noexcept + : main_( std::move( other.main_ ) ) , sub_( std::move( other.sub_ ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_ProgressInfoPart const& T_ProgressInfo::main( ) const noexcept +{ + return main_; +} + +inline bool T_ProgressInfo::hasSub( ) const noexcept +{ + return sub_.present( ); +} + +inline T_ProgressInfoPart const& T_ProgressInfo::sub( ) const +{ + return (T_ProgressInfoPart const&) sub_; +} + + +/*= T_GameMessage ============================================================*/ + +template< E_MessageDirection D , typename MT , typename MD > +inline constexpr T_GameMessage< D , MT , MD >::T_GameMessage( ) noexcept + : type_( ) , data_( ) +{ } + +template< E_MessageDirection D , typename MT , typename MD > +inline constexpr T_GameMessage< D , MT , MD >::T_GameMessage( + T_Type type ) noexcept + : type_( type ) , data_( ) +{ } + +template< E_MessageDirection D , typename MT , typename MD > +inline T_GameMessage< D , MT , MD >::T_GameMessage( + T_Type type , + T_Data data ) noexcept + : type_( type ) , data_( data ) +{ } + +template< E_MessageDirection D , typename MT , typename MD > +inline T_GameMessage< D , MT , MD >::T_GameMessage( + T_Self_&& other ) noexcept + : type_( other.type_ ) , data_( std::move( other.data_ ) ) +{ } + +template< E_MessageDirection D , typename MT , typename MD > +inline T_GameMessage< D >& T_GameMessage< D , MT , MD >::operator =( + T_Self_&& other ) noexcept +{ + type_ = other.type_; + data_ = std::move( other.data_ ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< E_MessageDirection D , typename MT , typename MD > +inline constexpr bool T_GameMessage< D , MT , MD >::hasMessage( ) const noexcept +{ + return type_.present( ); +} + +template< E_MessageDirection D , typename MT , typename MD > +inline constexpr MT T_GameMessage< D , MT , MD >::type( ) const noexcept +{ + return type_; +} + +template< E_MessageDirection D , typename MT , typename MD > +template< typename T > +constexpr T const& T_GameMessage< D , MT , MD >::data( ) const +{ + return (T const&)( (MD const&) data_ ); +} + + +} // namespace lw +#endif // _H_LW_LIB_INLINE_MESSAGES diff --git a/include/ebcl/inline/Mods.hh b/include/ebcl/inline/Mods.hh new file mode 100644 index 0000000..2e16087 --- /dev/null +++ b/include/ebcl/inline/Mods.hh @@ -0,0 +1,59 @@ +/******************************************************************************/ +/* MODDING SYSTEM - INLINE CODE ***********************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_MODS +#define _H_LW_LIB_INLINE_MODS +#include +namespace lw { + + +/*= T_ModIdentifier ==========================================================*/ + +inline bool T_ModIdentifier::operator ==( + T_ModIdentifier const& other ) const noexcept +{ + return &other == this || ( name == other.name + && version == other.version ); +} + +inline bool T_ModIdentifier::operator !=( + T_ModIdentifier const& other ) const noexcept +{ + return &other != this && ( name != other.name + || version != other.version ); +} + +inline M_DEFINE_HASH( T_ModIdentifier ) +{ + return ComputeHash( item.name ) * 47 + item.version; +} + +inline M_DEFINE_COMPARATOR( T_ModIdentifier ) +{ + return a.compare( b ); +} + +inline M_LSHIFT_OP( T_StringBuilder , T_ModIdentifier const& ) +{ + obj << value.name << ':' << value.version; + return obj; +} + + +/*= T_ModInfo ================================================================*/ + +inline bool T_ModInfo::isUserInterface( ) const noexcept +{ + return type == E_ModType::UI; +} + +inline M_LSHIFT_OP( T_StringBuilder , T_ModInfo const& ) +{ + obj << value.identifier << '.' << value.revision; + return obj; +} + + +} +#endif // _H_LW_LIB_INLINE_MODS diff --git a/include/ebcl/inline/Pointers.hh b/include/ebcl/inline/Pointers.hh new file mode 100644 index 0000000..4aaeb27 --- /dev/null +++ b/include/ebcl/inline/Pointers.hh @@ -0,0 +1,854 @@ +/******************************************************************************/ +/* POINTERS - INLINE CODE *****************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_POINTERS +#define _H_LW_LIB_INLINE_POINTERS +#include +namespace lw { + + +/*= T_OwnPtr =================================================================*/ + +template< typename T > +inline T_OwnPtr< T >::T_OwnPtr( T* p ) noexcept + : p_( p ) +{ } + +template< typename T > +inline T_OwnPtr< T >::T_OwnPtr( ) + : T_OwnPtr( nullptr ) +{ } + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_OwnPtr< T >::T_OwnPtr( T_OwnPtr< Q >&& source ) noexcept + : p_( nullptr ) +{ + T* temp( source.p_ ); + source.p_ = nullptr; + p_ = temp; +} + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_OwnPtr< T >::T_OwnPtr( T_OwnPtr< Q >&& source ) + : p_( nullptr ) +{ + T* temp( dynamic_cast< T* >( source.p_ ) ); + if ( source.p_ && !temp ) { + throw std::bad_cast( ); + } + source.p_ = nullptr; + p_ = temp; +} + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_OwnPtr< T >& T_OwnPtr< T >::operator= ( T_OwnPtr< Q >&& source ) noexcept +{ + T* temp( source.p_ ); + clear( ); + source.p_ = nullptr; + p_ = temp; + return *this; +} + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_OwnPtr< T >& T_OwnPtr< T >::operator= ( + T_OwnPtr< Q >&& source ) +{ + T* temp( dynamic_cast< T* >( source.p_ ) ); + if ( source.p_ && !temp ) { + throw std::bad_cast( ); + } + clear( ); + source.p_ = nullptr; + p_ = temp; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_OwnPtr< T >::~T_OwnPtr( ) +{ + clear( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_OwnPtr< T >& lhs , T_OwnPtr< T >& rhs ) noexcept +{ + std::swap( lhs.p_ , rhs.p_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_OwnPtr< T >::clear( ) +{ + T* ptr( nullptr ); + std::swap( p_ , ptr ); + delete ptr; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline bool T_OwnPtr< T >::operator== ( const T* p ) const +{ + return p == p_; +} + +template< typename T > +inline bool T_OwnPtr< T >::operator!= ( const T* p ) const +{ + return p != p_; +} + +template< typename T > +inline T_OwnPtr< T >::operator bool ( ) const +{ + return p_ != nullptr; +} + +template< typename T > +inline bool T_OwnPtr< T >::operator! ( ) const +{ + return p_ == nullptr; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T* T_OwnPtr< T >::get( ) const +{ + return p_; +} + +template< typename T > +inline T* T_OwnPtr< T >::operator-> ( ) const +{ + assert( p_ != nullptr ); + return p_; +} + +template< typename T > +inline T& T_OwnPtr< T >::operator* ( ) const +{ + assert( p_ != nullptr ); + return *p_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_SharedPtr< T > T_OwnPtr< T >::makeShared( ) +{ + T* temp( nullptr ); + std::swap( p_ , temp ); + return T_SharedPtr< T >( temp ); +} + + +/*= NewOwned / OwnRawPointer =================================================*/ + +template< + typename Type , + typename... ArgTypes + > +inline T_OwnPtr< Type > NewOwned( ArgTypes&& ... arguments ) +{ + return T_OwnPtr< Type >( new Type( std::forward< ArgTypes >( arguments ) ... ) ); +} + +template< typename Type > +inline T_OwnPtr< Type > OwnRawPointer( Type*& pointer ) noexcept +{ + assert( pointer != nullptr ); + T_OwnPtr< Type > p( pointer ); + pointer = nullptr; + return p; +} + +template< + typename Type , typename Other , + T_EnableForChild< Type , Other > +> inline T_OwnPtr< Type > OwnRawPointer( Other*& pointer ) noexcept +{ + assert( pointer != nullptr ); + T_OwnPtr< Type > p( pointer ); + pointer = nullptr; + return p; +} + +template< + typename Type , typename Other , + T_EnableForParent< Type , Other > +> inline T_OwnPtr< Type > OwnRawPointer( Other*& pointer ) +{ + assert( pointer != nullptr ); + Type* temp( dynamic_cast< Type* >( pointer ) ); + if ( temp == nullptr ) { + throw std::bad_cast( ); + } + T_OwnPtr< Type > p( temp ); + pointer = nullptr; + return p; +} + + +/*= T_Reference_ =============================================================*/ + +// Reference counter for shared pointers +class T_Reference_ +{ + private: + typedef std::function< void ( void* ) > F_Destr_; + + friend struct T_WeakChain_; + + void* pointer_; + F_Destr_ destr_; + uint32_t count_; + T_WeakChain_* weaks_; + + public: + /* References are pooled */ + void* operator new( size_t count ) noexcept; + void operator delete( void* object ) noexcept; + + T_Reference_( ) = delete; + T_Reference_( T_Reference_ const& ) = delete; + T_Reference_( T_Reference_&& ) = delete; + + T_Reference_( void* ptr , F_Destr_ destructor ); + + ~T_Reference_( ); + + void* pointer( ) const; + T_Reference_ * increase( ); + void decrease( ); + void* extract( ); +}; + + +/*----------------------------------------------------------------------------*/ + +inline void* T_Reference_::pointer( ) const +{ + return pointer_; +} + +inline T_Reference_* T_Reference_::increase( ) +{ + assert( count_ > 0 ); + count_ ++; + return this; +} + +inline void T_Reference_::decrease( ) +{ + assert( count_ > 0 ); + count_ --; + if ( count_ == 0 ) { + delete this; + } +} + + +/*= T_BasePtr_ ===============================================================*/ + +template< typename T > +inline T_BasePtr_< T >::T_BasePtr_( T_Reference_* ref ) noexcept + : ref_( ref ) +{ } + +template< typename T > +inline T_BasePtr_< T >::operator bool ( ) const +{ + return ref_ != nullptr; +} + +template< typename T > +inline bool T_BasePtr_< T >::operator! ( ) const +{ + return ref_ == nullptr; +} + +template< typename T > +inline T* T_BasePtr_< T >::operator-> ( ) const +{ + assert( ref_ != nullptr ); + return reinterpret_cast< T* >( ref_->pointer( ) ); +} + +template< typename T > +inline T_BasePtr_< T >::operator T* ( ) const +{ + return ref_ == nullptr + ? nullptr + : reinterpret_cast< T* >( ref_->pointer( ) ); +} + +template< typename T > +inline T& T_BasePtr_< T >::operator* ( ) const +{ + assert( ref_ != nullptr ); + return *reinterpret_cast< T* >( ref_->pointer( ) ); +} + + +/*= X_TooManyReferences ======================================================*/ + +inline X_TooManyReferences::X_TooManyReferences( ) + : std::runtime_error( "too many references" ) +{ } + +/*= T_SharedPtr ==============================================================*/ + +template< typename T > +inline T_Reference_* T_SharedPtr< T >::setRef( T_Reference_* from ) +{ + if ( from == nullptr ) { + return nullptr; + } + return from->increase( ); +} + +template< typename T > +inline void T_SharedPtr< T >::clearRef( ) +{ + if ( ref_ != nullptr ) { + ref_->decrease( ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_SharedPtr< T >::T_SharedPtr( T* ptr ) + : T_Base_( new T_Reference_( ptr , + []( void* p ) { + delete reinterpret_cast< T* >( p ); + } ) ) +{ } + +template< typename T > +inline T_SharedPtr< T >::T_SharedPtr( ) noexcept + : T_Base_( nullptr ) +{ } + +template< typename T > +inline T_SharedPtr< T >::~T_SharedPtr( ) +{ + clearRef( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_SharedPtr< T >::T_SharedPtr( T_Self_ const& source ) + : T_Base_( setRef( source.ref_ ) ) +{ } + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_SharedPtr< T >::T_SharedPtr( T_SharedPtr< Q > const& other ) + : T_Base_( setRef( other.ref_ ) ) +{ } + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_SharedPtr< T >::T_SharedPtr( T_SharedPtr< Q > const& other ) + : T_Base_( nullptr ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + ref_ = setRef( other.ref_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_SharedPtr< T >::T_SharedPtr( T_SharedPtr< Q >&& other ) noexcept + : T_Base_( other.ref_ ) +{ + other.ref_ = nullptr; +} + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_SharedPtr< T >::T_SharedPtr( T_SharedPtr< Q >&& other ) + : T_Base_( nullptr ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + using std::swap; + swap( ref_ , other.ref_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_SharedPtr< T >& T_SharedPtr< T >::operator= ( T_SharedPtr< T > const& other ) +{ + if ( &other != this && other.ref_ != ref_ ) { + clearRef( ); + ref_ = setRef( other.ref_ ); + } + return *this; +} + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_SharedPtr< T >& T_SharedPtr< T >::operator= ( T_SharedPtr< Q > const& other ) +{ + if ( other.ref_ != ref_ ) { + clearRef( ); + ref_ = setRef( other.ref_ ); + } + return *this; +} + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_SharedPtr< T >& T_SharedPtr< T >::operator= ( T_SharedPtr< Q > const& other ) +{ + if ( other.ref_ != ref_ ) { + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + clearRef( ); + ref_ = setRef( other.ref_ ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< + typename Q , + T_EnableForChild< T , Q > +> inline T_SharedPtr< T >& T_SharedPtr< T >::operator= ( T_SharedPtr< Q >&& other ) noexcept +{ + if ( other.ref_ != ref_ ) { + clearRef( ); + ref_ = nullptr; + std::swap( ref_ , other.ref_ ); + } + return *this; +} + +template< typename T > +template< + typename Q , + T_EnableForParent< T , Q > +> inline T_SharedPtr< T >& T_SharedPtr< T >::operator= ( T_SharedPtr< Q >&& other ) +{ + if ( other.ref_ != ref_ ) { + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + clearRef( ); + ref_ = nullptr; + std::swap( ref_ , other.ref_ ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_SharedPtr< T >& lhs , T_SharedPtr< T >& rhs ) noexcept +{ + std::swap( lhs.ref_ , rhs.ref_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline bool T_SharedPtr< T >::operator== ( T_SharedPtr< T > const& other ) const +{ + return other.ref_ == ref_; +} + +template< typename T > +inline bool T_SharedPtr< T >::operator!= ( T_SharedPtr< T > const& other ) const +{ + return other.ref_ != ref_; +} + +template< typename T > +inline bool T_SharedPtr< T >::operator== ( T_WeakPtr< T > const& other ) const +{ + return other.ref_ == ref_; +} + +template< typename T > +inline bool T_SharedPtr< T >::operator!= ( T_WeakPtr< T > const& other ) const +{ + return other.ref_ != ref_; +} + +template< typename T > +inline void T_SharedPtr< T >::clear( ) +{ + clearRef( ); + ref_ = nullptr; +} + +template< typename T > +T_OwnPtr< T > T_SharedPtr< T >::makeOwned( ) +{ + if ( ref_ == nullptr ) { + return T_OwnPtr< T >( ); + } + void* const p( ref_->extract( ) ); + ref_ = nullptr; + return T_OwnPtr< T >( reinterpret_cast< T* >( p ) ); +} + + +/*= T_WeakPtr ================================================================*/ + +template< typename T > +inline T_WeakPtr< T >::T_WeakPtr( T_Reference_* ref ) + : T_BasePtr_< T >( ref ) , chain_( ref_ ) +{ + chain_.init( ); +} + +template< typename T > +inline T_WeakPtr< T >::T_WeakPtr( ) + : T_WeakPtr( nullptr ) +{ } + +template< typename T > +inline T_WeakPtr< T >::~T_WeakPtr( ) +{ + chain_.unchain( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_WeakPtr< T >::T_WeakPtr( T_WeakPtr< T > const& other ) + : T_WeakPtr( other.ref_ ) +{ } + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_WeakPtr< Q > const& other ) + : T_WeakPtr( other.ref_ ) +{ } + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_WeakPtr< Q > const& other ) + : T_WeakPtr( ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + ref_ = other.ref_; + chain_.init( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_WeakPtr< Q >&& other ) noexcept + : T_WeakPtr( nullptr ) +{ + other.chain_.unchain( ); + ref_ = other.ref_; + other.ref_ = nullptr; + chain_.init( ); +} + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_WeakPtr< Q >&& other ) + : T_WeakPtr( nullptr ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + other.chain_.unchain( ); + ref_ = other.ref_; + other.ref_ = nullptr; + chain_.init( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_WeakPtr< T >::T_WeakPtr( T_SharedPtr< T > const& shared ) + : T_WeakPtr( shared.ref_ ) +{ } + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_SharedPtr< Q > const& shared ) + : T_WeakPtr( shared.ref_ ) +{ } + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >::T_WeakPtr( T_SharedPtr< Q > const& shared ) + : T_WeakPtr( nullptr ) +{ + Q* const p( shared ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + ref_ = shared.ref_; + chain_.init( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_WeakPtr< T >& lhs , T_WeakPtr< T >& rhs ) +{ + if ( lhs.ref_ != rhs.ref_ ) { + lhs.chain_.unchain( ); + rhs.chain_.unchain( ); + std::swap( lhs.ref_ , rhs.ref_ ); + lhs.chain_.init( ); + rhs.chain_.init( ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_WeakPtr< T > const& other ) +{ + chain_.unchain( ); + ref_ = other.ref_; + chain_.init( ); + return *this; +} + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_WeakPtr< Q > const& other ) +{ + chain_.unchain( ); + ref_ = other.ref_; + chain_.init( ); + return *this; +} + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_WeakPtr< Q > const& other ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + chain_.unchain( ); + ref_ = other.ref_; + chain_.init( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_WeakPtr< Q >&& other ) +{ + ref_ = nullptr; + chain_.unchain( ); + if ( other.ref_ ) { + other.chain_.unchain( ); + ref_ = other.ref_; + other.ref_ = nullptr; + chain_.init( ); + other.chain_.init( ); + } + return *this; +} + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_WeakPtr< Q >&& other ) +{ + Q* const p( other ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + ref_ = nullptr; + chain_.unchain( ); + if ( other.ref_ ) { + other.chain_.unchain( ); + ref_ = other.ref_; + other.ref_ = nullptr; + chain_.init( ); + other.chain_.init( ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_SharedPtr< T > const& shared ) +{ + chain_.unchain( ); + ref_ = shared.ref_; + chain_.init( ); + return *this; +} + +template< typename T > +template< typename Q , T_EnableForChild< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_SharedPtr< Q > const& shared ) +{ + chain_.unchain( ); + ref_ = shared.ref_; + chain_.init( ); + return *this; +} + +template< typename T > +template< typename Q , T_EnableForParent< T , Q > > +inline T_WeakPtr< T >& T_WeakPtr< T >::operator= ( T_SharedPtr< Q > const& shared ) +{ + Q* const p( shared ); + if ( p != nullptr && dynamic_cast< T* >( p ) == nullptr ) { + throw std::bad_cast( ); + } + chain_.unchain( ); + ref_ = shared.ref_; + chain_.init( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline bool T_WeakPtr< T >::operator== ( T_WeakPtr< T > const& other ) const +{ + return other.ref_ == ref_; +} + +template< typename T > +inline bool T_WeakPtr< T >::operator!= ( T_WeakPtr< T > const& other ) const +{ + return other.ref_ != ref_; +} + +template< typename T > +inline bool T_WeakPtr< T >::operator== ( T_SharedPtr< T > const& other ) const +{ + return other.ref_ == ref_; +} + +template< typename T > +inline bool T_WeakPtr< T >::operator!= ( T_SharedPtr< T > const& other ) const +{ + return other.ref_ != ref_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_WeakPtr< T >::clear( ) +{ + chain_.unchain( ); + ref_ = nullptr; +} + + +/*= NewShared ================================================================*/ + +template< + typename Type , + typename... ArgTypes +> inline T_SharedPtr< Type > NewShared( ArgTypes&& ... arguments ) +{ + return T_SharedPtr< Type >( new Type( std::forward< ArgTypes >( arguments ) ... ) ); +} + +template< typename Type > +T_SharedPtr< Type > ShareRawPointer( + Type*& pointer ) noexcept +{ + assert( pointer != nullptr ); + T_SharedPtr< Type > p( pointer ); + pointer = nullptr; + return p; +} + +template< + typename Type , typename Other , + T_EnableForChild< Type , Other > +> inline T_SharedPtr< Type > ShareRawPointer( + Other*& pointer ) noexcept +{ + assert( pointer != nullptr ); + T_SharedPtr< Type > p( pointer ); + pointer = nullptr; + return p; +} + +template< + typename Type , typename Other , + T_EnableForParent< Type , Other > +> inline T_SharedPtr< Type > ShareRawPointer( + Other*& pointer ) +{ + assert( pointer != nullptr ); + Type* temp( dynamic_cast< Type* >( pointer ) ); + if ( temp == nullptr ) { + throw std::bad_cast( ); + } + T_SharedPtr< Type > p( temp ); + pointer = nullptr; + return p; +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_POINTERS diff --git a/include/ebcl/inline/Registration.hh b/include/ebcl/inline/Registration.hh new file mode 100644 index 0000000..59b0c87 --- /dev/null +++ b/include/ebcl/inline/Registration.hh @@ -0,0 +1,124 @@ +/******************************************************************************/ +/* REGISTRATION SUPPORT - INLINE CODE *****************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_REGISTRATION +#define _H_LW_LIB_INLINE_REGISTRATION +#include +namespace lw { + + +/*= T_RegisteredItem ============================================================*/ + +inline T_RegisteredItem::T_RegisteredItem( ) noexcept + : automatic_( false ) , unregisterFunction_( ) , + data_( nullptr ) , dataDestructor_( ) +{ } + +inline T_RegisteredItem::T_RegisteredItem( + SP_Unregister& unregisterFunction , + void* data , + F_Destructor_ destructor ) noexcept + : automatic_( true ) , unregisterFunction_( unregisterFunction ) , + data_( data ) , dataDestructor_( destructor ) +{ } + +template< typename T > +inline T_RegisteredItem::T_RegisteredItem( + SP_Unregister& unregisterFunction , + T* data ) noexcept + : T_RegisteredItem( unregisterFunction , data , + []( void* d ) { + reinterpret_cast< T* >( d )->~T( ); + ::operator delete( d ); + } ) +{ } + +template< > +inline T_RegisteredItem::T_RegisteredItem( + SP_Unregister& unregisterFunction , + void* data ) noexcept + : T_RegisteredItem( unregisterFunction , data , [](void*){} ) +{ } + +inline T_RegisteredItem::~T_RegisteredItem( ) +{ + clear( ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_RegisteredItem::T_RegisteredItem( + T_RegisteredItem&& other ) noexcept + : T_RegisteredItem( ) +{ + swap( *this , other ); +} + +inline T_RegisteredItem& T_RegisteredItem::operator =( + T_RegisteredItem&& other ) noexcept +{ + clear( ); + swap( *this , other ); + return *this; +} + +inline M_DECLARE_SWAP( T_RegisteredItem ) +{ + using std::swap; + swap( lhs.automatic_ , rhs.automatic_ ); + swap( lhs.unregisterFunction_ , rhs.unregisterFunction_ ); + swap( lhs.data_ , rhs.data_ ); + swap( lhs.dataDestructor_ , rhs.dataDestructor_ ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_RegisteredItem::automatic( + const bool v ) noexcept +{ + automatic_ = v; +} + +inline bool T_RegisteredItem::automatic( ) const noexcept +{ + return automatic_; +} + +/*----------------------------------------------------------------------------*/ + +inline void T_RegisteredItem::unregister( ) noexcept +{ + if ( data_ && unregisterFunction_ ) { + (*unregisterFunction_)( data_ ); + deleteData( ); + } +} + +inline T_RegisteredItem::operator bool( ) const noexcept +{ + return bool( data_ ); +} + +inline void T_RegisteredItem::clear( ) noexcept +{ + if ( automatic_ ) { + unregister( ); + } else { + deleteData( ); + } +} + +inline void T_RegisteredItem::deleteData( ) noexcept +{ + if ( data_ != nullptr ) { + if ( dataDestructor_ ) { + dataDestructor_( data_ ); + } + data_ = nullptr; + } +} + + +} // namespace lw +#endif // _H_LW_LIB_INLINE_REGISTRATION diff --git a/include/ebcl/inline/SRDBinary.hh b/include/ebcl/inline/SRDBinary.hh new file mode 100644 index 0000000..cf25bad --- /dev/null +++ b/include/ebcl/inline/SRDBinary.hh @@ -0,0 +1,33 @@ +/******************************************************************************/ +/* SRD - BINARY STORAGE - INLINE CODE *****************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDBINARY +#define _H_LW_LIB_INLINE_SRDBINARY +#include +namespace lw { + + +inline void SRDBinaryWriteTo( A_OutputStream& output , T_SRDList const& data ) +{ + T_SRDBinaryWriter( output ).start( ).putList( data ).end( ); +} + + +/*= T_SRDBinaryReader ========================================================*/ + +inline T_SRDBinaryReader::T_SRDBinaryReader( A_SRDReaderTarget& target ) + : A_SRDReader( target ) +{ } + +inline T_SRDList SRDBinaryReadFrom( T_String const& name , A_InputStream& input , bool structured ) +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( structured ); + T_SRDBinaryReader( mt ).read( name , input ); + return mt.list( ); +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_SRDBINARY diff --git a/include/ebcl/inline/SRDData.hh b/include/ebcl/inline/SRDData.hh new file mode 100644 index 0000000..6bf0150 --- /dev/null +++ b/include/ebcl/inline/SRDData.hh @@ -0,0 +1,438 @@ +/******************************************************************************/ +/* SRD - DATA - INLINE CODE ***************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDDATA +#define _H_LW_LIB_INLINE_SRDDATA +#include +namespace lw { + + +/*= T_SRDLocationChaining ====================================================*/ + +inline T_SRDLocationChaining::T_SRDLocationChaining( + const E_SRDLocationChaining how , + const uint32_t depth , + SP_SRDLocation const& to ) noexcept + : circumstances( how ) , depth( depth ) , location( to ) +{ } + +inline bool T_SRDLocationChaining::isGenerated( ) const noexcept +{ + return circumstances == E_SRDLocationChaining::GENERATED + || circumstances == E_SRDLocationChaining::SUBSTITUTED; +} + + +/*= T_SRDLocation ============================================================*/ + +inline T_SRDLocation::T_SRDLocation( ) noexcept + : source_( ) , line_( 0 ) , character_( 0 ) +{ } + +inline T_SRDLocation& T_SRDLocation::operator= ( + T_SRDLocation&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +inline T_SRDLocation::T_SRDLocation( + T_SRDToken const& token ) noexcept + : T_SRDLocation( ) +{ + if ( token.hasLocation( ) ) { + *this = token.location( ); + } +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDLocation::chain( + const E_SRDLocationChaining how , + SP_SRDLocation const& to ) noexcept +{ + chain( how , 0 , to ); +} + +inline void T_SRDLocation::chain( + const E_SRDLocationChaining how , + const uint32_t depth , + SP_SRDLocation const& to ) noexcept +{ + assert( bool( to ) ); + chaining_.setNew( how , depth , to ); +} + +inline void T_SRDLocation::clearChain( ) noexcept +{ + chaining_.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_SRDLocation::unknown( ) const noexcept +{ + return source_.size( ) == 0; +} + +inline T_String const& T_SRDLocation::source( ) const noexcept +{ + return source_; +} + +inline bool T_SRDLocation::binary( ) const noexcept +{ + return line_ == 0; +} + +inline uint32_t T_SRDLocation::line( ) const noexcept +{ + return line_; +} + +inline size_t T_SRDLocation::character( ) const noexcept +{ + return character_; +} + +inline size_t T_SRDLocation::byte( ) const noexcept +{ + return character_; +} + +inline bool T_SRDLocation::isChained( ) const noexcept +{ + return chaining_.present( ); +} + +inline T_SRDLocationChaining const& T_SRDLocation::chaining( ) const noexcept +{ + return chaining_; +} + + +/*= T_SRDError ===============================================================*/ + +inline T_SRDError::T_SRDError( + T_String error , + T_SRDLocation location , + const bool details ) noexcept + : error_( std::move( error ) ) , + location_( std::move( location ) ) , + isDetails_( details ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_SRDError::T_SRDError( + T_SRDError const& other ) noexcept + : error_( other.error_ ) , + location_( other.location_ ) , + isDetails_( other.isDetails_ ) +{ } + +inline T_SRDError& T_SRDError::operator= ( + T_SRDError const& other ) noexcept +{ + error_ = other.error_; + location_ = other.location_; + isDetails_ = other.isDetails_; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDError::T_SRDError( + T_SRDError&& other ) noexcept + : error_( ) , location_( ) +{ + swap( *this , other ); +} + +inline T_SRDError& T_SRDError::operator=( + T_SRDError&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline M_DECLARE_SWAP( T_SRDError ) +{ + using std::swap; + swap( lhs.error_ , rhs.error_ ); + swap( lhs.location_ , rhs.location_ ); + swap( lhs.isDetails_ , rhs.isDetails_ ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_SRDError::error( ) const noexcept +{ + return error_; +} + +inline T_SRDLocation const& T_SRDError::location( ) const noexcept +{ + return location_; +} + +inline bool T_SRDError::isDetails( ) const noexcept +{ + return isDetails_; +} + + +/*= T_SRDErrors ==============================================================*/ + +template< typename ... ArgTypes > +inline void T_SRDErrors::add( + char const* error , + ArgTypes&&... locationArgs ) +{ + add( InPlace::v , T_String::Pooled( error ) , + T_SRDLocation( std::forward< ArgTypes >( locationArgs ) ... ) ); +} + +template< typename ... ArgTypes > +inline void T_SRDErrors::add( + T_String const& error , + ArgTypes&&... locationArgs ) +{ + add( InPlace::v , error , + T_SRDLocation( std::forward< ArgTypes >( locationArgs ) ... ) ); +} + +template< typename ... ArgTypes > +inline void T_SRDErrors::details( + char const* error , + ArgTypes&&... locationArgs ) +{ + add( InPlace::v , T_String::Pooled( error ) , + T_SRDLocation( std::forward< ArgTypes >( locationArgs ) ... ) , + true ); +} + +template< typename ... ArgTypes > +inline void T_SRDErrors::details( + T_String const& error , + ArgTypes&&... locationArgs ) +{ + add( InPlace::v , error , + T_SRDLocation( std::forward< ArgTypes >( locationArgs ) ... ) , + true ); +} + +template< typename ... ArgTypes > +inline void T_SRDErrors::add( + InPlace , + ArgTypes&&... args ) +{ + checkAdded( errors_.addNew( std::forward< ArgTypes >( args ) ... ) ); +} + +inline void T_SRDErrors::add( + T_SRDError const& error ) +{ + checkAdded( errors_.addNew( error ) ); +} + +inline void T_SRDErrors::add( + T_SRDError&& error ) +{ + checkAdded( errors_.addNew( std::move( error ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline uint32_t T_SRDErrors::size( ) const noexcept +{ + return errors_.size( ); +} + +inline T_SRDError const& T_SRDErrors::operator[] ( + uint32_t index ) const noexcept +{ + return errors_[ index ]; +} + +inline void T_SRDErrors::clear( ) noexcept +{ + errors_.clear( ); + errCount_ = 0; +} + + +/*= X_SRDErrors ==============================================================*/ + +inline X_SRDErrors::X_SRDErrors( T_SRDErrors const& errors ) + : errors( errors ) +{ } + + +/*= T_SRDToken ===============================================================*/ + +inline T_SRDToken::T_SRDToken( + const E_SRDTokenType type ) noexcept + : type_( type ) +{ } + +inline T_SRDToken::T_SRDToken( T_SRDToken&& other ) noexcept +{ + swap( *this , other ); +} + +inline T_SRDToken& T_SRDToken::operator= ( T_SRDToken&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_SRDToken T_SRDToken::Binary( + T const* const data , + const uint32_t count ) noexcept +{ + return Binary( (uint8_t const*) data , count ); +} + +template< > +inline T_SRDToken T_SRDToken::Binary< uint8_t >( + uint8_t const* const data , + const uint32_t count ) noexcept +{ + return Binary( NewShared< T_Buffer< uint8_t > >( data , count ) ); +} + +template< typename T > +inline T_SRDToken T_SRDToken::Binary( + T_Buffer< T > const& data ) noexcept +{ + if ( data.size( ) == 0 ) { + return Binary( NewShared< T_Buffer< uint8_t > >( ) ); + } else { + return Binary( (uint8_t const*) &data[ 0 ] , data.bytes( ) ); + } +} + + +/*----------------------------------------------------------------------------*/ + +inline E_SRDTokenType T_SRDToken::type( ) const +{ + return type_; +} + +inline bool T_SRDToken::isText( ) const +{ + return type_ == E_SRDTokenType::WORD || type_ == E_SRDTokenType::STRING; +} + +inline bool T_SRDToken::isNumeric( ) const +{ + return isInteger( ) || type_ == E_SRDTokenType::FLOAT; +} + +inline bool T_SRDToken::isInteger( ) const +{ + return type_ == E_SRDTokenType::INT || type_ == E_SRDTokenType::LONG; +} + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_SRDToken::text( ) const +{ + return text_; +} + +inline T_SRDList const& T_SRDToken::list( ) const +{ + return *list_; +} + +inline T_SRDList& T_SRDToken::list( ) +{ + return *list_; +} + +inline int64_t T_SRDToken::longValue( ) const +{ + return longValue_; +} + +inline double T_SRDToken::floatValue( ) const +{ + return floatValue_; +} + +inline T_String const& T_SRDToken::stringValue( ) const +{ + return stringValue_; +} + +inline T_Buffer< uint8_t > const& T_SRDToken::binary( ) const +{ + return *binary_; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_SRDToken::operator ==( T_SRDToken const& other ) const +{ + return compare( other ) == 0; +} + +inline bool T_SRDToken::operator !=( T_SRDToken const& other ) const +{ + return compare( other ) != 0; +} + +inline bool T_SRDToken::operator >( T_SRDToken const& other ) const +{ + return compare( other ) > 0; +} + +inline bool T_SRDToken::operator <( T_SRDToken const& other ) const +{ + return compare( other ) < 0; +} + +inline bool T_SRDToken::operator >=( T_SRDToken const& other ) const +{ + return compare( other ) >= 0; +} + +inline bool T_SRDToken::operator <=( T_SRDToken const& other ) const +{ + return compare( other ) <= 0; +} + +inline M_DEFINE_COMPARATOR( T_SRDToken ) +{ + return a.compare( b ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_SRDToken::hasLocation( ) const noexcept +{ + return bool( location_ ); +} + +inline T_SRDLocation& T_SRDToken::location( ) noexcept +{ + return *location_; +} + +inline T_SRDLocation const& T_SRDToken::location( ) const noexcept +{ + return *location_; +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_SRDDATA diff --git a/include/ebcl/inline/SRDDefinitions.hh b/include/ebcl/inline/SRDDefinitions.hh new file mode 100644 index 0000000..0c94d56 --- /dev/null +++ b/include/ebcl/inline/SRDDefinitions.hh @@ -0,0 +1,525 @@ +/******************************************************************************/ +/* SRD - PARSER DEFINITIONS - INLINE CODE *************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDDEFINITIONS +#define _H_LW_LIB_INLINE_SRDDEFINITIONS +#include +namespace lw { + + +/*= T_SRDEnum ================================================================*/ + +inline T_SRDEnum::T_SRDEnum( T_String name ) + : name_( std::move( name ) ) , index_( 32 , 32 , 32 ) , words_( 32 ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_SRDEnum& T_SRDEnum::operator<< ( char const* word ) +{ + return operator<< ( T_String::Pooled( word ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_SRDEnum::name( ) const +{ + return name_; +} + +inline auto T_SRDEnum::size( ) const +{ + return words_.size( ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_SRDEnum::operator[] ( int index ) const +{ + return words_[ index ]; +} + +inline T_String const& T_SRDEnum::operator[] ( uint32_t index ) const +{ + return words_[ index ]; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_SRDEnum::contains( T_String const& word ) const +{ + return (*this)[ word ] != T_HashIndex::INVALID_INDEX; +} + +inline bool T_SRDEnum::contains( char const* word ) const +{ + return (*this)[ word ] != T_HashIndex::INVALID_INDEX; +} + + +/*= T_SRDInputItem ===========================================================*/ + +inline T_SRDInputItem::T_ListHelper::T_ListHelper( ) + : items( 16 ) +{ } + +inline T_SRDInputItem::T_ListHelper& T_SRDInputItem::T_ListHelper::operator <<( T_SRDInputItem item ) +{ + items.add( std::move( item ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputItem::T_SRDInputItem( ) + : type_( E_SRDInputItem::ALTERNATIVE ) , items_( 32 ) +{ } + +inline T_SRDInputItem::T_SRDInputItem( T_String wordOrName , bool isWord ) + : type_( isWord ? E_SRDInputItem::WORD : E_SRDInputItem::ENUM ) , word_( std::move( wordOrName ) ) +{ } + +inline T_SRDInputItem::T_SRDInputItem( char const* cString ) + : T_SRDInputItem( T_String::Pooled( cString ) , true ) +{ } + +inline T_SRDInputItem::T_SRDInputItem( E_SRDTokenType type ) + : type_( E_SRDInputItem::TOKEN ) , token_( type ) +{ } + +inline T_SRDInputItem::T_SRDInputItem( uint32_t min , uint32_t max ) + : type_( E_SRDInputItem::REPETITION ) , items_( 32 ) , min_( min ) , max_( max ) +{ + if ( min > max ) { + throw std::invalid_argument( "min > max" ); + } else if ( min == 0 && max == 0 ) { + throw std::invalid_argument( "min = max = 0" ); + } +} + +/*----------------------------------------------------------------------------*/ + +inline E_SRDInputItem T_SRDInputItem::type( ) const +{ + return type_; +} + +inline T_String const& T_SRDInputItem::word( ) const +{ + return word_; +} + +inline E_SRDTokenType T_SRDInputItem::token( ) const +{ + return token_; +} + +inline T_Array< T_SRDInputItem > T_SRDInputItem::items( ) const +{ + return items_; +} + +inline uint32_t T_SRDInputItem::min( ) const +{ + return min_; +} + +inline uint32_t T_SRDInputItem::max( ) const +{ + return max_; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_SRDInputItem::operator ==( T_SRDInputItem const& ) const +{ + return false; +} + +/*----------------------------------------------------------------------------*/ + +inline T_Array< T_SRDInputItem >& operator <<( + T_Array< T_SRDInputItem >& items , + T_SRDInputItem::T_ListHelper& list ) +{ + items << T_SRDInputItem( E_SRDTokenType::START ); + items.addAll( list.items ); + items << T_SRDInputItem( E_SRDTokenType::END ); + return items; +} + +inline T_SRDInputItem& T_SRDInputItem::operator <<( T_ListHelper list ) +{ + items_ << list; + return *this; +} + + +/*= T_SRDInputRule ===========================================================*/ + +inline T_SRDInputRule::T_SetContextExecutor::T_SetContextExecutor( bool exit , F_SRDHandler executor ) + : exit( exit ) , executor( executor ) +{ } + +inline T_SRDInputRule::T_SetContext::T_SetContext( T_String string ) + : name( std::move( string ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputRule& T_SRDInputRule::operator<< ( F_SRDHandler executor ) +{ + executor_ = executor; + return *this; +} + +inline T_SRDInputRule& T_SRDInputRule::operator<< ( T_SetContextExecutor sce ) +{ + if ( sce.exit ) { + contextExit_ = sce.executor; + } else { + contextEnter_ = sce.executor; + } + return *this; +} + +inline T_SRDInputRule& T_SRDInputRule::operator<< ( T_SetContext sc ) +{ + context_ = sc.name; + return *this; +} + +inline T_SRDInputRule& T_SRDInputRule::operator <<( T_SRDInputItem::T_ListHelper lh ) +{ + items_ << lh; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline F_SRDHandler const& T_SRDInputRule::handler( ) const +{ + return executor_; +} + +inline T_Array< T_SRDInputItem > const& T_SRDInputRule::rule( ) const +{ + return items_; +} + +inline T_String const& T_SRDInputRule::context( ) const +{ + return context_; +} + +inline F_SRDHandler const& T_SRDInputRule::onEnter( ) const +{ + return contextEnter_; +} + +inline F_SRDHandler const& T_SRDInputRule::onExit( ) const +{ + return contextExit_; +} + +/*= T_SRDContext =============================================================*/ + +inline T_String const& T_SRDContext::name( ) const +{ + return name_; +} + +inline T_String const& T_SRDContext::parent( ) const +{ + return parent_; +} + +inline T_Array< T_SRDInputRule > const& T_SRDContext::rules( ) const +{ + return rules_; +} + + +/*= T_SRDParserDefs ==========================================================*/ + +inline T_SRDParserDefs::SetHandler::SetHandler( F_SRDHandler const& handler , bool start ) + : handler( handler ) , start( start ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_SRDParserDefs::T_SRDParserDefs( char const* name ) + : T_SRDParserDefs( T_String::Pooled( name ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_String const& T_SRDParserDefs::defaultContext( ) const +{ + return dfCtx_; +} + +inline void T_SRDParserDefs::defaultContext( char const* name ) +{ + defaultContext( T_String::Pooled( name ) ); +} + +inline F_SRDHandler const& T_SRDParserDefs::onStart( ) const +{ + return onStart_; +} + +inline F_SRDHandler const& T_SRDParserDefs::onFinish( ) const +{ + return onFinish_; +} + +/*----------------------------------------------------------------------------*/ + +inline uint32_t T_SRDParserDefs::contexts( ) const +{ + return ctx_.size( ); +} + +inline T_SRDContext& T_SRDParserDefs::operator[] ( uint32_t idx ) +{ + return ctx_[ idx ]; +} + +inline T_SRDContext const& T_SRDParserDefs::operator[] ( uint32_t idx ) const +{ + return ctx_[ idx ]; +} + +inline T_SRDContext& T_SRDParserDefs::context( char const* name ) +{ + return context( T_String::Pooled( name ) ); +} + +inline T_SRDContext& T_SRDParserDefs::context( char const* name , char const* parent ) +{ + return context( T_String::Pooled( name ) , T_String::Pooled( parent ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline uint32_t T_SRDParserDefs::enums( ) const +{ + return enums_.size( ); +} + +inline T_SRDEnum & T_SRDParserDefs::enumeration( char const* name ) +{ + return enumeration( T_String::Pooled( name ) ); +} + + +/*= SRD::* ===================================================================*/ + +namespace SRD { + +inline T_SRDParserDefs::SetHandler OnStart( F_SRDHandler const& handler ) +{ + return T_SRDParserDefs::SetHandler( handler , true ); +} + +inline T_SRDParserDefs::SetHandler OnFinish( F_SRDHandler const& handler ) +{ + return T_SRDParserDefs::SetHandler( handler , false ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDContext Context( T_String name ) +{ + return T_SRDContext( std::move( name ) ); +} + +inline T_SRDContext Context( char const* name ) +{ + return T_SRDContext( T_String::Pooled( name ) ); +} + +inline T_SRDContext Context( char const* name , char const* parent ) +{ + return T_SRDContext( T_String::Pooled( name ) , T_String::Pooled( parent ) ); +} + +inline T_SRDContext Context( T_String name , T_String parent ) +{ + return T_SRDContext( std::move( name ) , std::move( parent ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputRule Rule( ) +{ + return T_SRDInputRule( ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDEnum CreateEnum( T_String name ) +{ + return T_SRDEnum( std::move( name ) ); +} + +inline T_SRDEnum CreateEnum( char const* name ) +{ + return T_SRDEnum( T_String::Pooled( name ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputItem Word( T_String const& word ) +{ + return T_SRDInputItem( word , true ); +} + +inline T_SRDInputItem Word( char const* word ) +{ + return T_SRDInputItem( T_String::Pooled( word ) , true ); +} + +inline T_SRDInputItem Word( ) +{ + return T_SRDInputItem( E_SRDTokenType::WORD ); +} + +inline T_SRDInputItem String( ) +{ + return T_SRDInputItem( E_SRDTokenType::STRING ); +} + +inline T_SRDInputItem Binary( ) +{ + return T_SRDInputItem( E_SRDTokenType::BINARY ); +} + +inline T_SRDInputItem LStart( ) +{ + return T_SRDInputItem( E_SRDTokenType::START ); +} + +inline T_SRDInputItem LEnd( ) +{ + return T_SRDInputItem( E_SRDTokenType::END ); +} + +inline T_SRDInputItem Int32( ) +{ + return T_SRDInputItem( E_SRDTokenType::INT ); +} + +inline T_SRDInputItem Int64( ) +{ + return T_SRDInputItem( E_SRDTokenType::LONG ); +} + +inline T_SRDInputItem Float( ) +{ + return T_SRDInputItem( E_SRDTokenType::FLOAT ); +} + +inline T_SRDInputItem Enum( T_String name ) +{ + return T_SRDInputItem( std::move( name ) , false ); +} + +inline T_SRDInputItem Enum( char const* name ) +{ + return T_SRDInputItem( T_String::Pooled( name ) , false ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputItem::T_ListHelper List( ) +{ + return T_SRDInputItem::T_ListHelper( ); +} + +inline T_SRDInputItem Alt( ) +{ + return T_SRDInputItem( ); +} + +inline T_SRDInputItem Sub( ) +{ + return T_SRDInputItem( 1 , 1 ); +} + +inline T_SRDInputItem Opt( ) +{ + return T_SRDInputItem( 0 , 1 ); +} + +inline T_SRDInputItem Opt( T_SRDInputItem item ) +{ + return Opt( ) << std::move( item ); +} + +inline T_SRDInputItem AtLeast( uint32_t n ) +{ + return T_SRDInputItem( n , UINT32_MAX ); +} + +inline T_SRDInputItem AtMost( uint32_t n ) +{ + return T_SRDInputItem( 0 , n ); +} + +inline T_SRDInputItem Times( uint32_t n ) +{ + return T_SRDInputItem( n , n ); +} + +inline T_SRDInputItem Between( uint32_t a , uint32_t b ) +{ + return T_SRDInputItem( a > b ? b : a , a > b ? a : b ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputItem Integer( ) +{ + return Alt( ) << Int32( ) << Int64( ); +} +inline T_SRDInputItem Numeric( ) +{ + return Alt( ) << Integer( ) << Float( ); +} +inline T_SRDInputItem Text( ) +{ + return Alt( ) << String( ) << Word( ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputRule::T_SetContext EnterContext( T_String name ) +{ + return T_SRDInputRule::T_SetContext( std::move( name ) ); +} + +inline T_SRDInputRule::T_SetContext EnterContext( char const* name ) +{ + return EnterContext( T_String::Pooled( name ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDInputRule::T_SetContextExecutor OnEnter( F_SRDHandler f ) +{ + return T_SRDInputRule::T_SetContextExecutor( false , f ); +} + +inline T_SRDInputRule::T_SetContextExecutor OnExit( F_SRDHandler f ) +{ + return T_SRDInputRule::T_SetContextExecutor( true , f ); +} + +} // namespace SRD + + +} // namespace +#endif // _H_LW_LIB_INLINE_SRDDEFINITIONS diff --git a/include/ebcl/inline/SRDIO.hh b/include/ebcl/inline/SRDIO.hh new file mode 100644 index 0000000..55a1c69 --- /dev/null +++ b/include/ebcl/inline/SRDIO.hh @@ -0,0 +1,80 @@ +/******************************************************************************/ +/* SRD - INPUT AND OUTPUT - INLINE CODE ***************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDIO +#define _H_LW_LIB_INLINE_SRDIO +#include +namespace lw { + +/*= A_SRDWriter ==============================================================*/ + +inline A_SRDWriter::~A_SRDWriter( ) +{ } + + +/*= A_SRDReaderTarget ========================================================*/ + +inline A_SRDReaderTarget::~A_SRDReaderTarget( ) +{ } + + +/*= A_SRDReader ==============================================================*/ + +inline A_SRDReader::A_SRDReader( A_SRDReaderTarget& target ) + : target_( target ) +{ } + +inline A_SRDReader::~A_SRDReader( ) +{ } + + +/*= X_SRDWriterError =========================================================*/ + +inline X_SRDWriterError::X_SRDWriterError( char const* msg ) noexcept + : std::exception( ) , msg_( msg ) +{ } + + +/*= T_SRDReaderTargetHelper ==================================================*/ + +inline T_SRDReaderTargetHelper::T_SRDReaderTargetHelper( A_SRDReaderTarget& target , T_SRDErrors& errors ) + : target_( target ) , errors_( errors ) +{ + target_.start( errors_ ); +} + +inline T_SRDReaderTargetHelper::~T_SRDReaderTargetHelper( ) +{ + target_.end( errors_ ); +} + + +/*= T_SRDMemoryTarget ========================================================*/ + +inline void T_SRDMemoryTarget::clearFlushToken( + bool clearIt ) noexcept +{ + clearFlushToken_ = clearIt; +} + +inline bool T_SRDMemoryTarget::clearFlushToken( ) const noexcept +{ + return clearFlushToken_; +} + +inline T_SRDList const& T_SRDMemoryTarget::list( ) const +{ + return list_.list( ); +} + +inline bool T_SRDMemoryTarget::complete( ) const +{ + return current_ != nullptr && stack_.size( ) == 0 && list_.list( ).size( ) != 0; +} + + +} // namespace + + +#endif // _H_LW_LIB_INLINE_SRDIO diff --git a/include/ebcl/inline/SRDParserConfig.hh b/include/ebcl/inline/SRDParserConfig.hh new file mode 100644 index 0000000..58710ce --- /dev/null +++ b/include/ebcl/inline/SRDParserConfig.hh @@ -0,0 +1,33 @@ +/******************************************************************************/ +/* SRD - PARSER CONFIGURATION - INLINE CODE ***********************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDPARSERCONFIG +#define _H_LW_LIB_INLINE_SRDPARSERCONFIG +#include +namespace lw { + + +/*= T_SRDTransition ==========================================================*/ + +inline T_SRDTransition::T_SRDTransition( E_SRDTokenType tokenType , uint32_t next ) + : type( E_SRDTransitionType::TOKEN ) , tokenType( tokenType ) , + next( next ) +{ } + +inline T_SRDTransition::T_SRDTransition( E_SRDTransitionType type , uint32_t index , uint32_t next ) + : type( type ) , index( index ) , next( next ) +{ } + + +/*= T_SRDParserConfig ========================================================*/ + +inline T_Optional< uint32_t > T_SRDParserConfig::enumValue( + char const* name , T_String const& member ) const noexcept +{ + return enumValue( T_String::Pooled( name ) , member ); +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_SRDPARSERCONFIG diff --git a/include/ebcl/inline/SRDText.hh b/include/ebcl/inline/SRDText.hh new file mode 100644 index 0000000..2ba0bfb --- /dev/null +++ b/include/ebcl/inline/SRDText.hh @@ -0,0 +1,25 @@ +/******************************************************************************/ +/* SRD - TEXT STORAGE - INLINE CODE *******************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_SRDTEXT +#define _H_LW_LIB_INLINE_SRDTEXT +#include +namespace lw { + + +inline void SRDWriteAsText( A_OutputStream& output , T_SRDList const& data ) +{ + T_SRDTextWriter( output ).start( ).putList( data ).end( ); +} + + +/*= T_SRDTextReader ==========================================================*/ + +inline T_SRDTextReader::T_SRDTextReader( A_SRDReaderTarget& target ) + : A_SRDReader( target ) +{ } + + +} // namespace +#endif // _H_LW_LIB_INLINE_SRDTEXT diff --git a/include/ebcl/inline/Streams.hh b/include/ebcl/inline/Streams.hh new file mode 100644 index 0000000..8f83e0b --- /dev/null +++ b/include/ebcl/inline/Streams.hh @@ -0,0 +1,98 @@ +/******************************************************************************/ +/* STREAMS - INLINE CODE ******************************************************/ +/******************************************************************************/ + +#pragma once +#include +namespace lw { + + +/*= X_StreamError ============================================================*/ + +inline X_StreamError::X_StreamError( E_StreamError e ) + : std::exception( ) , error_( e ) , sysError_( -1 ) +{ } + +inline X_StreamError::X_StreamError( int error ) + : std::exception( ) , error_( E_StreamError::SYSTEM_ERROR ) , sysError_( error ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline E_StreamError X_StreamError::code( ) const +{ + return error_; +} + +inline int X_StreamError::systemError( ) const +{ + return sysError_; +} + + +/*= A_Stream =================================================================*/ + +inline A_Stream::A_Stream( bool isInput , size_t position ) noexcept + : isInput_( isInput ) , knownSize_( false ) , position_( position ) +{ } + +inline A_Stream::A_Stream( bool isInput , size_t position , size_t size ) noexcept + : isInput_( isInput ) , knownSize_( true ) , size_( size ) , + position_( position ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline A_Stream::~A_Stream( ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline bool A_Stream::isInput( ) const +{ + return isInput_; +} + +inline bool A_Stream::isSizeKnown( ) const +{ + return knownSize_; +} + +inline size_t A_Stream::position( ) const +{ + return position_; +} + +inline size_t A_Stream::size( ) const +{ + if ( knownSize_ ) { + return size_; + } else { + throw X_StreamError( E_StreamError::NOT_SUPPORTED ); + } +} + + +/*= A_InputStream ============================================================*/ + +inline A_InputStream::A_InputStream( size_t position ) noexcept + : A_Stream( true , position ) +{ } + +inline A_InputStream::A_InputStream( size_t position , size_t size ) noexcept + : A_Stream( true , position , size ) +{ } + + +/*= A_OutputStream ===========================================================*/ + +inline A_OutputStream::A_OutputStream( size_t position ) noexcept + : A_Stream( false , position ) +{ } + +inline A_OutputStream::A_OutputStream( size_t position , size_t size ) noexcept + : A_Stream( false , position , size ) +{ } + + +} // namespace diff --git a/include/ebcl/inline/Strings.hh b/include/ebcl/inline/Strings.hh new file mode 100644 index 0000000..ed443ce --- /dev/null +++ b/include/ebcl/inline/Strings.hh @@ -0,0 +1,591 @@ +/******************************************************************************/ +/* STRINGS AND RELATED UTILITIES - INLINE CODE ********************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_STRINGS +#define _H_LW_LIB_INLINE_STRINGS +#include +namespace lw { + + +/*= T_Character ==============================================================*/ + +inline T_Character::T_Character( ) noexcept + : codepoint( 0 ) +{ } + +inline T_Character::T_Character( const T_Character& other ) noexcept + : codepoint( other.codepoint ) +{ } + +template< typename T , typename > +inline T_Character::T_Character( T codepoint ) noexcept + : codepoint( uint32_t( codepoint ) ) +{ + assert( codepoint >= 0 ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_Character::isValid( ) const +{ + return codepoint < 0x110000; +} + +inline bool T_Character::isAscii( ) const +{ + return codepoint < 128; +} + +inline bool T_Character::isControl( ) const +{ + return codepoint < 32; +} + +inline bool T_Character::isUppercase( ) const +{ + return codepoint >= 'A' && codepoint <= 'Z'; +} + +inline bool T_Character::isLowercase( ) const +{ + return codepoint >= 'a' && codepoint <= 'z'; +} + +inline bool T_Character::isAlpha( ) const +{ + return isUppercase( ) || isLowercase( ); +} + +inline bool T_Character::isNumeric( ) const +{ + return codepoint >= '0' && codepoint <= '9'; +} + +inline bool T_Character::isAlphanumeric( ) const +{ + return isAlpha( ) || isNumeric( ); +} + +inline bool T_Character::isWhitespace( ) const +{ + return codepoint == 32 || codepoint == '\t' + || codepoint == '\n' || codepoint == '\r'; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_Character::operator== ( T_Character const& other ) const +{ + return codepoint == other.codepoint; +} + +inline bool T_Character::operator!= ( T_Character const& other ) const +{ + return codepoint != other.codepoint; +} + +inline bool T_Character::operator< ( T_Character const& other ) const +{ + return codepoint < other.codepoint; +} + +inline bool T_Character::operator> ( T_Character const& other ) const +{ + return codepoint > other.codepoint; +} + +inline bool T_Character::operator<= ( T_Character const& other ) const +{ + return codepoint <= other.codepoint; +} + +inline bool T_Character::operator>= ( T_Character const& other ) const +{ + return codepoint >= other.codepoint; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T , typename > +inline bool T_Character::operator== ( T other ) const +{ + return codepoint == uint32_t( other ); +} + +template< typename T , typename > +inline bool T_Character::operator!= ( T other ) const +{ + return codepoint != uint32_t( other ); +} + +template< typename T , typename > +inline bool T_Character::operator< ( T other ) const +{ + return codepoint < uint32_t( other ); +} + +template< typename T , typename > +inline bool T_Character::operator<= ( T other ) const +{ + return codepoint <= uint32_t( other ); +} + +template< typename T , typename > +inline bool T_Character::operator> ( T other ) const +{ + return codepoint > uint32_t( other ); +} + +template< typename T , typename > +inline bool T_Character::operator>= ( T other ) const +{ + return codepoint >= uint32_t( other ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_Character::operator uint32_t ( ) const +{ + return codepoint; +} + +/*----------------------------------------------------------------------------*/ + +inline uint32_t T_Character::writeTo( char* output , uint32_t avail ) const +{ + return UTF8PutCodepoint( output , avail , codepoint ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_Character T_Character::toUpper( ) const +{ + if ( isLowercase( ) ) { + return *this & (~0x20); + } else { + return *this; + } +} + +inline T_Character T_Character::toLower( ) const +{ + if ( isUppercase( ) ) { + return *this | 0x20; + } else { + return *this; + } +} + + +/*= A_StringData =============================================================*/ + +inline bool A_StringData::valid( ) const +{ + return valid_; +} + +inline char const* A_StringData::data( ) const +{ + return data_; +} + +inline uint32_t A_StringData::length( ) const +{ + return length_; +} + +inline uint32_t A_StringData::size( ) const +{ + return size_; +} + + +/*= T_String =================================================================*/ + +inline T_String T_String::Pooled( char const* string ) +{ + return Pooled( string , strlen( string ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_String::valid( ) const +{ + return data_->valid( ); +} + +inline uint32_t T_String::size( ) const +{ + return data_->size( ); +} + +inline uint32_t T_String::length( ) const +{ + return data_->length( ); +} + +inline char const* T_String::data( ) const +{ + return data_->data( ); +} + +inline T_String::operator bool ( ) const +{ + return size( ) != 0; +} + +inline bool T_String::operator! ( ) const +{ + return size( ) == 0; +} + +/*----------------------------------------------------------------------------*/ + +inline T_Character T_String::operator[] ( uint32_t index ) const +{ + assert( index < length( ) ); + char const* const d( data( ) ); + return UTF8GetCodepoint( d + UTF8GetMemoryOffset( d , index ) ); +} + +inline T_String T_String::range( uint32_t start , uint32_t end ) const +{ + if ( start > end ) { + return T_String( ); + } + return substr( start , end == UINT32_MAX ? UINT32_MAX : ( end - start + 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_String::equals( T_String const& other ) const +{ + return data_ == other.data_ || ( other.size( ) == size( ) + && ( size( ) == 0 || !memcmp( other.data( ) , data( ) , size( ) ) ) ); +} + +inline bool T_String::equals( char const* string ) const +{ + const auto l( strlen( string ) ); + return size( ) == l && ( l == 0 || !memcmp( string , data( ) , l ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_String::operator== ( T_String const& other ) const +{ + return equals( other ); +} + +inline bool T_String::operator!= ( T_String const& other ) const +{ + return !equals( other ); +} + +inline bool T_String::operator< ( T_String const& other ) const +{ + return compare( other ) < 0; +} + +inline bool T_String::operator> ( T_String const& other ) const +{ + return compare( other ) > 0; +} + +inline bool T_String::operator>= ( T_String const& other ) const +{ + return compare( other ) >= 0; +} + +inline bool T_String::operator<= ( T_String const& other ) const +{ + return compare( other ) <= 0; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_String::operator== ( char const* string ) const +{ + return equals( string ); +} + +inline bool T_String::operator!= ( char const* string ) const +{ + return !equals( string ); +} + +/*----------------------------------------------------------------------------*/ + +inline int64_t T_String::toInteger( bool * ok , int base , bool useSep , + T_Character separator ) const +{ + return UTF8ToInteger( data( ) , size( ) , ok , base , useSep , separator ); +} + +inline uint64_t T_String::toUnsignedInteger( bool * ok , int base , bool useSep , + T_Character separator ) const +{ + return UTF8ToUnsignedInteger( data( ) , size( ) , ok , base , useSep , separator ); +} + +inline double T_String::toDouble( bool * ok , T_Character decimalPoint , + bool useSep , T_Character separator ) const +{ + return UTF8ToDouble( data( ) , size( ) , ok , decimalPoint , useSep , separator ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_StringIterator T_String::getIterator( uint32_t offset ) const +{ + if ( offset >= data_->size( ) ) { + return T_StringIterator( nullptr , 0 ); + } + return T_StringIterator( data_ , offset ); +} + +inline T_String::operator T_StringIterator( ) const +{ + return getIterator( 0 ); +} + + +/*= T_String helpers =========================================================*/ + +inline M_DEFINE_HASH( T_String ) +{ + return HashData( + reinterpret_cast< uint8_t const* >( item.data( ) ) , + item.size( ) ); +} + +inline M_DEFINE_COMPARATOR( T_String ) +{ + return a.compare( b ); +} + + +/*= T_StringIterator =========================================================*/ + +inline T_StringIterator::T_StringIterator( T_StringIterator&& other ) noexcept + : data_( other.data_ ) , pos_( other.pos_ ) , + index_( other.index_ ) , + codepoint_( other.codepoint_ ) , + bytes_( other.bytes_ ) +{ + other.data_ = nullptr; +} + +inline T_StringIterator& T_StringIterator::operator= ( T_StringIterator&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline uint32_t T_StringIterator::index( ) const +{ + return index_; +} + +inline bool T_StringIterator::atEnd( ) const +{ + return data_ == nullptr || pos_ == data_->size( ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_Character T_StringIterator::character( ) const +{ + return codepoint_; +} + +inline T_StringIterator::operator T_Character ( ) const +{ + return codepoint_; +} + + +/*= T_StringBuilder ==========================================================*/ + +inline T_StringBuilder::T_StringBuilder( ) noexcept + : data_( nullptr ) , capacity_( 0 ) , size_( 0 ) , length_( 0 ) +{ } + +inline T_StringBuilder::T_StringBuilder( char const* string ) + : T_StringBuilder( string , strlen( string ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline char const* T_StringBuilder::data( ) const +{ + return data_; +} + +inline uint32_t T_StringBuilder::capacity( ) const +{ + return capacity_; +} + +inline uint32_t T_StringBuilder::size( ) const +{ + return size_; +} + +inline uint32_t T_StringBuilder::length( ) const +{ + return length_; +} + +inline T_StringBuilder::operator bool ( ) const +{ + return size_ != 0; +} + +inline bool T_StringBuilder::operator! ( ) const +{ + return size_ == 0; +} + +/*----------------------------------------------------------------------------*/ + +inline T_StringBuilder& T_StringBuilder::clear( ) +{ + size_ = length_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_StringBuilder::operator== ( T_StringBuilder const& other ) const +{ + return other.size_ == size_ && ( size_ == 0 || !memcmp( other.data_ , data_ , size_ ) ); +} + +inline bool T_StringBuilder::operator!= ( T_StringBuilder const& other ) const +{ + return other.size_ != size_ || ( size_ != 0 && memcmp( other.data_ , data_ , size_ ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_StringBuilder::operator== ( T_String const& string ) const +{ + return string.size( ) == size_ && ( size_ == 0 || !memcmp( string.data( ) , data_ , size_ ) ); +} + +inline bool T_StringBuilder::operator!= ( T_String const& string ) const +{ + return string.size( ) != size_ || ( size_ != 0 && memcmp( string.data( ) , data_ , size_ ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_StringBuilder::operator== ( char const* string ) const +{ + return strlen( string ) == size_ && ( size_ == 0 || !memcmp( string , data_ , size_ ) ); +} + +inline bool T_StringBuilder::operator!= ( char const* string ) const +{ + return strlen( string ) != size_ || ( size_ != 0 && memcmp( string , data_ , size_ ) ); +} + +/*----------------------------------------------------------------------------*/ + +inline int64_t T_StringBuilder::toInteger( bool * ok , int base , bool useSep , + T_Character separator ) const +{ + return UTF8ToInteger( data_ , size_ , ok , base , useSep , separator ); +} + +inline uint64_t T_StringBuilder::toUnsignedInteger( bool * ok , int base , bool useSep , + T_Character separator ) const +{ + return UTF8ToUnsignedInteger( data_ , size_ , ok , base , useSep , separator ); +} + +inline double T_StringBuilder::toDouble( bool * ok , T_Character decimalPoint , + bool useSep , T_Character separator ) const +{ + return UTF8ToDouble( data( ) , size( ) , ok , decimalPoint , useSep , separator ); +} + +/*----------------------------------------------------------------------------*/ + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , T_StringBuilder const& value ) +{ + return sb.append( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , T_StringBuilder&& value ) +{ + return sb.append( std::move( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , T_String const& value ) +{ + return sb.append( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , char const* value ) +{ + return sb.append( value , strlen( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , char value ) +{ + return sb.append( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , T_Character value ) +{ + return sb.append( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , int16_t value ) +{ + return sb.appendNumeric( int64_t( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , int32_t value ) +{ + return sb.appendNumeric( int64_t( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , int64_t value ) +{ + return sb.appendNumeric( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , uint16_t value ) +{ + return sb.appendNumeric( uint64_t( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , uint32_t value ) +{ + return sb.appendNumeric( uint64_t( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , uint64_t value ) +{ + return sb.appendNumeric( value ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , float value ) +{ + return sb.appendDouble( double( value ) ); +} + +inline T_StringBuilder& operator<< ( T_StringBuilder& sb , double value ) +{ + return sb.appendDouble( value ); +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_STRINGS diff --git a/include/ebcl/inline/Threading.hh b/include/ebcl/inline/Threading.hh new file mode 100644 index 0000000..df75db1 --- /dev/null +++ b/include/ebcl/inline/Threading.hh @@ -0,0 +1,352 @@ +/******************************************************************************/ +/* THREADING - INLINE CODE ****************************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_THREADING +#define _H_LW_LIB_INLINE_THREADING +#include +namespace lw { + + +/*= T_ReadWriteMutex =========================================================*/ + +inline T_ReadWriteMutex::T_ReadWriteMutex( ) + : state_( 0 ) +{ } + +inline T_ReadWriteMutex::~T_ReadWriteMutex( ) +{ + assert( state_ == 0 ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_ReadWriteMutex::lock( ) +{ + T_ExclusiveLock lock( mutex_ ); + writerBlock_.wait( lock , [this]( ) { + return ( state_ & C_WLOCKED_ ) == 0; + } ); + state_ |= C_WLOCKED_; + readerBlock_.wait( lock , [this]( ) { + return ( state_ & C_READERS_ ) == 0; + } ); +} + +inline bool T_ReadWriteMutex::try_lock( ) +{ + T_ExclusiveLock lock( mutex_ , std::try_to_lock ); + if ( lock.owns_lock( ) && state_ == 0 ) { + state_ = C_WLOCKED_; + return true; + } + return false; +} + +inline void T_ReadWriteMutex::unlock( ) +{ + T_ScopeLock lock( mutex_ ); + assert( ( state_ & C_WLOCKED_ ) != 0 ); + state_ = 0; + writerBlock_.notify_all( ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_ReadWriteMutex::lock_shared( ) +{ + T_ExclusiveLock lock( mutex_ ); + writerBlock_.wait( lock , [this]( ) { + return state_ < C_READERS_; + } ); + state_ ++; +} + +inline bool T_ReadWriteMutex::try_lock_shared( ) +{ + T_ExclusiveLock lock( mutex_ , std::try_to_lock ); + if ( !lock.owns_lock( ) && state_ < C_READERS_ ) { + state_ ++; + return true; + } + return false; +} + +inline void T_ReadWriteMutex::unlock_shared( ) +{ + T_ScopeLock lock( mutex_ ); + assert( ( state_ & C_READERS_ ) != 0 ); + auto previous( state_ -- ); + if ( previous == ( C_WLOCKED_ & 1 ) ) { + readerBlock_.notify_one( ); + } else if ( previous == C_READERS_ ) { + writerBlock_.notify_one( ); + } +} + +/*----------------------------------------------------------------------------*/ + +inline void T_ReadWriteMutex::upgradeLock( ) +{ + T_ExclusiveLock lock( mutex_ ); + assert( ( state_ & C_READERS_ ) != 0 ); + state_ --; + writerBlock_.wait( lock , [this]( ) { + return ( state_ & C_WLOCKED_ ) == 0; + } ); + state_ |= C_WLOCKED_; + readerBlock_.wait( lock , [this]( ) { + return ( state_ & C_READERS_ ) == 0; + } ); +} + + +/*= T_ReadLock ===============================================================*/ + +inline T_ReadLock::T_ReadLock( ) noexcept + : std::shared_lock< T_ReadWriteMutex >( ) +{ } + +inline T_ReadLock::T_ReadLock( T_ReadLock&& other ) + : std::shared_lock< T_ReadWriteMutex >( std::move( other ) ) +{ } + +inline T_ReadLock::T_ReadLock( T_ReadWriteMutex& m ) + : std::shared_lock< T_ReadWriteMutex >( m ) +{ } + +inline T_ReadLock::T_ReadLock( T_ReadWriteMutex& m , std::defer_lock_t t ) + : std::shared_lock< T_ReadWriteMutex >( m , t ) +{ } + +inline T_ReadLock::T_ReadLock( T_ReadWriteMutex& m , std::try_to_lock_t t ) + : std::shared_lock< T_ReadWriteMutex >( m , t ) +{ } + +inline T_ReadLock::T_ReadLock( T_ReadWriteMutex& m , std::adopt_lock_t t ) + : std::shared_lock< T_ReadWriteMutex >( m , t ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_WriteLock T_ReadLock::upgrade( ) +{ + assert( owns_lock( ) ); + RP_ReadWriteMutex mutex( release( ) ); + mutex->upgradeLock( ); + return T_WriteLock( *mutex , std::adopt_lock ); +} + + +/*= T_RingBuffer =============================================================*/ + +template< typename T > +inline T_RingBuffer< T >::T_RingBuffer( uint32_t expand ) + : expand_( expand ) , data_( nullptr ) , allocated_( 0 ) , + used_( 0 ) , readPos_( 0 ) +{ + assert( expand ); +} + +template< typename T > +inline T_RingBuffer< T >::T_RingBuffer( T_RingBuffer< T > const& other ) + : expand_( other.expand_ ) , allocated_( other.allocated_ ) , + used_( other.used_ ) , readPos_( 0 ) +{ + if ( allocated_ ) { + data_ = reinterpret_cast< T* >( ::operator new( sizeof( T ) * allocated_ ) ); + for ( uint32_t i = 0 ; i < other.used_ ; i ++ ) { + const auto sidx( ( i + other.readPos_ ) % allocated_ ); + new ( reinterpret_cast< char* >( &data_[ i ] ) ) T( other.data_[ sidx ] ); + } + } else { + data_ = nullptr; + } +} + +template< typename T > +inline T_RingBuffer< T >::T_RingBuffer( T_RingBuffer< T >&& other ) noexcept + : T_RingBuffer( other.growth( ) ) +{ + swap( *this , other ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_RingBuffer< T >::~T_RingBuffer( ) +{ + free( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_RingBuffer< T >& T_RingBuffer< T >::operator =( T_RingBuffer< T > const& other ) +{ + free( ); + + expand_ = other.expand_; + allocated_ = other.allocated_; + used_ = other.used_; + readPos_ = 0; + if ( allocated_ ) { + data_ = reinterpret_cast< T* >( ::operator new( sizeof( T ) * allocated_ ) ); + for ( uint32_t i = 0 ; i < other.used_ ; i ++ ) { + const auto sidx( ( i + other.readPos_ ) % allocated_ ); + new ( reinterpret_cast< char* >( &data_[ i ] ) ) T( other.data_[ sidx ] ); + } + } + + return *this; +} + +template< typename T > +inline T_RingBuffer< T >& T_RingBuffer< T >::operator =( T_RingBuffer< T >&& other ) noexcept +{ + free( ); + expand_ = other.expand_; + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void swap( T_RingBuffer< T >& lhs , T_RingBuffer< T >& rhs ) noexcept +{ + using std::swap; + swap( lhs.expand_ , rhs.expand_ ); + swap( lhs.data_ , rhs.data_ ); + swap( lhs.allocated_ , rhs.allocated_ ); + swap( lhs.used_ , rhs.used_ ); + swap( lhs.readPos_ , rhs.readPos_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_RingBuffer< T >::free( ) +{ + for ( uint32_t i = 0 ; i < used_ ; i ++ ) { + const auto idx( ( readPos_ + i ) % allocated_ ); + data_[ idx ].~T( ); + } + ::operator delete( (void*) data_ ); + data_ = nullptr; + allocated_ = 0; + used_ = 0; + readPos_ = 0; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline uint32_t T_RingBuffer< T >::size( ) const +{ + return used_; +} + +template< typename T > +inline uint32_t T_RingBuffer< T >::capacity( ) const +{ + return allocated_; +} + +template< typename T > +inline uint32_t T_RingBuffer< T >::growth( ) const +{ + return expand_; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline bool T_RingBuffer< T >::readNext( T& output ) +{ + if ( used_ == 0 ) { + return false; + } + output = std::move( data_[ readPos_ ] ); + data_[ readPos_ ].~T( ); + readPos_ = ( readPos_ + 1 ) % allocated_; + used_ --; + return true; +} + +template< typename T > +inline bool T_RingBuffer< T >::readAll( T_Array< T >& output ) +{ + if ( used_ == 0 ) { + return false; + } + for ( uint32_t i = 0 ; i < used_ ; i ++ ) { + output.add( std::move( data_[ ( readPos_ + i ) % allocated_ ] ) ); + } + for ( uint32_t i = 0 ; i < used_ ; i ++ ) { + data_[ ( readPos_ + i ) % allocated_ ].~T( ); + } + used_ = 0; + return true; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_RingBuffer< T >::put( T const& input ) +{ + if ( used_ == allocated_ ) { + expand( ); + } + const auto idx( ( readPos_ + used_ ) % allocated_ ); + new ( reinterpret_cast< char* >( &data_[ idx ] ) ) T( input ); + used_ ++; +} + +template< typename T > +inline void T_RingBuffer< T >::put( T&& input ) +{ + if ( used_ == allocated_ ) { + expand( ); + } + const auto idx( ( readPos_ + used_ ) % allocated_ ); + new ( reinterpret_cast< char* >( &data_[ idx ] ) ) T( std::move( input ) ); + used_ ++; +} + +template< typename T > +template< typename... ArgTypes > +inline void T_RingBuffer< T >::putNew( ArgTypes&&... arguments ) +{ + if ( used_ == allocated_ ) { + expand( ); + } + const auto idx( ( readPos_ + used_ ) % allocated_ ); + new ( reinterpret_cast< char* >( &data_[ idx ] ) ) T( std::forward< ArgTypes >( arguments ) ... ); + used_ ++; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_RingBuffer< T >::expand( ) +{ + const auto nsz( allocated_ + expand_ ); + T* nData( reinterpret_cast< T* >( ::operator new( sizeof( T ) * nsz ) ) ); + for ( uint32_t i = 0 ; i < used_ ; i ++ ) { + const auto idx( ( readPos_ + i ) % allocated_ ); + new( reinterpret_cast< char* >( &nData[ i ] ) ) T( std::move( data_[ idx ] ) ); + data_[ idx ].~T( ); + } + + using std::swap; + swap( data_ , nData ); + allocated_ = nsz; + readPos_ = 0; + ::operator delete( (void*) nData ); +} + + + +} +#endif // _H_LW_LIB_INLINE_THREADING diff --git a/include/ebcl/inline/Types.hh b/include/ebcl/inline/Types.hh new file mode 100644 index 0000000..3910b0f --- /dev/null +++ b/include/ebcl/inline/Types.hh @@ -0,0 +1,1000 @@ +/******************************************************************************/ +/* CONTAINER TYPES - INLINE CODE **********************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_TYPES +#define _H_LW_LIB_INLINE_TYPES +#include +namespace lw { + + +/*= VARIANT TYPE =============================================================*/ + +template< typename T > +inline void T_VariantHelper::T_CopyHandler< T , true >::copy( + void* target , + T const& source ) +{ + ::new (target) T( source ); +} + +template< typename T > +inline void T_VariantHelper::T_CopyHandler< T , false >::copy( + void* , T const& ) +{ + std::terminate( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline void T_VariantHelper::T_MoveHandler< T , true >::move( + void* target , + T& source ) +{ + ::new (target) T( std::move( source ) ); +} + +template< typename T > +inline void T_VariantHelper::T_MoveHandler< T , false >::move( + void* , T& ) +{ + std::terminate( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename Type > +template< typename Raw > +inline void T_VariantHelper::T_InPlaceHandler< Type >::init( + void* storage , + F_Handler& handler , + Raw const& value ) +{ + ::new (storage) Type( value ); + handler = &action; +} + +template< typename Type > +template< typename Raw > +inline void T_VariantHelper::T_InPlaceHandler< Type >::init( + void* storage , + F_Handler& handler , + Raw&& value ) +{ + ::new (storage) Type( std::forward< Raw >( value ) ); + handler = &action; +} + +template< typename Type > +template< typename... Arguments > +inline void T_VariantHelper::T_InPlaceHandler< Type >::init( + Construct< Type > , + void* storage , + F_Handler& handler , + Arguments&&... arguments ) +{ + ::new (storage) Type( std::forward< Arguments >( arguments ) ... ); + handler = &action; +} + +template< typename Type > +inline void T_VariantHelper::T_InPlaceHandler< Type >::action( + const E_HandlerOp op , + void const* const source , + void* const dest ) +{ + switch ( op ) { + + case E_HandlerOp::ALLOC: + std::terminate( ); // We shouldn't be here + + case E_HandlerOp::ACCESS: + *( (void const* *) dest ) = source; + break; + + case E_HandlerOp::DESTRUCT: + reinterpret_cast< Type* >( dest )->~Type( ); + break; + + case E_HandlerOp::TYPEID: + *( (std::type_info const* *) dest ) = &typeid( Type ); + break; + + case E_HandlerOp::COPY: + T_CopyHandler< Type >::copy( dest , + *reinterpret_cast< Type const* >( source ) ); + break; + + case E_HandlerOp::MOVE: + { + void* const ncsrc( const_cast< void* >( source ) ); + Type& ncobj( *reinterpret_cast< Type* >( ncsrc ) ); + T_MoveHandler< Type >::move( dest , ncobj ); + ncobj.~Type( ); + } + break; + + case E_HandlerOp::HINFO: + { + T_HandlerInfo& i( *reinterpret_cast< T_HandlerInfo* >( dest ) ); + i.size = sizeof( Type ); + i.align = alignof( Type ); + i.mode = E_HandlerMode::IN_PLACE; + } + break; + + case E_HandlerOp::MKHDL: + { + const E_HandlerMode mode( *( (E_HandlerMode*) &source ) ); + if ( mode == E_HandlerMode::IN_PLACE ) { + *( (F_Handler*) dest) = &T_InPlaceHandler< Type >::action; + } else { + *( (F_Handler*) dest) = &T_HeapHandler< Type >::action; + } + } + break; + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename Type > +template< typename Raw > +inline void T_VariantHelper::T_HeapHandler< Type >::init( + void* storage , + F_Handler& handler , + Raw const& value ) +{ + *( (void**) storage ) = new Type( value ); + handler = &action; +} + +template< typename Type > +template< typename Raw > +inline void T_VariantHelper::T_HeapHandler< Type >::init( + void* storage , + F_Handler& handler , + Raw&& value ) +{ + *( (void**) storage ) = new Type( std::forward< Raw >( value ) ); + handler = &action; +} + +template< typename Type > +template< typename... Arguments > +inline void T_VariantHelper::T_HeapHandler< Type >::init( + Construct< Type > , + void* storage , + F_Handler& handler , + Arguments&&... arguments ) +{ + *( (void**) storage ) = new Type( std::forward< Arguments >( arguments ) ... ); + handler = &action; +} + +template< typename Type > +inline void T_VariantHelper::T_HeapHandler< Type >::action( + const E_HandlerOp op , + void const* const source , + void* const dest ) +{ + switch ( op ) { + + case E_HandlerOp::ALLOC: + { + using T_Storage_ = std::aligned_storage_t< + sizeof( Type ) , alignof( Type ) >; + *( void** ) dest = new T_Storage_; + } + break; + + case E_HandlerOp::ACCESS: + *( (void const* *) dest ) = *( (void* const* ) source ); + break; + + case E_HandlerOp::DESTRUCT: + delete *reinterpret_cast< Type* const* >( dest ); + break; + + case E_HandlerOp::TYPEID: + *( (std::type_info const* *) dest ) = &typeid( Type ); + break; + + case E_HandlerOp::COPY: + { + using T_Storage_ = std::aligned_storage_t< + sizeof( Type ) , alignof( Type ) >; + void*& dptr( *( void** ) dest ); + dptr = new T_Storage_; + T_CopyHandler< Type >::copy( dptr , + *reinterpret_cast< Type const* >( source ) ); + } + break; + + case E_HandlerOp::MOVE: + { + *(void**) dest = *(void**) source; + *( (void**) const_cast< void* >( source ) ) = nullptr; + } + break; + + case E_HandlerOp::HINFO: + { + T_HandlerInfo& i( *reinterpret_cast< T_HandlerInfo* >( dest ) ); + i.size = sizeof( Type ); + i.align = alignof( Type ); + i.mode = E_HandlerMode::HEAP_ALLOC; + } + break; + + case E_HandlerOp::MKHDL: + { + const E_HandlerMode mode( *( (E_HandlerMode*) &source ) ); + if ( mode == E_HandlerMode::IN_PLACE ) { + *( (F_Handler*) dest) = &T_InPlaceHandler< Type >::action; + } else { + *( (F_Handler*) dest) = &T_HeapHandler< Type >::action; + } + } + break; + } +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +inline T_VariantExt< IPS >::T_VariantExt( ) noexcept + : handler_( nullptr ) +{ } + +template< size_t IPS > +inline T_VariantExt< IPS >::~T_VariantExt( ) +{ + clear( ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +inline T_VariantExt< IPS >::T_VariantExt( + T_VariantExt< IPS > const& other ) +{ + copyFrom_( other ); +} + +template< size_t IPS > +inline T_VariantExt< IPS >::T_VariantExt( + T_VariantExt< IPS >&& other ) +{ + moveFrom_( std::move( other ) ); +} + +template< size_t IPS > +template< size_t S > +inline T_VariantExt< IPS >::T_VariantExt( + T_VariantExt< S > const& other ) +{ + copyFrom_( other ); +} + +template< size_t IPS > +template< size_t S > +inline T_VariantExt< IPS >::T_VariantExt( + T_VariantExt< S >&& other ) +{ + moveFrom_( std::move( other ) ); +} + +template< size_t IPS > +inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator=( + T_VariantExt< IPS > const& other ) +{ + clear( ); + copyFrom_( other ); + return *this; +} + +template< size_t IPS > +inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator=( + T_VariantExt< IPS >&& other ) +{ + clear( ); + moveFrom_( std::move( other ) ); + return *this; +} + +template< size_t IPS > +template< size_t S > +inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator=( + T_VariantExt< S > const& other ) +{ + clear( ); + copyFrom_( other ); + return *this; +} + +template< size_t IPS > +template< size_t S > +inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator=( + T_VariantExt< S >&& other ) +{ + clear( ); + moveFrom_( std::move( other ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +template< + typename T , + typename Raw , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > +> inline T_VariantExt< IPS >::T_VariantExt( + T&& value ) +{ + using Handler = T_VariantHelper::T_BestHandlerFor< Raw , StorageSize >; + Handler::init( &storage_ , handler_ , std::forward< T >( value ) ); +} + +template< size_t IPS > +template< + typename T , + typename Raw , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > +> inline T_VariantExt< IPS >::T_VariantExt( + T&& value ) +{ + using Handler = T_VariantHelper::T_BestHandlerFor< Raw , StorageSize >; + Handler::init( &storage_ , handler_ , value ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +template< + typename T , typename Raw , + typename std::enable_if_t< std::is_constructible< Raw , T&& >::value , bool > +> inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator= ( + T&& value ) +{ + clear( ); + using Handler = T_VariantHelper::T_BestHandlerFor< Raw , StorageSize >; + Handler::init( &storage_ , handler_ , std::forward< T >( value ) ); + return *this; +} + +template< size_t IPS > +template< + typename T , typename Raw , + typename std::enable_if_t< !std::is_constructible< Raw , T&& >::value , bool > +> inline T_VariantExt< IPS >& T_VariantExt< IPS >::operator= ( + T&& value ) +{ + clear( ); + using Handler = T_VariantHelper::T_BestHandlerFor< Raw , StorageSize >; + Handler::init( &storage_ , handler_ , value ); + return *this; +} + +template< size_t IPS > +template< typename Type , typename... Arguments > +void T_VariantExt< IPS >::setNew( + Arguments&&... arguments ) +{ + clear( ); + using Handler = T_VariantHelper::T_BestHandlerFor< Type , StorageSize >; + Handler::init( Construct< Type >( ) , &storage_ , handler_ , + std::forward< Arguments >( arguments ) ... ); +} + +/*----------------------------------------------------------------------------*/ + +template< size_t A , size_t B > +inline void swap( + T_VariantExt< A >& a , + T_VariantExt< B >& b ) noexcept +{ + if ( &a == &b || !( a || b ) ) { + return; + } + + using T_A = T_VariantExt< A >; + using T_B = T_VariantExt< B >; + if ( a && b ) { + if ( T_A::StorageSize > T_B::StorageSize ) { + T_A temp( std::move( a ) ); + a = std::move( b ); + b = std::move( temp ); + } else { + T_B temp( std::move( b ) ); + b = std::move( a ); + a = std::move( temp ); + } + } else if ( a ) { + b = std::move( a ); + } else { + a = std::move( b ); + } +} + +template< size_t IPS > +inline void T_VariantExt< IPS >::clear( ) noexcept +{ + if ( handler_ ) { + handler_( T_VariantHelper::E_HandlerOp::DESTRUCT , + nullptr , &storage_ ); + handler_ = nullptr; + } +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +inline std::type_info const& T_VariantExt< IPS >::typeInfo( ) const +{ + if ( handler_ ) { + std::type_info* rv; + handler_( T_VariantHelper::E_HandlerOp::TYPEID , nullptr , &rv ); + return *rv; + } else { + return typeid( void ); + } +} + +template< size_t IPS > +template< typename T > +inline T const& T_VariantExt< IPS >::value( ) const +{ + if ( typeInfo( ) != typeid( T ) ) { + throw std::bad_cast( ); + } + void const* p; + handler_( T_VariantHelper::E_HandlerOp::ACCESS , &storage_ , &p ); + return *static_cast< T const* >( p ); +} + +template< size_t IPS > +template< typename T > +inline T& T_VariantExt< IPS >::value( ) +{ + if ( typeInfo( ) != typeid( T ) ) { + throw std::bad_cast( ); + } + + void* p; + handler_( T_VariantHelper::E_HandlerOp::ACCESS , &storage_ , &p ); + return *static_cast< T* >( p ); +} + +template< size_t IPS > +inline T_VariantExt< IPS >::operator bool( ) const noexcept +{ + return handler_ != nullptr; +} + +template< size_t IPS > +inline bool T_VariantExt< IPS >::operator !( ) const noexcept +{ + return handler_ == nullptr; +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +template< size_t S > +inline void T_VariantExt< IPS >::copyFrom_( + T_VariantExt< S > const& other ) +{ + /* When copying, we want to use the most appropriate storage type, and + * we have to query the source object to determine that, because the + * source could use heap allocation despite being big enough to store + * its contents if it was moved from a smaller variant previously. + */ + if ( other ) { + using M = T_VariantHelper::E_HandlerMode; + using O = T_VariantHelper::E_HandlerOp; + + T_VariantHelper::T_HandlerInfo hi; + char* data; + other.handler_( O::HINFO , nullptr , &hi ); + other.handler_( O::ACCESS , &other.storage_ , &data ); + + const M mode( ( hi.size <= StorageSize && hi.align <= alignof( void* ) ) + ? M::IN_PLACE + : M::HEAP_ALLOC ); + other.handler_( O::MKHDL , (char *) mode , &handler_ ); + handler_( O::COPY , data , &storage_ ); + } else { + handler_ = nullptr; + } +} + +/*----------------------------------------------------------------------------*/ + +template< size_t IPS > +template< + size_t S , + std::enable_if_t< T_VariantHelper::T_CanFit< S , IPS >::value , bool > +> inline void T_VariantExt< IPS >::moveFrom_( + T_VariantExt< S >&& other ) +{ + /* When moving from a variant that has at most the same amount of + * in-place storage, we don't check anything, and simply move it with + * its handler type intact (if it has been allocated on the heap + * already, it's pointless to move it back to in-place storage, even + * if it would fit in the target instance). + */ + handler_ = nullptr; + if ( other ) { + using O = T_VariantHelper::E_HandlerOp; + std::swap( handler_ , other.handler_ ); + handler_( O::MOVE , &other.storage_ , &storage_ ); + } +} + +template< size_t IPS > +template< + size_t S , + std::enable_if_t< !T_VariantHelper::T_CanFit< S , IPS >::value , bool > +> inline void T_VariantExt< IPS >::moveFrom_( + T_VariantExt< S >&& other ) +{ + /* When moving from a variant that has more storage than the target + * instance, there are a few different cases: + * - if the source uses heap-allocated storage, just move it; + * - if the source is in-place and the object can fit inside the + * target, the target will use in-place storage as well; + * - if the source uses in-place storage but the data can't fit + * in the target, we need to move to heap-allocated storage. + */ + if ( other ) { + using M = T_VariantHelper::E_HandlerMode; + using O = T_VariantHelper::E_HandlerOp; + + T_VariantHelper::T_HandlerInfo hi; + other.handler_( O::HINFO , nullptr , &hi ); + if ( hi.mode == M::HEAP_ALLOC || hi.size <= StorageSize ) { + handler_ = other.handler_; + handler_( O::MOVE , &other.storage_ , &storage_ ); + } else { + char* data; + other.handler_( O::ACCESS , &other.storage_ , &data ); + other.handler_( O::MKHDL , (char *) M::HEAP_ALLOC , &handler_ ); + handler_( O::ALLOC , nullptr , &storage_ ); + other.handler_( O::MOVE , &other.storage_ , + *(void**) &storage_ ); + } + other.handler_ = nullptr; + + } else { + handler_ = nullptr; + } +} + + +/*= T_Optional ===============================================================*/ + +template< typename T > +inline constexpr T_Optional< T >::T_Optional( ) noexcept + : present_( false ) +{} + +template< typename T > +inline T_Optional< T >::T_Optional( T_Self_ const& other ) + : present_( other.present_ ) +{ + if ( present_ ) { + ::new (&storage_) T( *other.target( ) ); + } +} + +template< typename T > +inline T_Optional< T >::T_Optional( T_Self_&& other ) noexcept + : T_Optional( ) +{ + swap( *this , other ); +} + +template< typename T > +inline T_Optional< T >::~T_Optional( ) noexcept +{ + clear( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Optional< T >& T_Optional< T >::operator =( T_Self_ const& other ) +{ + clear( ); + present_ = other.present_; + if ( present_ ) { + ::new (&storage_) T( *other.target( ) ); + } + return *this; +} + +template< typename T > +inline T_Optional< T >& T_Optional< T >::operator =( T_Self_&& other ) noexcept +{ + clear( ); + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename Type > +inline M_DECLARE_SWAP( T_Optional< Type > ) +{ + if ( lhs.present_ && rhs.present_ ) { + decltype( lhs.storage_ ) temp; + Type* const p( reinterpret_cast< Type * >( &temp ) ); + + // lhs -> temp + ::new (&temp) Type( std::move( *( lhs.target( ) ) ) ); + lhs.target( )->~Type( ); + + // rhs -> lhs + ::new (&lhs.storage_) Type( std::move( *( rhs.target( ) ) ) ); + rhs.target( )->~Type( ); + + // temp -> rhs + ::new (&rhs.storage_) Type( std::move( *p ) ); + p->~Type( ); + + } else if ( lhs.present_ || rhs.present_ ) { + auto& src( lhs.present_ ? lhs : rhs ); + auto& dst( lhs.present_ ? rhs : lhs ); + ::new (&dst.storage_) Type( std::move( *( src.target( ) ) ) ); + dst.present_ = true; + src.clear( ); + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline T_Optional< T >::T_Optional( T const& value ) + : present_( true ) +{ + ::new (&storage_) T( value ); +} + +template< typename T > +inline T_Optional< T >::T_Optional( T&& value ) noexcept + : present_( true ) +{ + ::new (&storage_) T( std::move( value ) ); +} + +template< typename T > +inline T_Optional< T >& T_Optional< T >::operator =( T const& value ) +{ + clear( ); + present_ = true; + ::new (&storage_) T( value ); + return *this; +} + +template< typename T > +inline T_Optional< T >& T_Optional< T >::operator =( T&& value ) noexcept +{ + clear( ); + present_ = true; + ::new (&storage_) T( std::move( value ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +template< typename... Arguments > +inline T_Optional< T >::T_Optional( Construct< T > , Arguments&&... arguments ) + : present_( true ) +{ + ::new (&storage_) T( std::forward< Arguments&& >( arguments )... ); +} + +template< typename T > +template< typename... Arguments > +inline void T_Optional< T >::setNew( Arguments&&... arguments ) +{ + clear( ); + present_ = true; + ::new (&storage_) T( std::forward< Arguments&& >( arguments )... ); +} + +template< typename T > +inline void T_Optional< T >::clear( ) noexcept +{ + if ( present_ ) { + reinterpret_cast< T* >( &storage_ )->~T( ); + present_ = false; + } +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline constexpr bool T_Optional< T >::present( ) const noexcept +{ + return present_; +} + +template< typename T > +inline constexpr T const* T_Optional< T >::target( ) const noexcept +{ + return present_ ? reinterpret_cast< T const* >( &storage_ ) : nullptr; +} + +template< typename T > +inline constexpr T* T_Optional< T >::target( ) noexcept +{ + return present_ ? reinterpret_cast< T* >( &storage_ ) : nullptr; +} + +template< typename T > +inline T_Optional< T >::operator T const&( ) const +{ + if ( present_ ) { + return *target( ); + } + throw std::bad_cast( ); +} + +template< typename T > +inline T_Optional< T >::operator T& ( ) +{ + if ( present_ ) { + return *target( ); + } + throw std::bad_cast( ); +} + + +/*= T_Union ==================================================================*/ + +template< typename... TL > +template< + typename T0 , + typename std::enable_if_t< std::is_default_constructible< T0 >::value , bool > +> inline T_Union< TL... >::T_Union( ) + : type_( 0 ) +{ + ::new ( &storage_ ) GetType_< 0 >( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline T_Union< TL... >::T_Union( T_Self_ const& other ) + : type_( other.type_ ) +{ + static_assert( CanCopyConsAll< TL... >::value , + "copy construction not supported" ); + other.copy( &storage_ ); +} + +template< typename... TL > +inline T_Union< TL... >::T_Union( T_Self_&& other ) noexcept + : type_( other.type_ ) +{ + static_assert( CanMoveConsAll< TL... >::value , + "move construction not supported" ); + other.move( &storage_ ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline T_Union< TL... >::~T_Union( ) +{ + destroy( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline T_Union< TL... >& T_Union< TL... >::operator =( + T_Self_ const& other ) +{ + static_assert( CanCopyConsAll< TL... >::value , + "copy assignment not supported" ); + destroy( ); + other.copy( &storage_ ); + type_ = other.type_; + return *this; +} + +template< typename... TL > +inline T_Union< TL... >& T_Union< TL... >::operator =( + T_Self_&& other ) noexcept +{ + static_assert( CanMoveConsAll< TL... >::value , + "move assignment not supported" ); + destroy( ); + other.move( &storage_ ); + type_ = other.type_; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline M_DECLARE_SWAP( T_Union< TL... > ) +{ + T_Union< TL... > temp( std::move( rhs ) ); + rhs = std::move( lhs ); + lhs = std::move( temp ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +template< typename T , typename... ArgTypes > +inline T_Union< TL... >::T_Union( Construct< T > , ArgTypes&&... arguments ) + : type_( IndexOf_< T >::value ) +{ + ::new ( &storage_ ) T( std::forward< ArgTypes >( arguments ) ... ); +} + +template< typename... TL > +template< typename T , typename R , + typename std::enable_if_t< std::is_constructible< R , T&& >::value , bool > > +inline T_Union< TL... >::T_Union( T&& x ) + : type_( IndexOf_< R >::value ) +{ + ::new ( &storage_ ) R( std::forward( x ) ); +} + +template< typename... TL > +template< typename T , typename R , + typename std::enable_if_t< !std::is_constructible< R , T&& >::value , bool > > +inline T_Union< TL... >::T_Union( T&& x ) + : type_( IndexOf_< R >::value ) +{ + ::new ( &storage_ ) R( (R const&) x ); +} + +template< typename... TL > +template< typename T , typename R , + typename std::enable_if_t< std::is_constructible< R , T&& >::value , bool > > +inline T_Union< TL... >& T_Union< TL... >::operator =( T&& x ) +{ + destroy( ); + type_ = IndexOf_< R >::value; + ::new ( &storage_ ) R( std::forward( x ) ); + return *this; +} + +template< typename... TL > +template< typename T , typename R , + typename std::enable_if_t< !std::is_constructible< R , T&& >::value , bool > > +inline T_Union< TL... >& T_Union< TL... >::operator =( T&& x ) +{ + destroy( ); + type_ = IndexOf_< R >::value; + ::new ( &storage_ ) R( (R const&) x ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +template< typename T > +inline constexpr bool T_Union< TL... >::hasType( ) const noexcept +{ + return type_ == IndexOf_< T >::value; +} + +template< typename... TL > +inline std::type_info const& T_Union< TL... >::typeInfo( ) const noexcept +{ + static constexpr std::type_info const* types[] = { + &typeid( TL )... + }; + return *types[ type_ ]; +} + +template< typename... TL > +template< typename T > +constexpr T const* T_Union< TL... >::target( ) const noexcept +{ + return hasType< T >( ) ? reinterpret_cast< T const* >( &storage_ ) : nullptr; +} + +template< typename... TL > +template< typename T > +constexpr T* T_Union< TL... >::target( ) noexcept +{ + return hasType< T >( ) ? reinterpret_cast< T* >( &storage_ ) : nullptr; +} + +template< typename... TL > +template< typename T > +T const& T_Union< TL... >::value( ) const +{ + T const* const t( target< T >( ) ); + if ( t == nullptr ) { + throw std::bad_cast( ); + } + return *t; +} + +template< typename... TL > +template< typename T > +T& T_Union< TL... >::value( ) +{ + T* const t( target< T >( ) ); + if ( t == nullptr ) { + throw std::bad_cast( ); + } + return *t; +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline void T_Union< TL... >::destroy( ) +{ + using F_Destroyer_ = void (*)( T_Storage_ * ); + static constexpr F_Destroyer_ destroyers[] = { + destroyImpl< TL >... + }; + destroyers[ type_ ]( &storage_ ); +} + +template< typename... TL > +template< typename T > +inline void T_Union< TL... >::destroyImpl( T_Storage_* storage ) +{ + ( reinterpret_cast< T const* >( storage ) )->~T( ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline void T_Union< TL... >::copy( T_Storage_* to ) const +{ + using F_Copier_ = void (*)( T_Storage_ const* , T_Storage_* ); + static constexpr F_Copier_ copiers[] = { + copyImpl< TL >... + }; + copiers[ type_ ]( &storage_ , to ); +} + +template< typename... TL > +template< typename T > +inline void T_Union< TL... >::copyImpl( T_Storage_ const* from , T_Storage_* to ) +{ + ::new ( reinterpret_cast< char* >( to ) ) T( + *reinterpret_cast< T const* >( from ) ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename... TL > +inline void T_Union< TL... >::move( T_Storage_* to ) +{ + using F_Mover_ = void (*)( T_Storage_* , T_Storage_* ); + static constexpr F_Mover_ movers[] = { + moveImpl< TL >... + }; + movers[ type_ ]( &storage_ , to ); +} + +template< typename... TL > +template< typename T > +inline void T_Union< TL... >::moveImpl( T_Storage_* from , T_Storage_* to ) +{ + ::new ( reinterpret_cast< char* >( to ) ) T( + std::move( *reinterpret_cast< T* >( from ) ) ); +} + + +} // namespace +#endif // _H_LW_LIB_INLINE_TYPES + diff --git a/include/ebcl/inline/Utilities.hh b/include/ebcl/inline/Utilities.hh new file mode 100644 index 0000000..fa0922e --- /dev/null +++ b/include/ebcl/inline/Utilities.hh @@ -0,0 +1,303 @@ +/******************************************************************************/ +/* VARIOUS UTILITIES - INLINE CODE ********************************************/ +/******************************************************************************/ + +#ifndef _H_LW_LIB_INLINE_UTILITIES +#define _H_LW_LIB_INLINE_UTILITIES +#include +namespace lw { + + +template< typename T > +inline constexpr bool IsPowerOf2( T value ) +{ + static_assert( std::is_integral< T >( ) , "integer type only" ); + return value > 0 && ( value & ( value - 1 ) ) == 0; +} + + +/*= ENDIAN DETECTION =========================================================*/ + +inline static constexpr bool IsBigEndian( ) +{ + return E_Endian::NATIVE == E_Endian::BIG; +} + +inline static constexpr bool IsLittleEndian( ) +{ + return E_Endian::NATIVE == E_Endian::LITTLE; +} + +static_assert( IsBigEndian( ) || IsLittleEndian( ) , + "unsupported endianness" ); + + +/*= ENDIAN CONVERSION ========================================================*/ + +template< + typename T , + uint32_t S = sizeof( T ) , + typename = std::enable_if_t< + std::is_integral< T >::value + || std::is_floating_point< T >::value + > + > +struct T_ByteSwapper_ { }; + + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_ByteSwapper_< T , 1 > +{ + static constexpr T swap( T value ) + { + return value; + } +}; + + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_ByteSwapper_< T , 2 > +{ + static T swap( T value ) + { + union { T v; + uint16_t i; + } u = { value }; + u.i = __builtin_bswap16( u.i ); + return u.v; + } +}; + + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_ByteSwapper_< T , 4 > +{ + static T swap( T value ) + { + union { T v; + uint32_t i; + } u = { value }; + u.i = __builtin_bswap32( u.i ); + return u.v; + } +}; + + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_ByteSwapper_< T , 8 > +{ + static T swap( T value ); +}; + + +template< typename T > +inline T T_ByteSwapper_< T , 8 >::swap( T value ) +{ + union { T v; + uint64_t i; + } u = { value }; + u.i = __builtin_bswap64( u.i ); + return u.v; +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_LittleEndian< T , E_Endian::LITTLE > +{ + static T convert( T value ) + { + return value; + } +}; + + +template< typename T > +struct T_LittleEndian< T , E_Endian::BIG > +{ + static T convert( T value ) + { + return T_ByteSwapper_< T >::swap( value ); + } +}; + + +/*----------------------------------------------------------------------------*/ + +template< typename T > +struct T_BigEndian< T , E_Endian::LITTLE > +{ + static T convert( T value ) + { + return T_ByteSwapper_< T >::swap( value ); + } +}; + + +template< typename T > +struct T_BigEndian< T , E_Endian::BIG > +{ + static T convert( T value ) + { + return value; + } +}; + + +/*= COMPARATORS AND SORTING ==================================================*/ + +template< typename T > +inline int T_Comparator< T >::compare( T const& a , T const& b ) +{ + if ( a < b ) return -1; + if ( a > b ) return 1; + return 0; +} + +/*----------------------------------------------------------------------------*/ + +// T_Sorter_ - "Plumbing" for Sort +struct T_Sorter_ +{ + typedef std::function< void ( uint8_t* a , uint8_t* b ) > F_Swap; + typedef std::function< int ( uint8_t const* a , uint8_t const* b ) > F_Cmp; + static void sort( uint8_t* data , uint32_t itemSize , uint32_t items , F_Swap swap , F_Cmp cmp ); +}; + + +template< typename T > +void Sort( T* array , uint32_t items , F_Comparator< T > comparator ) +{ + T_Sorter_::sort( reinterpret_cast< uint8_t* >( array ) , + sizeof( T ) , items , + []( uint8_t* a , uint8_t* b ) { + using std::swap; + swap( *reinterpret_cast< T* >( a ) , + *reinterpret_cast< T* >( b ) ); + } , + [comparator]( uint8_t const* a , uint8_t const* b ) { + return comparator( + *reinterpret_cast< T const* >( a ) , + *reinterpret_cast< T const* >( b ) ); + } ); +} + + +/*= HASHING ==================================================================*/ + +template< typename T , int S > +inline uint32_t T_HashFunction< T , S >::hash( T const& item ) +{ + return HashData( reinterpret_cast< uint8_t const* >( &item ) , sizeof( T ) ); +} + +/*----------------------------------------------------------------------------*/ + +template< typename T > +inline uint32_t ComputeHash( T const& item ) +{ + return T_HashFunction< T >::hash( item ); +} + +/*----------------------------------------------------------------------------*/ +// Hash function specializations for data sizes 1, 2, 4 and 8. + +template< typename T > +struct T_HashFunction< T , 1 > +{ + static uint32_t hash( T const& item ) + { + return *reinterpret_cast< uint8_t const* >( &item ); + } +}; + + +template< typename T > +struct T_HashFunction< T , 2 > +{ + static uint32_t hash( T const& item ) + { + return *reinterpret_cast< uint16_t const* >( &item ); + } +}; + + +template< typename T > +struct T_HashFunction< T , 4 > +{ + static uint32_t hash( T const& item ) + { + return *reinterpret_cast< uint32_t const* >( &item ); + } +}; + + +template< typename T > +struct T_HashFunction< T , 8 > +{ + static uint32_t hash( T const& item ) + { + uint64_t v = *reinterpret_cast< uint64_t const* >( &item ); + return ( v >> 32 ) ^ v; + } +}; + + +/*= PRIVATE IMPLEMENTATION BASE ==============================================*/ + +inline A_PrivateImplementation::A_PrivateImplementation( void* p , T_Destructor_ destructor ) + : p_( p ) , piDestructor_( destructor ) +{ } + +template< typename T > +inline A_PrivateImplementation::A_PrivateImplementation( T* p ) + : A_PrivateImplementation( p , []( void* ptr ) { + reinterpret_cast< T* >( ptr )->~T( ); + } ) +{ } + +inline A_PrivateImplementation::A_PrivateImplementation( A_PrivateImplementation&& other ) noexcept + : p_( other.p_ ) , piDestructor_( other.piDestructor_ ) +{ + other.p_ = nullptr; +} + +inline A_PrivateImplementation& A_PrivateImplementation::operator=( A_PrivateImplementation&& other ) noexcept +{ + callPrivateDestructor( ); + p_ = other.p_; + piDestructor_ = other.piDestructor_; + other.p_ = nullptr; + return *this; +} + +inline A_PrivateImplementation::~A_PrivateImplementation( ) +{ + callPrivateDestructor( ); +} + +template< typename T > +inline T& A_PrivateImplementation::p( ) const +{ + return *reinterpret_cast< T* >( p_ ); +} + +inline void A_PrivateImplementation::callPrivateDestructor( ) noexcept +{ + if ( p_ && piDestructor_ ) { + piDestructor_( p_ ); + ::operator delete( p_ ); + } +} + + +} +#endif // _H_LW_LIB_INLINE_UTILITIES diff --git a/src/Console-Unix.hh b/src/Console-Unix.hh new file mode 100644 index 0000000..db05496 --- /dev/null +++ b/src/Console-Unix.hh @@ -0,0 +1,667 @@ +/******************************************************************************/ +/* CONSOLE SUPPORT - UNIX IMPLEMENTATION **************************************/ +/******************************************************************************/ + +#include +#include +#include +#include + + +namespace { + +// List of unsupported terminals +char const* const BadTerminals_[] = { + "dumb" , "cons25" , "emacs" , + nullptr +}; + +// Extended input sequences +char const* const InputSequences_[] = { + "A" , // Up + "B" , // Down + "C" , // Right + "D" , // Left + "H" , // Home + "F" , // End + "3~" , // Delete + "1;5C" , // C-Right + "1;5D" , // C-Left + "15~" , // F5 + "17~" , // F6 + "18~" , // F7 + "19~" , // F8 + "20~" , // F9 + "21~" , // F10 + "23~" , // F11 + "24~" , // F12 + "1;2P" , // S-F1 + "1;2Q" , // S-F2 + "1;2R" , // S-F3 + "1;2S" , // S-F4 + "15;2~" , // S-F5 + "17;2~" , // S-F6 + "18;2~" , // S-F7 + "19;2~" , // S-F8 + "20;2~" , // S-F9 + "21;2~" , // S-F10 + "23;2~" , // S-F11 + "24;2~" , // S-F12 + "1;5P" , // C-F1 + "1;5Q" , // C-F2 + "1;5R" , // C-F3 + "1;5S" , // C-F4 + "15;5~" , // C-F5 + "17;5~" , // C-F6 + "18;5~" , // C-F7 + "19;5~" , // C-F8 + "20;5~" , // C-F9 + "21;5~" , // C-F10 + "23;5~" , // C-F11 + "24;5~" , // C-F12 + "1;6P" , // C-S-F1 + "1;6Q" , // C-S-F2 + "1;6R" , // C-S-F3 + "1;6S" , // C-S-F4 + "15;6~" , // C-S-F5 + "17;6~" , // C-S-F6 + "18;6~" , // C-S-F7 + "19;6~" , // C-S-F8 + "20;6~" , // C-S-F9 + "21;6~" , // C-S-F10 + "23;6~" , // C-S-F11 + "24;6~" , // C-S-F12 + nullptr +}; +const lw::E_ConsoleKey InputKeys_[] = { + lw::E_ConsoleKey::UP , + lw::E_ConsoleKey::DOWN , + lw::E_ConsoleKey::RIGHT , + lw::E_ConsoleKey::LEFT , + lw::E_ConsoleKey::HOME , + lw::E_ConsoleKey::END , + lw::E_ConsoleKey::DELETE , + lw::E_ConsoleKey::WNEXT , + lw::E_ConsoleKey::WPREV , + lw::E_ConsoleKey::F5 , + lw::E_ConsoleKey::F6 , + lw::E_ConsoleKey::F7 , + lw::E_ConsoleKey::F8 , + lw::E_ConsoleKey::F9 , + lw::E_ConsoleKey::F10 , + lw::E_ConsoleKey::F11 , + lw::E_ConsoleKey::F12 , + lw::E_ConsoleKey::S_F1 , + lw::E_ConsoleKey::S_F2 , + lw::E_ConsoleKey::S_F3 , + lw::E_ConsoleKey::S_F4 , + lw::E_ConsoleKey::S_F5 , + lw::E_ConsoleKey::S_F6 , + lw::E_ConsoleKey::S_F7 , + lw::E_ConsoleKey::S_F8 , + lw::E_ConsoleKey::S_F9 , + lw::E_ConsoleKey::S_F10 , + lw::E_ConsoleKey::S_F11 , + lw::E_ConsoleKey::S_F12 , + lw::E_ConsoleKey::C_F1 , + lw::E_ConsoleKey::C_F2 , + lw::E_ConsoleKey::C_F3 , + lw::E_ConsoleKey::C_F4 , + lw::E_ConsoleKey::C_F5 , + lw::E_ConsoleKey::C_F6 , + lw::E_ConsoleKey::C_F7 , + lw::E_ConsoleKey::C_F8 , + lw::E_ConsoleKey::C_F9 , + lw::E_ConsoleKey::C_F10 , + lw::E_ConsoleKey::C_F11 , + lw::E_ConsoleKey::C_F12 , + lw::E_ConsoleKey::CS_F1 , + lw::E_ConsoleKey::CS_F2 , + lw::E_ConsoleKey::CS_F3 , + lw::E_ConsoleKey::CS_F4 , + lw::E_ConsoleKey::CS_F5 , + lw::E_ConsoleKey::CS_F6 , + lw::E_ConsoleKey::CS_F7 , + lw::E_ConsoleKey::CS_F8 , + lw::E_ConsoleKey::CS_F9 , + lw::E_ConsoleKey::CS_F10 , + lw::E_ConsoleKey::CS_F11 , + lw::E_ConsoleKey::CS_F12 , +}; + +// Various control codes +const char CCGetTermSizeStart_[] = "\033[s\033[99999C\033[99999A"; +const char CCGetTermSizeEnd_[] = "\033[u"; +const char CCGetCursorPos_[] = "\033[6n"; +const char CCClearScreen_[] = "\033[H\033[2J"; +const char CCNextLine_[] = "\n\033[99999D\033[K"; +const char CCClearLine_[] = "\033[99999D\033[K"; +const char CCClearLineUp_[] = "\033[99999D\033[K\033[A"; + +// Are we running in an unsupported terminal? +inline bool IsBadTerminal_( ) +{ + char const* const t( getenv( "TERM" ) ); + if ( t == nullptr ) { + return true; + } + for ( auto i = 0 ; BadTerminals_[ i ] ; i ++ ) { + if ( !strcasecmp( t , BadTerminals_[ i ] ) ) { + return true; + } + } + return false; +} + +/*----------------------------------------------------------------------------*/ + +class T_ConsoleImpl_ +{ + private: + bool ok_; + termios initialTerm_; + lw::E_ConsoleInteractionMode mode_; + bool failed_; + bool interrupted_; + + int sizePolling_; + uint32_t rows_ , cols_; + + lw::T_StringBuilder styleSeq_; + uint8_t keyBuf_[ 16 ]; + uint32_t kbPos_; + + public: + T_ConsoleImpl_( ); + ~T_ConsoleImpl_( ); + + bool initConsole( ); + void shutdownConsole( ); + bool failed( ) const; + + bool resized( uint32_t& rows , uint32_t& cols ) const; + bool getTerminalSize( uint32_t& rows , uint32_t& cols ); + bool getCursorPosition( uint32_t& column , uint32_t& row ); + + bool readKey( lw::E_ConsoleKey& key , uint32_t& character ); + + bool clearScreen( ); + void setTextStyle( uint8_t style , lw::T_TextColor color ); + void printCharacter( lw::T_Character c ); + void nextLine( ); + void clearLine( ); + void clearLines( uint32_t count ); + void moveCursor( int32_t x , int32_t y ); + + private: + bool initTTY( ); + bool checkInput( lw::E_ConsoleKey& key , uint32_t& character ); + + bool getTerminalSize( ); + bool writeSequence( char const* sequence ); + bool writeSequence( char const* sequence , uint32_t size ); +}; + +/*----------------------------------------------------------------------------*/ + +T_ConsoleImpl_::T_ConsoleImpl_( ) +{ + // Buffer size: + // * "ESC[0m" always => 4 + // * 2 per enabled style => 6 + // * color: "38;2;" + 3 unsigned 8-bit numbers + 2 ";" separators => 16 + // That would be 26, but let's go for 32 instead + styleSeq_.ensureCapacity( 32 ); +} + +T_ConsoleImpl_::~T_ConsoleImpl_( ) +{ + shutdownConsole( ); +} + +bool T_ConsoleImpl_::initConsole( ) +{ + failed_ = false; + kbPos_ = 0; + ok_ = isatty( STDIN_FILENO ) && !IsBadTerminal_( ) && initTTY( ); + if ( ok_ && getTerminalSize( ) ) { + sizePolling_ = 0; + } + return ok_ && !failed_; +} + +void T_ConsoleImpl_::shutdownConsole( ) +{ + if ( ok_ ) { + setTextStyle( 0 , lw::E_TextColor::WHITE ); + tcsetattr( STDIN_FILENO , TCSAFLUSH , &initialTerm_ ); + } +} + +inline bool T_ConsoleImpl_::failed( ) const +{ + return failed_; +} + +bool T_ConsoleImpl_::resized( uint32_t& rows , uint32_t& cols ) const +{ + if ( cols_ != cols || rows_ != rows ) { + cols = cols_; + rows = rows_; + return true; + } + return false; +} + +bool T_ConsoleImpl_::getTerminalSize( uint32_t& rows , uint32_t& cols ) +{ + if ( !getTerminalSize( ) ) { + return false; + } + rows = rows_; + cols = cols_; + return true; +} + +/*----------------------------------------------------------------------------*/ + +bool T_ConsoleImpl_::readKey( lw::E_ConsoleKey& key , uint32_t& character ) +{ + if ( failed_ ) { + return false; + } + + do { + assert( kbPos_ < sizeof( keyBuf_ ) ); + const auto n( read( STDIN_FILENO , &keyBuf_[ kbPos_ ] , 1 ) ); + if ( n != 1 ) { + failed_ = failed_ || ( n == -1 ); + if ( n == 0 && kbPos_ == 0 ) { + sizePolling_ = ( sizePolling_ + 1 ) % 20; + if ( sizePolling_ == 0 ) { + getTerminalSize( ); + } + } + return false; + } + kbPos_ ++; + } while ( !checkInput( key , character ) ); + kbPos_ = 0; + return true; +} + +bool T_ConsoleImpl_::checkInput( lw::E_ConsoleKey& key , uint32_t& character ) +{ + assert( kbPos_ > 0 ); + + const uint8_t first( keyBuf_[ 0 ] ); + if ( kbPos_ == 1 ) { + // Escape or UTF-8 input + if ( first == 0x1b || first > 0x7f ) { + return false; + } + + switch ( first ) { + case 0x01: // Ctrl+A + key = lw::E_ConsoleKey::HOME; + return true; + + case 0x02: // Ctrl+B + key = lw::E_ConsoleKey::LEFT; + return true; + + case 0x03: // Ctrl+C + key = lw::E_ConsoleKey::INTERRUPT; + return true; + + case 0x04: // Ctrl+D + key = lw::E_ConsoleKey::EXIT; + return true; + + case 0x05: // Ctrl+E + key = lw::E_ConsoleKey::END; + return true; + + case 0x06: // Ctrl+F + key = lw::E_ConsoleKey::RIGHT; + return true; + + case 0x08: // Ctrl+H + case 0x7f: + key = lw::E_ConsoleKey::BACKSPACE; + return true; + + case 0x09: // Ctrl+I/Tab + key = lw::E_ConsoleKey::CHARACTER; + character = ' '; + return true; + + case 0x0b: // Ctrl+K + key = lw::E_ConsoleKey::KILL_REST; + return true; + + case 0x0c: // Ctrl+L + key = lw::E_ConsoleKey::CLEAR; + return true; + + case 0x0d: // Ctrl+M/Enter + key = lw::E_ConsoleKey::ENTER; + return true; + + case 0x0e: // Ctrl+N + key = lw::E_ConsoleKey::DOWN; + return true; + + case 0x10: // Ctrl+P + key = lw::E_ConsoleKey::UP; + return true; + + case 0x15: // Ctrl+U + key = lw::E_ConsoleKey::KILL_LINE; + return true; + + case 0x17: // Ctrl+W + key = lw::E_ConsoleKey::KILL_WORD; + return true; + + case 0x19: // Ctrl+Y + key = lw::E_ConsoleKey::PASTE; + return true; + } + + // Skip other control characters + if ( first < 32 ) { + kbPos_ = 0; + return false; + } + + key = lw::E_ConsoleKey::CHARACTER; + character = first; + return true; + } + + // Check for UTF-8 input + if ( first != 0x1b ) { + uint32_t l( 0 ); + if ( !lw::UTF8BufferInfo( (char const*) keyBuf_ , kbPos_ , l ) ) { + if ( kbPos_ > 4 ) { + kbPos_ = 0; + } + return false; + } + if ( l != 1 ) { + kbPos_ = 0; + return false; + } else { + key = lw::E_ConsoleKey::CHARACTER; + character = lw::UTF8GetCodepoint( (char const*) keyBuf_ ); + return true; + } + } + + // Escape sequences + const uint8_t second( keyBuf_[ 1 ] ); + if ( kbPos_ == 2 ) { + switch ( second ) { + case 0x7f: + key = lw::E_ConsoleKey::KILL_WORD; + return true; + + case 'f': + case 'F': + key = lw::E_ConsoleKey::WNEXT; + return true; + + case 'b': + case 'B': + key = lw::E_ConsoleKey::WPREV; + return true; + + default: + kbPos_ = 0; + case 'O': + case '[': + return false; + } + } + + // ESC-O-x sequences + if ( second == 'O' ) { + switch ( keyBuf_[ 2 ] ) { + case 'F': + key = lw::E_ConsoleKey::END; + return true; + case 'H': + key = lw::E_ConsoleKey::HOME; + return true; + case 'P': + key = lw::E_ConsoleKey::F1; + return true; + case 'Q': + key = lw::E_ConsoleKey::F2; + return true; + case 'R': + key = lw::E_ConsoleKey::F3; + return true; + case 'S': + key = lw::E_ConsoleKey::F4; + return true; + } + kbPos_ = 0; + return false; + } + + // Extended sequences - ESC-[, ends with ~ or uppercase letter + const uint8_t last( keyBuf_[ kbPos_ - 1 ] ); + if ( last != '~' && ( last < 'A' || last > 'Z' ) ) { + if ( kbPos_ == sizeof( keyBuf_ ) - 1 ) { + kbPos_ = 0; + } + return false; + } + + // Check the list + uint32_t idx( 0 ); + keyBuf_[ kbPos_ ] = 0; + while ( InputSequences_[ idx ] != nullptr ) { + if ( !strcmp( (char const*) keyBuf_ + 2 , InputSequences_[ idx ] ) ) { + key = InputKeys_[ idx ]; + return true; + } + idx ++; + } + kbPos_ = 0; + return false; +} + +/*----------------------------------------------------------------------------*/ + +bool T_ConsoleImpl_::clearScreen( ) +{ + return writeSequence( CCClearScreen_ , sizeof( CCClearScreen_ ) - 1 ); +} + +void T_ConsoleImpl_::setTextStyle( uint8_t style , lw::T_TextColor color ) +{ + styleSeq_.clear( ); + styleSeq_ << "\033[0"; + if ( ( style & uint8_t( lw::E_TextStyle::BOLD ) ) != 0 ) { + styleSeq_ << ";1"; + } + if ( ( style & uint8_t( lw::E_TextStyle::ITALIC ) ) != 0 ) { + styleSeq_ << ";3"; + } + if ( ( style & uint8_t( lw::E_TextStyle::UNDERLINE ) ) != 0 ) { + styleSeq_ << ";4"; + } + if ( color.type == lw::E_TextColor::CUSTOM ) { + styleSeq_ << ";38;2;" << color.r + << ';' << color.g + << ';' << color.b; + } else { + styleSeq_ << ';' << ( 30 + uint8_t( color.type ) ); + } + styleSeq_ << 'm'; + writeSequence( styleSeq_.data( ) , styleSeq_.size( ) ); +} + +void T_ConsoleImpl_::printCharacter( lw::T_Character c ) +{ + char buf[ 8 ]; + uint32_t l( UTF8PutCodepoint( buf , 7 , c ) ); + if ( l == 0 ) { + return; + } + writeSequence( buf , l ); +} + +void T_ConsoleImpl_::nextLine( ) +{ + writeSequence( CCNextLine_ , sizeof( CCNextLine_ ) - 1 ); +} + +void T_ConsoleImpl_::clearLine( ) +{ + writeSequence( CCClearLine_ , sizeof( CCClearLine_ ) - 1 ); +} + +void T_ConsoleImpl_::clearLines( uint32_t count ) +{ + assert( count > 0 ); + while ( --count ) { + writeSequence( CCClearLineUp_ , sizeof( CCClearLineUp_ ) - 1 ); + } + clearLine( ); +} + +void T_ConsoleImpl_::moveCursor( int32_t x , int32_t y ) +{ + styleSeq_.clear( ); + if ( x ) { + styleSeq_ << "\033[" + << ( x > 0 ? x : -x ) + << ( x > 0 ? 'C' : 'D' ); + } + if ( y ) { + styleSeq_ << "\033[" + << ( y > 0 ? y : -y ) + << ( y > 0 ? 'B' : 'A' ); + } + if ( styleSeq_.size( ) ) { + writeSequence( styleSeq_.data( ) , styleSeq_.size( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_ConsoleImpl_::initTTY( ) +{ + if ( tcgetattr( STDIN_FILENO , &initialTerm_ ) == -1 ) { + failed_ = true; + return false; + } + + termios rawTerm( initialTerm_ ); + rawTerm.c_iflag &= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON ); + rawTerm.c_oflag &= ~OPOST; + rawTerm.c_cflag |= CS8; + rawTerm.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG ); + rawTerm.c_cc[ VMIN ] = 0; + rawTerm.c_cc[ VTIME ] = 0; + + if ( tcsetattr( STDIN_FILENO , TCSAFLUSH , &rawTerm ) >= 0 ) { + return getTerminalSize( ); + } else { + failed_ = true; + return false; + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_ConsoleImpl_::getTerminalSize( ) +{ + // Try obtaining the size using ioctl + winsize ws; + if ( ioctl( STDIN_FILENO , TIOCGWINSZ , &ws ) != -1 && ws.ws_col != 0 ) { + rows_ = ws.ws_row; + cols_ = ws.ws_col; + return true; + } + + // Otherwise, we need to move the cursor to the bottom/right corner, then + // get its position. + return writeSequence( CCGetTermSizeStart_ , sizeof( CCGetTermSizeStart_ ) - 1 ) + && getCursorPosition( cols_ , rows_ ) + && writeSequence( CCGetTermSizeEnd_ , sizeof( CCGetTermSizeEnd_ ) - 1 ); +} + +bool T_ConsoleImpl_::getCursorPosition( uint32_t& column , uint32_t& row ) +{ + if ( failed_ || tcflush( STDIN_FILENO , TCIFLUSH ) != 0 ) { + failed_ = true; + return false; + } + if ( !writeSequence( CCGetCursorPos_ , sizeof( CCGetCursorPos_ ) - 1 ) ) { + return false; + } + + char buf[ 32 ]; + size_t i( 0 ); + while ( i < sizeof( buf ) ) { + if ( read( STDIN_FILENO , buf + i , 1 ) != 1 ) { + i = sizeof( buf ); + break; + } + if ( buf[ i ] == 'R' ) { + break; + } + i ++; + } + + if ( i == sizeof( buf ) || i < 6 || buf[ 0 ] != 27 || buf[ 1 ] != '[' ) { + failed_ = true; + return false; + } + + buf[ i ] = 0; + if ( sscanf( buf + 2 , "%d;%d" , &row , &column ) != 2 ) { + failed_ = true; + return false; + } + return true; +} + +#if 0 +bool T_ConsoleImpl_::writeSequence( char const* sequence ) +{ + if ( failed_ ) { + return false; + } + + const int n( strlen( sequence ) ); + if ( write( STDOUT_FILENO , sequence , n ) != n ) { + failed_ = true; + return false; + } + + return true; +} +#endif + +bool T_ConsoleImpl_::writeSequence( char const* sequence , uint32_t size ) +{ + if ( failed_ ) { + return false; + } + + if ( write( STDOUT_FILENO , sequence , size ) != size ) { + failed_ = true; + return false; + } + + return true; +} + + +} diff --git a/src/Console.cc b/src/Console.cc new file mode 100644 index 0000000..2c217b5 --- /dev/null +++ b/src/Console.cc @@ -0,0 +1,1346 @@ +/******************************************************************************/ +/* CONSOLE SUPPORT ************************************************************/ +/******************************************************************************/ + +#include +#include +using namespace lw; + + +/*= T_TextBuilder ============================================================*/ + +T_TextBuilder::T_TextBuilder( ) noexcept + : sb_( ) , chunks_( 16 ) , flushedLength_( 0 ) , + cStyle_( 0 ) , cColor_( E_TextColor::WHITE ) +{ } + +/*----------------------------------------------------------------------------*/ + +T_TextBuilder::T_TextBuilder( T_TextBuilder&& other ) noexcept + : T_TextBuilder( ) +{ + swap( *this , other ); +} + +T_TextBuilder& T_TextBuilder::operator =( T_TextBuilder&& other ) noexcept +{ + sb_ = std::move( other.sb_ ); + chunks_ = std::move( other.chunks_ ); + flushedLength_ = other.flushedLength_; + cStyle_ = other.cStyle_; + cColor_ = other.cColor_; + other.flushedLength_ = other.cStyle_ = 0; + other.cColor_ = E_TextColor::WHITE; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_SWAP( T_TextBuilder ) +{ + using std::swap; + swap( lhs.sb_ , rhs.sb_ ); + swap( lhs.chunks_ , rhs.chunks_ ); + swap( lhs.flushedLength_ , rhs.flushedLength_ ); + swap( lhs.cStyle_ , rhs.cStyle_ ); + swap( lhs.cColor_ , rhs.cColor_ ); +} + +/*----------------------------------------------------------------------------*/ + +void T_TextBuilder::clear( ) +{ + chunks_.clear( ); + sb_.clear( ); + flushedLength_ = 0; + cStyle_ = 0; + cColor_ = E_TextColor::WHITE; +} + +void T_TextBuilder::reset( bool style , bool color ) +{ + if ( style ) { + setStyle( 0 ); + } + if ( color ) { + setColor( E_TextColor::WHITE ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_TextBuilder::setStyle( uint8_t style ) +{ + if ( style == cStyle_ ) { + return; + } + flush( ); + cStyle_ = style; +} + +void T_TextBuilder::setColor( T_TextColor color ) +{ + if ( color == cColor_ ) { + return; + } + flush( ); + cColor_ = color; +} + +/*----------------------------------------------------------------------------*/ + +T_TextBuilder& T_TextBuilder::append( + T_Text const& text ) noexcept +{ + flush( ); + const uint32_t nChunks( text.chunks( ) ); + for ( uint32_t i = 0 ; i < nChunks ; i ++ ) { + chunks_.add( text.chunk( i ) ); + } + sb_ << text.string( ); + flushedLength_ = sb_.size( ); + return *this; +} + +T_TextBuilder& T_TextBuilder::append( + T_TextBuilder const& text ) noexcept +{ + flush( ); + chunks_.addAll( text.chunks_ ); + sb_ << text.sb_; + flushedLength_ += text.flushedLength_; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void T_TextBuilder::flush( ) +{ + const auto len( sb_.length( ) ); + if ( flushedLength_ == len ) { + return; + } + + T_TextChunk& chunk( chunks_.addNew( ) ); + chunk.length = len - flushedLength_; + chunk.style = cStyle_; + chunk.color = cColor_; + flushedLength_ = len; +} + + +/*= T_Text ===================================================================*/ + +T_Text::T_Text( ) + : string_( ) , chunks_( 1 ) +{ } + +T_Text::T_Text( T_TextBuilder const& builder ) + : string_( builder.sb_ ) , chunks_( std::max( 1u , builder.chunksCount( ) ) ) +{ + initChunks( builder ); +} + +T_Text::T_Text( T_TextBuilder&& builder ) + : string_( std::move( builder.sb_ ) ) , + chunks_( std::max( 1u , builder.chunksCount( ) ) ) +{ + initChunks( builder ); + builder.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_SWAP( T_Text ) +{ + using std::swap; + swap( lhs.string_ , rhs.string_ ); + swap( lhs.chunks_ , rhs.chunks_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_TextChunk const& T_Text::chunkAt( uint32_t pos ) const +{ + uint32_t sz( 0 ); + int idx( -1 ); + while ( pos >= sz ) { + idx ++; + sz += chunks_[ idx ].length; + } + return chunks_[ idx ]; +} + +/*----------------------------------------------------------------------------*/ + +void T_Text::initChunks( T_TextBuilder const& builder ) +{ + chunks_.addAll( builder.chunks_ ); + if ( chunks_.size( ) < chunks_.growth( ) ) { + const auto len( string_.length( ) ); + T_TextChunk& chunk( chunks_.addNew( ) ); + chunk.length = len - builder.flushedLength_; + chunk.style = builder.cStyle_; + chunk.color = builder.cColor_; + } +} + + +/*= T_ConsoleLineState =======================================================*/ + +T_ConsoleLineState::T_ConsoleLineState( ) + : characters_( GROWTH ) +{ + reset( ); +} + +void T_ConsoleLineState::reset( ) +{ + size_ = 0; + pos_ = 0; + historyIndex_ = 0; +} + +void T_ConsoleLineState::setContents( T_String const& contents , uint32_t index ) +{ + const uint32_t len( contents.length( ) ); + ensureCapacity( len ); + + T_StringIterator it( contents ); + while ( !it.atEnd( ) ) { + characters_[ it.index( ) ] = T_Character( it ); + it.next( ); + } + + historyIndex_ = index; + size_ = pos_ = len; +} + +/*----------------------------------------------------------------------------*/ + +T_String T_ConsoleLineState::getContents( ) const +{ + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < size_ ; i ++ ) { + sb << T_Character( characters_[ i ] ); + } + return T_String( std::move( sb ) ); +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsoleLineState::prevWord( ) +{ + while ( pos_ > 0 && characters_[ pos_ - 1 ] == ' ' ) { + pos_ --; + } + while ( pos_ > 0 && characters_[ pos_ - 1 ] != ' ' ) { + pos_ --; + } +} + +void T_ConsoleLineState::nextWord( ) +{ + while ( pos_ < size_ && characters_[ pos_ ] == ' ' ) { + pos_ ++; + } + while ( pos_ < size_ && characters_[ pos_ ] != ' ' ) { + pos_ ++; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsoleLineState::removeCharacter( ) +{ + if ( pos_ == size_ ) { + return; + } + size_--; + if ( pos_ < size_ ) { + memmove( &characters_[ pos_ ] , &characters_[ pos_ + 1 ] , + ( size_ - pos_ ) * sizeof( uint32_t ) ); + } +} + +void T_ConsoleLineState::removeWord( T_Buffer< uint32_t >& cpBuf , uint32_t& cpLength ) +{ + const uint32_t end( pos_ ); + prevWord( ); + if ( pos_ == end ) { + return; + } + + const uint32_t rmCount( end - pos_ ); + cpLength = rmCount; + if ( cpBuf.size( ) < rmCount ) { + cpBuf.resize( rmCount ); + } + memcpy( &cpBuf[ 0 ] , &characters_[ pos_ ] , rmCount * sizeof( uint32_t ) ); + + size_ -= rmCount; + if ( pos_ < size_ ) { + memmove( &characters_[ pos_ ] , &characters_[ end ] , + ( size_ - pos_ ) * sizeof( uint32_t ) ); + } +} + +void T_ConsoleLineState::removeRestOfLine( T_Buffer< uint32_t >& cpBuf , uint32_t& cpLength ) +{ + if ( pos_ == size_ ) { + return; + } + + const uint32_t rmCount( size_ - pos_ ); + cpLength = rmCount; + if ( cpBuf.size( ) < rmCount ) { + cpBuf.resize( rmCount ); + } + memcpy( &cpBuf[ 0 ] , &characters_[ pos_ ] , rmCount * sizeof( uint32_t ) ); + + size_ = pos_; +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsoleLineState::insert( T_Character c ) +{ + ensureCapacity( size_ + 1 ); + if ( pos_ < size_ ) { + memmove( &characters_[ pos_ + 1 ] , &characters_[ pos_ ] , + ( size_ - pos_ ) * sizeof( uint32_t ) ); + } + size_ ++; + characters_[ pos_ ++ ] = c; +} + +void T_ConsoleLineState::insert( T_String const& s ) +{ + const uint32_t length( s.length( ) ); + + ensureCapacity( size_ + length ); + if ( pos_ < size_ ) { + memmove( &characters_[ pos_ + length ] , &characters_[ pos_ ] , + ( size_ - pos_ ) * sizeof( uint32_t ) ); + } + + T_StringIterator it( s ); + while ( !it.atEnd( ) ) { + const T_Character c( it ); + const uint32_t value( c.isControl( ) ? 32 : uint32_t( c ) ); + characters_[ pos_ + it.index( ) ] = value; + it.next( ); + } + + size_ += length; + pos_ += length; +} + +void T_ConsoleLineState::insert( T_Buffer< uint32_t > const& buffer , uint32_t length ) +{ + if ( length == 0 ) { + return; + } + + ensureCapacity( size_ + length ); + if ( pos_ < size_ ) { + memmove( &characters_[ pos_ + length ] , &characters_[ pos_ ] , + ( size_ - pos_ ) * sizeof( uint32_t ) ); + } + size_ += length; + memcpy( &characters_[ pos_ ] , &buffer[ 0 ] , length * sizeof( uint32_t ) ); + pos_ += length; +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsoleLineState::ensureCapacity( uint32_t needed ) +{ + const uint32_t extra( needed + GROWTH - 1 ); + const uint32_t actual( extra - extra % GROWTH ); + if ( actual > characters_.size( ) ) { + characters_.resize( actual ); + } +} + + +/*= T_ConsoleControl_ =========================================================*/ + +// Information sent to the console +namespace { +struct T_ConsoleControl_ +{ + enum { + C_TERMINATE , // End thread + C_SETMODE , // Set interaction mode + C_SETPROMPT , // Set prompt text + C_SETEXIT , // Set text for Ctrl+D + C_SETPROGRESS , // Set progress information + C_SETSHORTCUT , // Set a keyboard shortcut + C_SETHISTSIZE , // Set maximal history size + C_SETHISTORY , // Replace history contents + C_PRINT , // Print text + } type; + T_Variant data; +}; +} // namespace + + +/*= T_ConsolePrivate_ ========================================================*/ + +// Load the implementation +#ifdef _WIN32 +# error "Not implemented!" +#else +# include "Console-Unix.hh" +#endif + +namespace { +struct T_ConsolePrivate_ +{ + enum { + C_TERMINATED , // Thread stopped + C_OUTPUT , // Output mode + C_INPUT , // Input mode + C_INPUT_READY , // Input finished + C_PROGRESS , // Progress display + C_INTERRUPTED // Progress display, interrupted + } state_; + + T_Text progress_; + + T_Text prompt_; + T_String exitLine_; + T_String line_; + + T_ConsoleLineState input_; + uint32_t ilLines_ , ilLastPromptLine_; + uint32_t ilCursorX_ , ilCursorY_; + + T_Buffer< uint32_t > cpBuffer_; + uint32_t cpLength_; + + T_String shortcuts_[ T_Console::NbShortcuts ]; + + T_Array< T_String > history_; + uint32_t maxHistory_; + uint32_t historyPos_; + + OP_Thread thread_; + + T_Mutex controlMutex_; + T_Condition controlCond_; + T_RingBuffer< T_ConsoleControl_ > control_; + + T_ConsoleImpl_ impl_; + uint32_t width_ , height_; + + // -------------------------------------------------------------------- + + T_ConsolePrivate_( ); + ~T_ConsolePrivate_( ); + + E_ConsoleInteractionMode interactionMode( ) const; + + // -------------------------------------------------------------------- + + bool start( ); + void terminate( ); + void resetData( ); + + void mainLoop( ); + bool handleControlMessage( T_ConsoleControl_& o ); + + // -------------------------------------------------------------------- + + void printText( T_Text const& text , bool line ); + void printProgress( ); + + // -------------------------------------------------------------------- + + void inputKey( E_ConsoleKey key , T_Character character ); + void clearInputLine( ); + void printInputLine( ); + void moveInputCursor( ); + + // -------------------------------------------------------------------- + + void setHistorySize( uint32_t newMax ); + void setHistory( T_Array< T_String >& newData ); + + void getTextSize( T_String const& text , uint32_t& lines , uint32_t& lastLineLength ); + + void updateHistory( T_String const& string ); + void newHistoryEntry( ); + + // -------------------------------------------------------------------- + + void sendToThread( T_ConsoleControl_&& o ); + bool receiveFromMain( T_ConsoleControl_& output ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_ConsolePrivate_::T_ConsolePrivate_( ) + : maxHistory_( T_Console::DefaultHistory ) , + historyPos_( 0 ) +{ + history_.addNew( ); +} + +inline T_ConsolePrivate_::~T_ConsolePrivate_( ) +{ + terminate( ); +} + +inline E_ConsoleInteractionMode T_ConsolePrivate_::interactionMode( ) const +{ + switch ( state_ ) { + case C_INPUT: + case C_INPUT_READY: + return E_ConsoleInteractionMode::INPUT; + case C_PROGRESS: + case C_INTERRUPTED: + return E_ConsoleInteractionMode::PROGRESS; + default: + return E_ConsoleInteractionMode::OUTPUT; + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_ConsolePrivate_::start( ) +{ + if ( impl_.initConsole( ) ) { + resetData( ); + state_ = C_OUTPUT; + thread_ = NewOwned< T_Thread >( [this]() { mainLoop( ); } ); + return true; + } else { + return false; + } +} + +void T_ConsolePrivate_::terminate( ) +{ + if ( thread_ ) { + T_ConsoleControl_ out; + out.type = T_ConsoleControl_::C_TERMINATE; + sendToThread( std::move( out ) ); + thread_->join( ); + thread_.clear( ); + resetData( ); + } +} + +void T_ConsolePrivate_::resetData( ) +{ + control_.free( ); + line_ = T_String( ); + prompt_ = T_Text( ); + exitLine_ = T_String( ); + progress_ = T_Text( ); + input_.reset( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::mainLoop( ) +{ + E_ConsoleKey key( E_ConsoleKey::CHARACTER ); + uint32_t character( 0 ); + T_ConsoleControl_ o; + bool keepRunning( true ); + + impl_.getTerminalSize( height_ , width_ ); + while ( keepRunning && !impl_.failed( ) ) { + if ( impl_.readKey( key , character ) ) { + if ( key == E_ConsoleKey::CLEAR ) { + impl_.clearScreen( ); + if ( interactionMode( ) == E_ConsoleInteractionMode::PROGRESS ) { + printProgress( ); + } else if ( state_ == C_INPUT ) { + printInputLine( ); + } + + } else if ( state_ == C_PROGRESS && key == E_ConsoleKey::INTERRUPT ) { + state_ = C_INTERRUPTED; + + } else if ( state_ == C_INPUT ) { + inputKey( key , character ); + } + + } else if ( receiveFromMain( o ) ) { + keepRunning = handleControlMessage( o ); + } else if ( impl_.resized( height_ , width_ ) ) { + if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + printProgress( ); + } + } + } + + impl_.shutdownConsole( ); +} + +bool T_ConsolePrivate_::handleControlMessage( T_ConsoleControl_& o ) +{ + switch ( o.type ) { + + case T_ConsoleControl_::C_TERMINATE: + if ( state_ == C_INPUT ) { + clearInputLine( ); + } else if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + impl_.clearLine( ); + } + state_ = C_TERMINATED; + return false; + + case T_ConsoleControl_::C_SETMODE: + { + const auto m( o.data.value< E_ConsoleInteractionMode >( ) ); + if ( m == E_ConsoleInteractionMode::OUTPUT ) { + if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + impl_.clearLine( ); + } else if ( state_ == C_INPUT ) { + clearInputLine( ); + } + state_ = C_OUTPUT; + } else if ( m == E_ConsoleInteractionMode::INPUT ) { + if ( state_ != C_INPUT_READY ) { + if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + impl_.clearLine( ); + } + input_.reset( ); + printInputLine( ); + state_ = C_INPUT; + } + } else { + assert( m == E_ConsoleInteractionMode::PROGRESS ); + if ( state_ == C_INPUT ) { + clearInputLine( ); + } + state_ = C_PROGRESS; + printProgress( ); + } + } + break; + + case T_ConsoleControl_::C_SETPROMPT: + if ( state_ == C_INPUT ) { + clearInputLine( ); + } + prompt_ = o.data.value< T_Text >( ); + if ( state_ == C_INPUT ) { + printInputLine( ); + } + break; + + case T_ConsoleControl_::C_SETEXIT: + exitLine_ = o.data.value< T_String >( ); + break; + + case T_ConsoleControl_::C_SETPROGRESS: + progress_ = o.data.value< T_Text >( ); + if ( interactionMode( ) == E_ConsoleInteractionMode::PROGRESS ) { + printProgress( ); + } + break; + + case T_ConsoleControl_::C_SETSHORTCUT: + { + auto const& p( o.data.value< std::pair< int , T_String > >( ) ); + shortcuts_[ p.first ] = p.second; + } + break; + + case T_ConsoleControl_::C_SETHISTSIZE: + setHistorySize( o.data.value< uint32_t >( ) ); + break; + + case T_ConsoleControl_::C_SETHISTORY: + setHistory( o.data.value< T_Array< T_String > >( ) ); + break; + + case T_ConsoleControl_::C_PRINT: + if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + impl_.clearLine( ); + } else if ( state_ == C_INPUT ) { + clearInputLine( ); + } + printText( o.data.value< T_Text >( ) , true ); + if ( state_ == C_PROGRESS || state_ == C_INTERRUPTED ) { + printProgress( ); + } else if ( state_ == C_INPUT ) { + printInputLine( ); + } + break; + } + + return true; +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::printText( T_Text const& text , bool line ) +{ + T_StringIterator it( text.string( ) ); + uint32_t chunkLeft( 0 ); + uint32_t chunkIndex( 0 ); + bool lastWasNL( false ); + uint32_t cols( 0 ); + while ( !( it.atEnd( ) || impl_.failed( ) ) ) { + if ( chunkLeft == 0 ) { + T_TextChunk const& chunk( text.chunk( chunkIndex ++ ) ); + chunkLeft = chunk.length; + impl_.setTextStyle( chunk.style , chunk.color ); + } + + T_Character c( it ); + lastWasNL = ( c == '\n' ); + if ( !c.isControl( ) ) { + impl_.printCharacter( c ); + cols ++; + } else if ( c == '\t' ) { + do { + impl_.printCharacter( ' ' ); + cols ++; + } while ( cols % 8 != 0 ); + } else if ( c == '\n' ) { + impl_.nextLine( ); + cols = 0; + } + it.next( ); + chunkLeft --; + } + if ( !lastWasNL && line ) { + impl_.nextLine( ); + } +} + +void T_ConsolePrivate_::printProgress( ) +{ + impl_.getTerminalSize( height_ , width_ ); + impl_.clearLine( ); + + T_StringIterator it( progress_.string( ) ); + uint32_t chunkLeft( 0 ); + uint32_t chunkIndex( 0 ); + uint32_t cols( 0 ); + while ( !( it.atEnd( ) || impl_.failed( ) || cols == width_ ) ) { + if ( chunkLeft == 0 ) { + T_TextChunk const& chunk( progress_.chunk( chunkIndex ++ ) ); + chunkLeft = chunk.length; + impl_.setTextStyle( chunk.style , chunk.color ); + } + + T_Character c( it ); + if ( !c.isControl( ) ) { + impl_.printCharacter( c ); + cols ++; + } else if ( c == '\t' ) { + do { + impl_.printCharacter( ' ' ); + cols ++; + } while ( cols % 8 != 0 && cols < width_ ); + } else if ( c == '\n' ) { + break; + } + it.next( ); + chunkLeft --; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::inputKey( E_ConsoleKey key , T_Character character ) +{ + bool cursorMoved( true ) , stringChanged( false ); + switch ( key ) { + + case E_ConsoleKey::CLEAR: + // This should never happen, but better safe than sorry + return; + + case E_ConsoleKey::ENTER: + input_.toEnd( ); + moveInputCursor( ); + line_ = input_.getContents( ); + { + const uint32_t hs( history_.size( ) ); + if ( line_ && ( hs == 1 || history_[ ( historyPos_ - 1 + hs ) % hs ] != line_ ) ) { + history_[ historyPos_ ] = line_; + newHistoryEntry( ); + } else { + history_[ historyPos_ ] = T_String( ); + } + } + input_.reset( ); + impl_.nextLine( ); + state_ = C_INPUT_READY; + return; + + case E_ConsoleKey::INTERRUPT: + if ( input_.size( ) != 0 ) { + input_.reset( ); + impl_.nextLine( ); + printInputLine( ); + } + return; + + case E_ConsoleKey::CHARACTER: + input_.insert( character ); + stringChanged = true; + break; + + case E_ConsoleKey::UP: + { + const auto hs( history_.size( ) ) , hi( input_.historyIndex( ) ); + if ( hi == hs - 1 ) { + cursorMoved = false; + } else { + if ( input_.size( ) ) { + updateHistory( input_.getContents( ) ); + } + input_.setContents( history_[ ( historyPos_ - hi - 1 + hs ) % hs ] , hi + 1 ); + stringChanged = true; + } + } + break; + + case E_ConsoleKey::DOWN: + { + const auto hs( history_.size( ) ) , hi( input_.historyIndex( ) ); + if ( hi == 0 ) { + cursorMoved = false; + } else { + if ( input_.size( ) ) { + updateHistory( input_.getContents( ) ); + } + input_.setContents( history_[ ( historyPos_ - hi + 1 + hs ) % hs ] , hi - 1 ); + stringChanged = true; + } + } + break; + + case E_ConsoleKey::LEFT: + if ( input_.pos( ) == 0 ) { + cursorMoved = false; + } else { + input_.left( ); + } + break; + + case E_ConsoleKey::RIGHT: + if ( input_.pos( ) == input_.size( ) ) { + cursorMoved = false; + } else { + input_.right( ); + } + break; + + case E_ConsoleKey::WPREV: + if ( input_.pos( ) == 0 ) { + cursorMoved = false; + } else { + input_.prevWord( ); + } + break; + + case E_ConsoleKey::WNEXT: + if ( input_.pos( ) == input_.size( ) ) { + cursorMoved = false; + } else { + input_.nextWord( ); + } + break; + + case E_ConsoleKey::HOME: + if ( input_.pos( ) == 0 ) { + cursorMoved = false; + } else { + input_.toStart( ); + } + break; + + case E_ConsoleKey::END: + if ( input_.pos( ) == input_.size( ) ) { + cursorMoved = false; + } else { + input_.toEnd( ); + } + break; + + case E_ConsoleKey::EXIT: + if ( input_.size( ) == 0 && exitLine_ ) { + input_.setContents( exitLine_ , 0 ); + clearInputLine( ); + printInputLine( ); + impl_.nextLine( ); + line_ = exitLine_; + state_ = C_INPUT_READY; + return; + } + case E_ConsoleKey::DELETE: + if ( input_.pos( ) == input_.size( ) ) { + cursorMoved = false; + } else { + input_.removeCharacter( ); + stringChanged = true; + } + break; + + case E_ConsoleKey::BACKSPACE: + if ( input_.pos( ) == 0 ) { + cursorMoved = false; + } else { + input_.left( ); + input_.removeCharacter( ); + stringChanged = true; + } + break; + + case E_ConsoleKey::KILL_WORD: + if ( input_.pos( ) == 0 ) { + cursorMoved = false; + } else { + input_.removeWord( cpBuffer_ , cpLength_ ); + stringChanged = true; + } + break; + + case E_ConsoleKey::KILL_REST: + if ( input_.pos( ) == input_.size( ) ) { + cursorMoved = false; + } else { + input_.removeRestOfLine( cpBuffer_ , cpLength_ ); + stringChanged = true; + } + break; + + case E_ConsoleKey::KILL_LINE: + if ( 0 == input_.size( ) ) { + cursorMoved = false; + } else { + input_.toStart( ); + input_.removeRestOfLine( cpBuffer_ , cpLength_ ); + stringChanged = true; + } + break; + + case E_ConsoleKey::PASTE: + if ( 0 == cpLength_ ) { + cursorMoved = false; + } else { + input_.insert( cpBuffer_ , cpLength_ ); + stringChanged = true; + } + break; + + default: + { + assert( key >= E_ConsoleKey::F1 ); + const T_String shortcut( shortcuts_[ int( key ) - int( E_ConsoleKey::F1 ) ] ); + if ( shortcut ) { + input_.insert( shortcut ); + stringChanged = true; + } else { + cursorMoved = false; + } + } + break; + } + + if ( stringChanged ) { + clearInputLine( ); + printInputLine( ); + } else if ( cursorMoved ) { + moveInputCursor( ); + } +} + +void T_ConsolePrivate_::clearInputLine( ) +{ + if ( ilLines_ != 0 ) { + impl_.moveCursor( 0 , ilLines_ - ilCursorY_ - 1 ); + impl_.clearLines( ilLines_ ); + ilLines_ = 0; + } +} + +void T_ConsolePrivate_::printInputLine( ) +{ + uint32_t promptLines; + getTextSize( prompt_.string( ) , promptLines , ilLastPromptLine_ ); + + const uint32_t iSize( input_.size( ) ); + const uint32_t inputFull( ilLastPromptLine_ + iSize + 1 ); + const uint32_t inputLines( inputFull / width_ + + ( inputFull % width_ != 0 ? 1 : 0 ) ); + const uint32_t totalLines( inputLines + promptLines - 1 ); + ilLines_ = totalLines; + + if ( prompt_.string( ) ) { + printText( prompt_ , false ); + } else { + impl_.setTextStyle( 0 , E_TextColor::WHITE ); + } + for ( uint32_t i = 0 ; i < iSize ; i ++ ) { + impl_.printCharacter( input_[ i ] ); + } + impl_.printCharacter( ' ' ); + + ilCursorY_ = totalLines - 1; + ilCursorX_ = inputFull % width_; + if ( ilCursorX_ == 0 ) { + ilCursorX_ = width_ - 1; + } + moveInputCursor( ); +} + +void T_ConsolePrivate_::moveInputCursor( ) +{ + if ( ilLines_ == 0 ) { + return; + } + const uint32_t iPos( input_.pos( ) ); + const int32_t line( ( iPos + ilLastPromptLine_ ) / width_ ); + const int32_t column( ( iPos + ilLastPromptLine_ ) % width_ ); + impl_.moveCursor( column - ilCursorX_ , line - ilCursorY_ ); + ilCursorX_ = column; + ilCursorY_ = line; +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::setHistorySize( uint32_t newMax ) +{ + assert( state_ != C_INPUT ); + assert( newMax > 0 ); + + if ( newMax == maxHistory_ ) { + return; + } + maxHistory_ = newMax; + + const uint32_t hs( history_.size( ) ); + if ( hs > maxHistory_ || historyPos_ != hs - 1 ) { + const uint32_t cs( std::min( hs , maxHistory_ ) ); + const uint32_t start( historyPos_ + hs - cs + 1 ); + T_Array< T_String > nh; + for ( uint32_t i = 0 ; i < cs ; i ++ ) { + nh.add( std::move( history_[ ( start + i ) % hs ] ) ); + } + swap( history_ , nh ); + historyPos_ = cs - 1; + } +} + +void T_ConsolePrivate_::setHistory( T_Array< T_String >& newData ) +{ + assert( state_ != C_INPUT ); + const auto sz( newData.size( ) ); + if ( sz > maxHistory_ ) { + history_ = newData.moveRange( sz - maxHistory_ , sz - 1 ); + } else { + swap( history_ , newData ); + if ( sz == 0 ) { + history_.addNew( ); + historyPos_ = 0; + } else { + historyPos_ = sz - 1; + } + } + if ( history_[ historyPos_ ] ) { + newHistoryEntry( ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::getTextSize( T_String const& text , uint32_t& lines , uint32_t& lastLineLength ) +{ + T_StringIterator it( text ); + lines = 1; + lastLineLength = 0; + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + + if ( c == '\n' ) { + lines ++; + lastLineLength = 0; + } else if ( c == '\t' ) { + lastLineLength += 8 - ( lastLineLength % 8 ); + } else if ( !c.isControl( ) ) { + lastLineLength ++; + } + + while ( lastLineLength >= width_ ) { + lines ++; + lastLineLength -= width_; + } + } +} + +/*----------------------------------------------------------------------------*/ + +inline void T_ConsolePrivate_::updateHistory( T_String const& string ) +{ + const uint32_t hs( history_.size( ) ); + assert( hs > 0 ); + history_[ ( historyPos_ - input_.historyIndex( ) + hs ) % hs ] = string; +} + +void T_ConsolePrivate_::newHistoryEntry( ) +{ + if ( history_.size( ) < maxHistory_ ) { + assert( historyPos_ == history_.size( ) - 1 ); + historyPos_ ++; + history_.addNew( ); + } else { + historyPos_ = ( historyPos_ + 1 ) % maxHistory_; + history_[ historyPos_ ] = T_String( ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ConsolePrivate_::sendToThread( T_ConsoleControl_&& o ) +{ + assert( thread_ ); + T_ScopeLock lock( controlMutex_ ); + control_.put( std::move( o ) ); + controlCond_.notify_one( ); +} + +bool T_ConsolePrivate_::receiveFromMain( T_ConsoleControl_& output ) +{ + using namespace std::chrono_literals; + T_ExclusiveLock lock( controlMutex_ ); + controlCond_.wait_for( lock , 20ms , [this]() { + return control_.size( ) != 0; + } ); + return control_.readNext( output ); +} + + +/*= T_Console ================================================================*/ + +T_Console::T_Console( ) + : A_PrivateImplementation( new T_ConsolePrivate_( ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_Console::mode( E_ConsoleMode mode ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( mode == E_ConsoleMode::DISABLED ) { + pi.terminate( ); + return true; + } + return isActive( ) || pi.start( ) || mode == E_ConsoleMode::OPTIONAL; +} + +bool T_Console::isActive( ) const +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + return pi.thread_ && pi.state_ != pi.C_TERMINATED; +} + +/*----------------------------------------------------------------------------*/ + +void T_Console::interactionMode( E_ConsoleInteractionMode mode ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETMODE; + o.data = mode; + pi.sendToThread( std::move( o ) ); + } +} + +E_ConsoleInteractionMode T_Console::interactionMode( ) const +{ + return p< T_ConsolePrivate_ >( ).interactionMode( ); +} + +void T_Console::wait( ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + bool hasMessages( true ); + while ( isActive( ) && hasMessages ) { + { + T_ScopeLock lock( pi.controlMutex_ ); + hasMessages = pi.control_.size( ) != 0; + } + if ( hasMessages ) { + std::this_thread::yield( ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_Console::putLine( T_Text text ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_PRINT; + o.data = std::move( text ); + pi.sendToThread( std::move( o ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_Console::setPrompt( T_Text prompt ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETPROMPT; + o.data = std::move( prompt ); + pi.sendToThread( std::move( o ) ); + } +} + +void T_Console::setExitLine( T_String const& line ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETEXIT; + o.data = line; + pi.sendToThread( std::move( o ) ); + } +} + +bool T_Console::getLine( T_String& line ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.state_ == pi.C_INPUT_READY && pi.line_ != "\001" ) { + line = pi.line_; + pi.line_ = T_String::Pooled( "\001" ); + + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETMODE; + o.data = E_ConsoleInteractionMode::OUTPUT; + pi.sendToThread( std::move( o ) ); + return true; + } + return false; +} + +/*----------------------------------------------------------------------------*/ + +void T_Console::setShortcut( E_ConsoleKey key , T_String const& value ) +{ + assert( key >= E_ConsoleKey::F1 ); + auto& pi( p< T_ConsolePrivate_ >( ) ); + const int index( int( key ) - int( E_ConsoleKey::F1 ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETSHORTCUT; + o.data = std::make_pair< int , T_String >( int( index ) , T_String( value ) ); + pi.sendToThread( std::move( o ) ); + } else { + pi.shortcuts_[ index ] = value; + } +} + +T_Array< T_String > T_Console::getShortcuts( ) +{ + wait( ); + auto& pi( p< T_ConsolePrivate_ >( ) ); + T_Array< T_String > shortcuts( NbShortcuts ); + for ( auto i = 0 ; i < NbShortcuts ; i ++ ) { + shortcuts.add( pi.shortcuts_[ i ] ); + } + return shortcuts; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t T_Console::maxHistorySize( ) const +{ + return p< T_ConsolePrivate_ >( ).maxHistory_; +} + +T_Array< T_String > T_Console::history( ) +{ + wait( ); + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.state_ == pi.C_INPUT ) { + throw X_InvalidConsoleState( ); + } + + const uint32_t hs( pi.history_.size( ) ); + if ( hs == pi.historyPos_ + 1 ) { + return pi.history_; + } + + T_Array< T_String > copy( hs ); + for ( uint32_t i = 0 ; i < hs ; i ++ ) { + copy.add( pi.history_[ ( pi.historyPos_ + i ) % hs ] ); + } + return copy; +} + + +void T_Console::maxHistorySize( uint32_t mhs ) +{ + assert( mhs > 0 ); + wait( ); + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.state_ == pi.C_INPUT ) { + throw X_InvalidConsoleState( ); + } + if ( isActive( ) ) { + T_ConsoleControl_ c; + c.type = T_ConsoleControl_::C_SETHISTSIZE; + c.data = mhs; + pi.sendToThread( std::move( c ) ); + } else { + pi.setHistorySize( mhs ); + } +} + +void T_Console::history( T_Array< T_String > const& lines ) +{ + wait( ); + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.state_ == pi.C_INPUT ) { + throw X_InvalidConsoleState( ); + } + if ( isActive( ) ) { + T_ConsoleControl_ c; + c.type = T_ConsoleControl_::C_SETHISTORY; + c.data = lines; + pi.sendToThread( std::move( c ) ); + } else { + T_Array< T_String > lcopy( lines ); + pi.setHistory( lcopy ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_Console::setProgressInfo( T_Text info ) +{ + auto& pi( p< T_ConsolePrivate_ >( ) ); + if ( pi.thread_ ) { + T_ConsoleControl_ o; + o.type = T_ConsoleControl_::C_SETPROGRESS; + o.data = std::move( info ); + pi.sendToThread( std::move( o ) ); + } +} + +bool T_Console::wasInterrupted( ) const +{ + return p< T_ConsolePrivate_ >( ).state_ == T_ConsolePrivate_::C_INTERRUPTED; +} diff --git a/src/ConsoleLogWriter.cc b/src/ConsoleLogWriter.cc new file mode 100644 index 0000000..e59d979 --- /dev/null +++ b/src/ConsoleLogWriter.cc @@ -0,0 +1,83 @@ +#include +using namespace lw; + + +namespace { + +char const* const V_Name_ = "console"; +inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); } + +} + + +/*= T_ConsoleLogWriterFactory ================================================*/ + +T_ConsoleLogWriterFactory::T_ConsoleLogWriterFactory( T_Console& console ) + : A_LogWriterFactory( Name_( ) ) , console_( console ) +{ } + +RP_LogWriterConfiguration T_ConsoleLogWriterFactory::createConfiguration( T_String const& name ) const +{ + RP_LogWriterConfiguration p( new T_LogWriterConfiguration( Name_( ) ) ); + p->setName( name ); + return p; +} + +OP_LogWriter T_ConsoleLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const +{ + T_ConsoleLogWriter* p( new T_ConsoleLogWriter( std::move( configuration ) , console_ ) ); + return OwnRawPointer( p ); +} + + +/*= T_ConsoleLogWriter =======================================================*/ + +T_ConsoleLogWriter::T_ConsoleLogWriter( OP_LogWriterConfiguration&& configuration , T_Console& console ) + : A_LogWriter( std::move( configuration ) ) , console_( console ) +{ } + +void T_ConsoleLogWriter::log( T_LogTimestamp const& timestamp , + E_LogLevel level , T_LogPath const& path , + T_LogStringData const& data , uint32_t size ) +{ + using namespace std::chrono; + + char timeBuffer[ 128 ]; + std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) ); + std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) ); + const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 ); + + T_TextBuilder sb; + + sb << timeBuffer << '.'; + if ( ms < 100 ) { + sb << '0'; + if ( ms < 10 ) { + sb << '0'; + } + } + sb << ms << ' ' << path.toString( ) << " - " + << E_TextStyle::UNDERLINE; + switch ( level ) { + case E_LogLevel::TRACE: + case E_LogLevel::DEBUG: + case E_LogLevel::INFO: + sb << E_TextColor::CYAN; + break; + case E_LogLevel::NOTICE: + break; + case E_LogLevel::WARNING: + sb << E_TextColor::YELLOW; + break; + case E_LogLevel::CRITICAL: + sb << E_TextStyle::BOLD; + case E_LogLevel::ERROR: + sb << E_TextColor::RED; + break; + } + sb << level; + sb.reset( ); + sb << " - " << T_String( &( (*data) [ 0 ] ) , size ); + + console_.putLine( std::move( sb ) ); +} diff --git a/src/CwdFileLogger.cc b/src/CwdFileLogger.cc new file mode 100644 index 0000000..452fbeb --- /dev/null +++ b/src/CwdFileLogger.cc @@ -0,0 +1,153 @@ +/******************************************************************************/ +/* LOGGING SYSTEM - BUILT-IN LOGGERS - NON-VFS FILE LOG WRITER ****************/ +/******************************************************************************/ + +#include +#include +using namespace lw; + + +namespace { + + +char const* const V_Name_ = "preinit-file"; +inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); } + +bool CFLCPath_( T_SRDParserData const& data ) +{ + auto const& ptok( (*data.input)[ 1 ] ); + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setPath( ptok.stringValue( ) ); + return true; +} + +bool CFLCAppend_( T_SRDParserData const& data ) +{ + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setAppend( true ); + return true; +} + +bool CFLCTruncate_( T_SRDParserData const& data ) +{ + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_CWDFileLogWriterCfg* >( lconf ) )->setAppend( false ); + return true; +} + +} + + +/*= T_CWDFileLogWriterFactory ==================================================*/ + +T_CWDFileLogWriterFactory::T_CWDFileLogWriterFactory( ) + : A_LogWriterFactory( Name_( ) ) +{ } + +RP_LogWriterConfiguration T_CWDFileLogWriterFactory::createConfiguration( T_String const& name ) const +{ + RP_LogWriterConfiguration p( new T_CWDFileLogWriterCfg( ) ); + p->setName( name ); + return p; +} + +OP_LogWriter T_CWDFileLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const +{ + T_CWDFileLogWriter* p( new T_CWDFileLogWriter( std::move( configuration ) ) ); + return OwnRawPointer( p ); +} + +void T_CWDFileLogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& main ) const +{ + using namespace lw::SRD; + main << ( Rule( ) << "file" << Text( ) << CFLCPath_ ); + main << ( Rule( ) << "append" << CFLCAppend_ ); + main << ( Rule( ) << "truncate" << CFLCTruncate_ ); +} + + +/*= T_CWDFileLogWriterCfg ======================================================*/ + +T_CWDFileLogWriterCfg::T_CWDFileLogWriterCfg( ) + : T_LogWriterConfiguration( Name_( ) ) +{ } + +T_CWDFileLogWriterCfg::T_CWDFileLogWriterCfg( T_CWDFileLogWriterCfg const& source ) + : T_LogWriterConfiguration( source ) , path_( source.path_ ) , + append_( source.append_ ) +{ } + +OP_LogWriterConfiguration T_CWDFileLogWriterCfg::clone( ) +{ + T_CWDFileLogWriterCfg* ptr( new T_CWDFileLogWriterCfg( *this ) ); + return OwnRawPointer( ptr ); +} + +void T_CWDFileLogWriterCfg::check( T_SRDErrors& errors , T_SRDList const& input ) +{ + T_LogWriterConfiguration::check( errors , input ); + if ( !path_ ) { + errors.add( "no file selected" , input[ 0 ] ); + } +} + + +/*= T_CWDFileLogWriter =========================================================*/ + +T_CWDFileLogWriter::T_CWDFileLogWriter( OP_LogWriterConfiguration&& configuration ) + : A_LogWriter( std::move( configuration ) ) +{ } + +void T_CWDFileLogWriter::log( T_LogTimestamp const& timestamp , + E_LogLevel level , T_LogPath const& path , + T_LogStringData const& data , uint32_t size ) +{ + using namespace std::chrono; + + char timeBuffer[ 128 ]; + std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) ); + std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) ); + const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 ); + + T_StringBuilder sb; + + sb << timeBuffer << '.'; + if ( ms < 100 ) { + sb << '0'; + if ( ms < 10 ) { + sb << '0'; + } + } + sb << ms << ' ' << path.toString( ) << " - " << level << ": "; + sb.append( &( (*data) [ 0 ] ) , size ); + sb << '\n'; + + auto const& cfg( configuration< T_CWDFileLogWriterCfg >( ) ); + auto const& p( cfg.path( ) ); + if ( !file_ ) { + file_ = NewOwned< T_File >( p , cfg.append( ) ? E_FileMode::READ_WRITE : E_FileMode::OVERWRITE ); + if ( !file_ ) { + disable( ); + return; + } + + try { + file_->open( ); + } catch ( X_StreamError const& ) { + disable( ); + file_.clear( ); + return; + } + } + + try { + file_->position( 0 , true ); + file_->write( sb.data( ) , sb.size( ) ); + file_->flush( ); + } catch ( X_StreamError ) { + disable( ); + file_.clear( ); + return; + } +} + diff --git a/src/DynLib.cc b/src/DynLib.cc new file mode 100644 index 0000000..8a255ee --- /dev/null +++ b/src/DynLib.cc @@ -0,0 +1,135 @@ +/******************************************************************************/ +/* DYNAMIC LIBRARIES **********************************************************/ +/******************************************************************************/ + +#include +#include +using namespace lw; + +#ifdef _WIN32 +# error "Not implemented" +#endif + + +/*= T_DynLibLinux_ ===========================================================*/ + +#include +#include + +namespace { +struct T_DynLibLinux_ +{ + T_Buffer< char > path; + T_String error; + void* lib; + + explicit T_DynLibLinux_( T_String const& path ) noexcept; + explicit T_DynLibLinux_( char const* const path ) noexcept; + ~T_DynLibLinux_( ) noexcept; + + bool load( ) noexcept; + void unload( ) noexcept; + + bool isLoaded( ) const noexcept; + + void* getSymbol( char const* const symbol ) noexcept; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_DynLibLinux_::T_DynLibLinux_( + T_String const& path ) noexcept + : path( path.toOSString( ) ) , lib( nullptr ) +{ } + +inline T_DynLibLinux_::T_DynLibLinux_( + char const* const path ) noexcept + : path( path , strlen( path ) ) , lib( nullptr ) +{ } + +inline T_DynLibLinux_::~T_DynLibLinux_( ) noexcept +{ + unload( ); +} + +/*----------------------------------------------------------------------------*/ + +inline bool T_DynLibLinux_::load( ) noexcept +{ + if ( lib != nullptr ) { + return true; + } + lib = dlopen( &path[ 0 ] , RTLD_LOCAL | RTLD_NOW ); + error = lib ? T_String{} : T_String{ dlerror( ) }; + return lib != nullptr; +} + +inline void T_DynLibLinux_::unload( ) noexcept +{ + if ( lib != nullptr ) { + dlclose( lib ); + lib = nullptr; + } +} + +inline bool T_DynLibLinux_::isLoaded( ) const noexcept +{ + return lib != nullptr; +} + +/*----------------------------------------------------------------------------*/ + +inline void* T_DynLibLinux_::getSymbol( + char const* const symbol ) noexcept +{ + assert( lib != nullptr ); + void* const ptr( dlsym( lib , symbol ) ); + error = ( ptr == nullptr ) ? T_String{ dlerror( ) } : T_String{}; + return ptr; +} + + +/*= T_DynLib =================================================================*/ + +using T_DynLibImpl_ = T_DynLibLinux_; // FIXME + +T_DynLib::T_DynLib( + T_String const& name ) + : A_PrivateImplementation( new T_DynLibImpl_( name ) ) +{ } + +T_DynLib::T_DynLib( + char const* const name ) + : A_PrivateImplementation( new T_DynLibImpl_( name ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_DynLib::load( ) +{ + return p< T_DynLibImpl_ >( ).load( ); +} + +void T_DynLib::unload( ) +{ + p< T_DynLibImpl_ >( ).unload( ); +} + +bool T_DynLib::isLoaded( ) const noexcept +{ + return p< T_DynLibImpl_ >( ).isLoaded( ); +} + +T_String T_DynLib::lastError( ) const noexcept +{ + return p< T_DynLibImpl_ >( ).error; +} + +/*----------------------------------------------------------------------------*/ + +void* T_DynLib::getRawSymbol( + char const* const name ) const noexcept +{ + return p< T_DynLibImpl_ >( ).getSymbol( name ); +} diff --git a/src/Files.cc b/src/Files.cc new file mode 100644 index 0000000..933204c --- /dev/null +++ b/src/Files.cc @@ -0,0 +1,326 @@ +/******************************************************************************/ +/* FILES **********************************************************************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +/*= T_File ==================================================================*/ + +T_File::T_File( ) + : path_( ) , file_( nullptr ) +{ } + +T_File::T_File( T_String const& path , E_FileMode mode ) + : path_( path ) , mode_( mode ) , file_( nullptr ) +{ } + +T_File::T_File( T_File&& other ) noexcept + : T_File( ) +{ + swap( *this , other ); +} + +T_File& T_File::operator= ( T_File&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*---------------------------------------------------------------------------*/ + +void T_File::open( ) +{ + if ( file_ != nullptr ) { + return; + } + + // Copy file path to C string + const T_Buffer< char > path( path_.toOSString( ) ); + + // Select the right mode + char const* mode; + switch ( mode_ ) { + case E_FileMode::READ_ONLY: + mode = "rb"; + break; + + case E_FileMode::READ_WRITE: + mode = "rb+"; + break; + + case E_FileMode::OVERWRITE: + mode = "wb+"; + break; + } + + // Open the file +#ifdef WIN32_ + file_ = _wfopen( ( wchar_t const* ) &path[ 0 ] , mode ); +#else + file_ = fopen( &path[ 0 ] , mode ); +#endif + if ( file_ == nullptr ) { + throw X_StreamError( errno ); + } + + // Get initial size + if ( fseek( file_ , 0 , SEEK_END ) != 0 ) { + auto err( errno ); + close( ); + throw X_StreamError( err ); + } + off_t sz( ftell( file_ ) ); + if ( sz < 0 || fseek( file_ , 0 , SEEK_SET ) != 0 ) { + auto err( errno ); + close( ); + throw X_StreamError( err ); + } + pos_ = 0; + size_ = sz; +} + +void T_File::close( ) noexcept +{ + if ( file_ != nullptr ) { + fclose( file_ ); + file_ = nullptr; + } +} + +/*---------------------------------------------------------------------------*/ + +void T_File::position( size_t position , bool fromEnd ) +{ + open( ); + const auto rv( fromEnd + ? fseek( file_ , -position , SEEK_END ) + : fseek( file_ , +position , SEEK_SET ) ); + if ( rv < 0 ) { + throw X_StreamError( errno ); + } + pos_ = rv; +} + +void T_File::move( ssize_t offset ) +{ + open( ); + const auto rv( fseek( file_ , offset , SEEK_CUR ) ); + if ( rv < 0 ) { + throw X_StreamError( errno ); + } + pos_ = rv; +} + +/*---------------------------------------------------------------------------*/ + +size_t T_File::read( void* data , size_t size ) +{ + open( ); + if ( pos_ >= size_ ) { + throw X_StreamError( E_StreamError::END ); + } + + const auto rv( fread( data , 1 , size , file_ ) ); + if ( ferror( file_ ) ) { + throw X_StreamError( errno ); + } else { + pos_ += rv; + return rv; + } +} + +/*---------------------------------------------------------------------------*/ + +size_t T_File::write( void const* data , size_t size ) +{ + if ( mode_ == E_FileMode::READ_ONLY ) { + throw X_StreamError( E_StreamError::NOT_SUPPORTED ); + } + + open( ); + auto rv( fwrite( data , 1 , size , file_ ) ); + if ( ferror( file_ ) ) { + throw X_StreamError( errno ); + } else { + pos_ += rv; + if ( pos_ > size_ ) { + size_ = pos_; + } + return rv; + } +} + +/*---------------------------------------------------------------------------*/ + +void T_File::flush( ) +{ + if ( !isOpen( ) || mode_ == E_FileMode::READ_ONLY ) { + return; + } + fflush( file_ ); +} + + +/*= T_FileInputStream =======================================================*/ + + +T_FileInputStream::T_FileInputStream( T_File& file , ssize_t offset , size_t limit ) + : A_InputStream( 0 , 0 ) , fileRaw_( &file ) , fileOwned_( ) +{ + init( offset , limit ); +} + +T_FileInputStream::T_FileInputStream( OP_File&& file , ssize_t offset , size_t limit ) + : A_InputStream( 0 , 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) ) +{ + file.clear( ); + init( offset , limit ); +} + + +/*---------------------------------------------------------------------------*/ + +T_FileInputStream::T_FileInputStream( T_FileInputStream const& other ) + : A_InputStream( other.position( ) , other.size( ) ) , + fileRaw_( &( other.file( ) ) ) , fileOwned_( ) , + start_( other.start_ ) +{ } + +T_FileInputStream& T_FileInputStream::operator= ( T_FileInputStream const& other ) +{ + position_ = other.position_; + size_ = other.size_; + fileRaw_ = &( other.file( ) ); + fileOwned_ = OP_File( ); + start_ = other.start_; + return *this; +} + +/*---------------------------------------------------------------------------*/ + +void T_FileInputStream::swap( T_FileInputStream& rhs ) noexcept +{ + using std::swap; + swap( size_ , rhs.size_ ); + swap( position_ , rhs.position_ ); + swap( fileRaw_ , rhs.fileRaw_ ); + swap( fileOwned_ , rhs.fileOwned_ ); + swap( start_ , rhs.start_ ); +} + +/*---------------------------------------------------------------------------*/ + +size_t T_FileInputStream::read( void* data , size_t size ) +{ + if ( position_ >= size_ ) { + throw X_StreamError( E_StreamError::END ); + } + + auto& f( file( ) ); + f.open( ); + if ( f.position( ) != start_ + position_ ) { + f.position( start_ + position_ ); + } + + const size_t rSize( std::min( size , size_ - position_ ) ); + const auto r( f.read( data , rSize ) ); + position_ += r; + return r; +} + +/*---------------------------------------------------------------------------*/ + +void T_FileInputStream::init( ssize_t offset , size_t limit ) +{ + auto& f( file( ) ); + f.open( ); + const ssize_t start( f.position( ) + offset ); + if ( start < 0 || start > ssize_t( f.size( ) ) ) { + throw X_StreamError( E_StreamError::INVALID_POSITION ); + } + start_ = start; + size_ = std::min( f.size( ) - start , limit ); +} + + +/*= T_FileOutputStream =======================================================*/ + + +T_FileOutputStream::T_FileOutputStream( T_File& file , ssize_t offset ) + : A_OutputStream( 0 ) , fileRaw_( &file ) , fileOwned_( ) +{ + init( offset ); +} + +T_FileOutputStream::T_FileOutputStream( OP_File&& file , ssize_t offset ) + : A_OutputStream( 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) ) +{ + init( offset ); +} + +/*---------------------------------------------------------------------------*/ + +T_FileOutputStream::T_FileOutputStream( T_FileOutputStream const& other ) + : A_OutputStream( other.position( ) ) , fileRaw_( &other.file( ) ) , + fileOwned_( ) , start_( other.start_ ) +{ } + +T_FileOutputStream& T_FileOutputStream::operator= ( T_FileOutputStream const& other ) +{ + position_ = other.position_; + fileRaw_ = &other.file( ); + fileOwned_ = OP_File( ); + start_ = other.start_; + return *this; +} + +/*---------------------------------------------------------------------------*/ + +inline void T_FileOutputStream::swap( T_FileOutputStream& rhs ) noexcept +{ + using std::swap; + swap( position_ , rhs.position_ ); + swap( fileRaw_ , rhs.fileRaw_ ); + swap( fileOwned_ , rhs.fileOwned_ ); + swap( start_ , rhs.start_ ); +} + +/*---------------------------------------------------------------------------*/ + +size_t T_FileOutputStream::write( void const* data , size_t size ) +{ + auto& f( file( ) ); + f.open( ); + if ( f.position( ) != start_ + position_ ) { + f.position( start_ + position_ ); + } + + const auto w( f.write( data , size ) ); + position_ += w; + return w; +} + +/*---------------------------------------------------------------------------*/ + +void T_FileOutputStream::flush( ) +{ + file( ).flush( ); +} + +/*---------------------------------------------------------------------------*/ + +void T_FileOutputStream::init( ssize_t offset ) +{ + auto& f( file( ) ); + if ( f.mode( ) == E_FileMode::READ_ONLY ) { + throw X_StreamError( E_StreamError::NOT_SUPPORTED ); + } + const ssize_t start( f.position( ) + offset ); + if ( start < 0 || start > ssize_t( f.size( ) ) ) { + throw X_StreamError( E_StreamError::INVALID_POSITION ); + } + start_ = start; +} diff --git a/src/GameLoop.cc b/src/GameLoop.cc new file mode 100644 index 0000000..5a3930c --- /dev/null +++ b/src/GameLoop.cc @@ -0,0 +1,140 @@ +/******************************************************************************/ +/* GAME'S MAIN LOOP ***********************************************************/ +/******************************************************************************/ + +#include +#include +#include +#include +using namespace lw; + + +/*= T_GameLoopPrivate_ =======================================================*/ + +namespace { +struct T_GameLoopPrivate_ +{ + T_Logger logger{ "/core/loop" }; + + OP_Thread thread; + bool active = false; + bool forceShutdown = false; + + T_Mutex mutex; + T_Condition cond; + T_RingBuffer< T_UIMessage > messages{ 64 }; + + /* Game loop */ + void run( ) noexcept; + + /* Wait for the next message, no timeout */ + bool nextMessage( + T_UIMessage& message ) noexcept; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline void T_GameLoopPrivate_::run( ) noexcept +{ + const bool trace( logger.hasLevel( E_LogLevel::TRACE ) ); + logger.debug( ) << "Game loop thread starting"; + + while ( !forceShutdown ) { + T_UIMessage message; + if ( !nextMessage( message ) ) { + continue; + } + + const auto mt( message.type( ) ); + if ( trace ) { + logger.trace( ) << "Got message " << mt; + } + + switch ( mt ) { + + case lw::E_GameUIMessage::QUIT: + // FIXME quit properly + LW::ui( ).putMessage( E_GameLoopMessage::TERMINATED ); + forceShutdown = true; + break; + + default: + logger.debug( ) << "Unhandled message " << mt; + break; + + } + } + + active = false; + logger.debug( ) << "Game loop thread exiting"; +} + +inline bool T_GameLoopPrivate_::nextMessage( + T_UIMessage& message ) noexcept +{ + T_ExclusiveLock lock( mutex ); + cond.wait( lock , [this]() { + return messages.size( ) != 0 || forceShutdown; + } ); + return messages.readNext( message ) + && message.hasMessage( ); +} + + +/*= T_GameLoop ===============================================================*/ + +#define M_PRIVATE_ \ + auto& pi( p< T_GameLoopPrivate_ >( ) ); + +T_GameLoop::T_GameLoop( ) noexcept + : A_PrivateImplementation( new T_GameLoopPrivate_( ) ) +{ } + +bool T_GameLoop::active( ) const noexcept +{ + M_PRIVATE_; + return bool( pi.thread ) && !pi.active; +} + +void T_GameLoop::start( ) noexcept +{ + M_PRIVATE_; + pi.logger.trace( ) << "Starting main loop thread"; + if ( pi.thread ) { + pi.logger.warning( ) + << "The main loop thread is already active?!"; + } + pi.active = true; + pi.thread = NewOwned< T_Thread >( [&]{ + pi.run( ); + } ); +} + +void T_GameLoop::shutdown( ) noexcept +{ + M_PRIVATE_; + if ( !pi.thread ) { + pi.logger.trace( ) + << "The main loop thread is not present"; + return; + } + + if ( pi.active ) { + pi.logger.notice( ) << "Main loop is still active at shutdown!"; + pi.forceShutdown = true; + + T_ExclusiveLock lock( pi.mutex ); + pi.cond.notify_one( ); + } + pi.thread->join( ); + pi.thread.clear( ); +} + +void T_GameLoop::putMessage( T_UIMessage&& message ) noexcept +{ + M_PRIVATE_; + T_ExclusiveLock lock( pi.mutex ); + pi.messages.put( std::move( message ) ); + pi.cond.notify_one( ); +} diff --git a/src/HashIndex.cc b/src/HashIndex.cc new file mode 100644 index 0000000..12097e4 --- /dev/null +++ b/src/HashIndex.cc @@ -0,0 +1,231 @@ +/******************************************************************************/ +/* HASH INDEX *****************************************************************/ +/******************************************************************************/ + + +#include +#include +using namespace lw; + + +/*= T_HashIndex =============================================================*/ + +constexpr uint32_t T_HashIndex::INVALID_INDEX; +uint32_t T_HashIndex::invalidIndex_ = T_HashIndex::INVALID_INDEX; + +T_HashIndex::T_HashIndex( ) noexcept + : T_HashIndex( DEFAULT_SIZE , DEFAULT_SIZE ) +{ } + +T_HashIndex::T_HashIndex( uint32_t hashSize , uint32_t indexSize , uint32_t growth ) noexcept + : hashSize_( hashSize ) , hash_( &invalidIndex_ ) , + indexSize_( indexSize ) , indexGrowth_( growth ) , + indexUsed_( 0 ) , index_( &invalidIndex_ ) , + hashMask_( hashSize - 1 ) , lookupMask_( 0 ) +{ + assert( IsPowerOf2( hashSize ) && "size must be a power of 2" ); + assert( growth > 0 && "growth must be greater than 0" ); +} + +/*----------------------------------------------------------------------------*/ + +T_HashIndex::T_HashIndex( T_HashIndex const& other ) + : hashSize_( other.hashSize_ ) , indexSize_( other.indexSize_ ) , + indexGrowth_( other.indexGrowth_ ) , indexUsed_( other.indexUsed_ ) , + hashMask_( other.hashMask_ ) , lookupMask_( other.lookupMask_ ) +{ + if ( indexUsed_ == 0 ) { + hash_ = &invalidIndex_; + index_ = &invalidIndex_; + } else { + hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) ); + index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + memcpy( hash_ , other.hash_ , hashSize_ * sizeof( uint32_t ) ); + memcpy( index_ , other.index_ , indexSize_ * sizeof( uint32_t ) ); + memcpy( indexReverse_ , other.indexReverse_ , indexSize_ * sizeof( uint32_t ) ); + } +} + + +T_HashIndex::T_HashIndex( T_HashIndex&& other ) noexcept + : T_HashIndex( ) +{ + swap( *this , other ); +} + +/*----------------------------------------------------------------------------*/ + +T_HashIndex& T_HashIndex::operator =( T_HashIndex const& other ) +{ + if ( hash_ != &invalidIndex_ ) { + ::free( hash_ ); + ::free( index_ ); + ::free( indexReverse_ ); + } + hashSize_ = other.hashSize_; + indexSize_ = other.indexSize_; + indexGrowth_ = other.indexGrowth_; + indexUsed_ = other.indexUsed_; + hashMask_ = other.hashMask_; + lookupMask_ = other.lookupMask_; + + if ( indexUsed_ == 0 ) { + hash_ = &invalidIndex_; + index_ = &invalidIndex_; + } else { + hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) ); + index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + memcpy( hash_ , other.hash_ , hashSize_ * sizeof( uint32_t ) ); + memcpy( index_ , other.index_ , indexSize_ * sizeof( uint32_t ) ); + memcpy( indexReverse_ , other.indexReverse_ , indexSize_ * sizeof( uint32_t ) ); + } + return *this; +} + +T_HashIndex& T_HashIndex::operator =( T_HashIndex&& other ) noexcept +{ + swap( *this , other ); + other.clear( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_HashIndex& lhs , T_HashIndex& rhs ) noexcept +{ + using std::swap; + + swap( lhs.hashSize_ , rhs.hashSize_ ); + swap( lhs.hash_ , rhs.hash_ ); + + swap( lhs.indexSize_ , rhs.indexSize_ ); + swap( lhs.indexGrowth_ , rhs.indexGrowth_ ); + swap( lhs.indexUsed_ , rhs.indexUsed_ ); + swap( lhs.index_ , rhs.index_ ); + swap( lhs.indexReverse_ , rhs.indexReverse_ ); + + swap( lhs.hashMask_ , rhs.hashMask_ ); + swap( lhs.lookupMask_ , rhs.lookupMask_ ); +} + +/*----------------------------------------------------------------------------*/ + +void T_HashIndex::free( ) +{ + if ( hash_ != &invalidIndex_ ) { + ::free( hash_ ); + ::free( index_ ); + ::free( indexReverse_ ); + hash_ = &invalidIndex_; + index_ = &invalidIndex_; + } + lookupMask_ = 0; + indexUsed_ = 0; +} + +void T_HashIndex::clear( ) +{ + if ( hash_ != &invalidIndex_ ) { + memset( hash_ , 0xff , hashSize_ * sizeof( uint32_t ) ); + memset( index_ , 0xff , indexSize_ * sizeof( uint32_t ) ); + indexUsed_ = 0; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_HashIndex::enlargeIndex( uint32_t needed ) +{ + const uint32_t mod = needed % indexGrowth_; + const uint32_t newSize = ( mod == 0 ) + ? needed + : ( needed + indexGrowth_ - mod ); + if ( index_ != &invalidIndex_ ) { + index_ = ( uint32_t* ) realloc( index_ , + newSize * sizeof( uint32_t ) ); + indexReverse_ = ( uint32_t* ) realloc( indexReverse_ , + newSize * sizeof( uint32_t ) ); + memset( index_ + indexSize_ , 0xff , + ( newSize - indexSize_ ) * sizeof( uint32_t ) ); + memset( indexReverse_ + indexSize_ , 0xff , + ( newSize - indexSize_ ) * sizeof( uint32_t ) ); + } + indexSize_ = newSize; +} + +void T_HashIndex::allocateIfNecessary( ) +{ + if ( hash_ == &invalidIndex_ ) { + hash_ = ( uint32_t* ) malloc( hashSize_ * sizeof( uint32_t ) ); + index_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + indexReverse_ = ( uint32_t* ) malloc( indexSize_ * sizeof( uint32_t ) ); + memset( hash_ , 0xff , hashSize_ * sizeof( uint32_t ) ); + memset( index_ , 0xff , indexSize_ * sizeof( uint32_t ) ); + memset( indexReverse_ , 0xff , indexSize_ * sizeof( uint32_t ) ); + lookupMask_ = INVALID_INDEX; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_HashIndex::add( uint32_t key ) +{ + const uint32_t index = indexUsed_; + if ( index >= indexSize_ ) { + enlargeIndex( index + indexGrowth_ ); + } + allocateIfNecessary( ); + + assert( index_[ index ] == INVALID_INDEX ); + + const uint32_t hti = key & hashMask_; + const uint32_t oldIndex = hash_[ hti ]; + hash_[ hti ] = index; + index_[ index ] = oldIndex; + indexReverse_[ index ] = hti; + indexUsed_ ++; +} + +void T_HashIndex::remove( uint32_t index ) +{ + assert( hash_ != &invalidIndex_ ); + assert( index < indexUsed_ ); + + // Follow the chain until we find the reference to the index. + const auto key( indexReverse_[ index ] ); + uint32_t* ptr = &hash_[ key & hashMask_ ]; + assert( *ptr != INVALID_INDEX ); + while ( *ptr != index ) { + ptr = &index_[ *ptr ]; + assert( *ptr != INVALID_INDEX ); + } + + // Update the reference so it points to the next item in the chain + *ptr = index_[ index ]; + + // If the index wasn't the last used index, swap it with the last used + // value + const uint32_t last = indexUsed_ - 1; + if ( index != last ) { + const uint32_t reverse = indexReverse_[ last ]; + assert( reverse != INVALID_INDEX ); + + index_[ index ] = index_[ last ]; + indexReverse_[ index ] = reverse; + + // Change the reference to the item we moved in its chain + uint32_t* ptr = &hash_[ reverse ]; + assert( *ptr != INVALID_INDEX ); + while ( *ptr != last ) { + ptr = &index_[ *ptr ]; + assert( *ptr != INVALID_INDEX ); + } + *ptr = index; + } + index_[ last ] = INVALID_INDEX; + indexReverse_[ last ] = INVALID_INDEX; + indexUsed_ = last; +} + diff --git a/src/LW.cc b/src/LW.cc new file mode 100644 index 0000000..2e0db32 --- /dev/null +++ b/src/LW.cc @@ -0,0 +1,603 @@ +/******************************************************************************/ +/* MAIN CLASS *****************************************************************/ +/******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +using namespace lw; + + +/*= LIBRARY DATA =============================================================*/ + +namespace { +#ifdef LW_BUILD +# include "lib-rom.hh" +#else +const uint8_t lw_library_rom[] = { 0 }; +#endif +} // namespace + + +/*= INTERNALS ================================================================*/ + +namespace { + +/* Command line arguments */ +struct T_CommandLine_ +{ + T_String homePath; + T_Array< T_String > extraPaths; + T_String ui; +}; + +struct T_LWInternals_ +{ + T_LWComponents& components; + + const T_String executablePath_; + T_CommandLine_ arguments_; + + OP_LoggingSystem loggingSystem; + T_Logger lPreinit; + T_Logger logger; + T_Logger lShutdown; + + bool uiInitialised; + + // --------------------------------------------------------------------- + + T_LWInternals_( T_String const& executablePath , + T_LWComponents& components ) noexcept; + + // --------------------------------------------------------------------- + + [[noreturn]] void fatalError( T_Logger& logger , char const* text ); + + /* Load a logging configuration file */ + OP_LoggingConfiguration loadLoggingConfigFile( + T_Logger& logger , + char const* path ) noexcept; + + // --------------------------------------------------------------------- + + /* Pre-initialisation - console, logging, VFS, mods manager */ + void preInit( T_String const& commandLine ); + + OP_LoggingConfiguration getInitialLoggingConfig( ); + void parseCommandLine( + T_String const& commandLine ); + OP_LoggingConfiguration loadPreinitLoggingConfig( ); + OP_LoggingConfiguration getDefaultPreinitLoggingConfig( ); + T_ModsManagerConfiguration getModsManagerConfiguration( ); + + // --------------------------------------------------------------------- + + void init( ); + void run( ); + + // --------------------------------------------------------------------- + + void shutdown( ); + OP_LoggingConfiguration loadShutdownLoggingConfig( ); + OP_LoggingConfiguration getDefaultShutdownLoggingConfig( ); +}; + +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_LWInternals_::T_LWInternals_( + T_String const& executablePath , + T_LWComponents& components ) noexcept + : components( components ) , + executablePath_( executablePath ) , + lPreinit( "/core/preinit" ) , + logger( "/core/init" ) , + lShutdown( "/core/shutdown" ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_LWInternals_::fatalError( T_Logger& logger , char const* message ) +{ + logger.critical( ) << message; + // XXX UI-specific error display if possible + throw X_FatalError( ); +} + +OP_LoggingConfiguration T_LWInternals_::loadLoggingConfigFile( + T_Logger& logger , + char const* const path ) noexcept +{ + T_StringBuilder sb; + sb << path << ".srd"; + const T_String srd( std::move( sb ) ); + sb << path << ".srb"; + const T_String srb( std::move( sb ) ); + + auto& vfs( LW::vfs( ) ); + const bool useSRD( vfs.typeOf( srd ) == E_VFSEntryType::FILE ); + if ( !useSRD && vfs.typeOf( srb ) != E_VFSEntryType::FILE ) { + return {}; + } + T_String const& pstr( useSRD ? srd : srb ); + + auto cfg( LW::logWriters( ).getParserConfiguration( ) ); + T_SRDParser parser( cfg ); + OP_SRDReader reader( ([&]() -> OP_SRDReader { + if ( useSRD ) { + return NewOwned< T_SRDTextReader >( parser ); + } else { + return NewOwned< T_SRDBinaryReader >( parser ); + } + })( ) ); + try { + OP_InputStream input( vfs.read( pstr ) ); + if ( !input ) { + throw X_StreamError( E_StreamError::UNAVAILABLE ); + } + reader->read( pstr , *input ); + return NewOwned< T_LoggingConfiguration >( + std::move( *parser.getData< SP_LoggingConfiguration >( ) ) ); + + } catch ( X_StreamError const& e ) { + logger.warning( ) << "could not load '" + << pstr << "': " << e.what( ); + } catch ( X_SRDErrors const& errors ) { + errors.log( lPreinit ); + } + + return {}; +} + + +/*= PRE-INITIALISATION =======================================================*/ + +void T_LWInternals_::preInit( + T_String const& commandLine ) +{ + // Initialise the console, if we can + components.console = NewOwned< T_Console >( ); + components.console->mode( E_ConsoleMode::OPTIONAL ); + + // Register basic log writers + components.logWriters = NewOwned< T_LogWriterRegistry >( ); + LW::logWriters( ).add< T_CWDFileLogWriterFactory >( ).automatic( false ); + LW::logWriters( ).add< T_ConsoleLogWriterFactory >( LW::console( ) ).automatic( false ); + + // Initialise the logging system with a temporary configuration + loggingSystem = NewOwned< T_LoggingSystem >( + LW::logWriters( ) , + getInitialLoggingConfig( ) ); + + // Parse command line arguments + if ( commandLine ) { + parseCommandLine( commandLine ); + } + + // Initialise the VFS + try { + if ( arguments_.homePath ) { + components.vfs = NewOwned< T_VFS >( arguments_.homePath ); + } else { + components.vfs = NewOwned< T_VFS >( ); + } + } catch ( X_VFSInitialisationFailure const& ) { + fatalError( lPreinit , "Could not initialise the VFS." ); + } + + // Add the VFS-based log writer, then try reconfiguring + LW::logWriters( ).add< T_TextFileLogWriterFactory >( LW::vfs( ) ).automatic( false ); + loggingSystem->reconfigure( loadPreinitLoggingConfig( ) ); + + // Finish configuring the VFS + lPreinit.debug( ) << "Adding install path '" << executablePath_ + << "' to the VFS"; + { + auto ipvfsd( LW::vfs( ).addDriver< T_VFSFilesystemDriver >( + executablePath_ ) ); + if ( !ipvfsd ) { + fatalError( lPreinit , "Could not initialise the VFS." ); + } else { + ipvfsd.automatic( false ); + } + } + const uint32_t nExtraPaths( arguments_.extraPaths.size( ) ); + for ( uint32_t i = 0 ; i < nExtraPaths ; i ++ ) { + auto const& path( arguments_.extraPaths[ i ] ); + lPreinit.trace( ) << "Adding extra path '" << path << "' to the VFS"; + auto vfsd( LW::vfs( ).addDriver< T_VFSFilesystemDriver >( path ) ); + if ( vfsd ) { + vfsd.automatic( false ); + } else { + lPreinit.warning( ) << "unable to add directory '" + << path << "' to the VFS"; + } + } + lPreinit.debug( ) << "Adding library data to the VFS"; + LW::vfs( ).addDriver( LW::getLibData( ) ).automatic( false ); + + // Create the global preprocessor configuration + lPreinit.trace( ) << "Creating SRD preprocessor configuration"; + components.ppConfig = NewOwned< T_SRDPreprocessorConfig >( ); + components.ppConfig->addBuiltinCommands( ); + components.ppConfig->addVFSCommands( *components.vfs ); + + // Initialise the mods manager + lPreinit.trace( ) << "Initialising mods manager"; + components.mods = NewOwned< T_ModsManager >( + getModsManagerConfiguration( ) ); + auto& mods( LW::mods( ) ); + if ( !mods.scanForMods( ) ) { + // TODO later - switch to installer + fatalError( lPreinit , "No mods found" ); + } + if ( !mods.resolveDependencies( ) ) { + // TODO later - switch to installer + fatalError( lPreinit , + "Could not find a valid set of mods " + "matching the current configuration" ); + } + + // Pre-initialise mods + mods.preinitCommon( ); + components.ui = arguments_.ui + ? mods.preinitUIMods( arguments_.ui ) + : mods.preinitUIMods( ); + if ( !components.ui ) { + fatalError( lPreinit , "Unable to start the user interface." ); + } + uiInitialised = false; + + // Create the main loop object + components.gameLoop = NewOwned< T_GameLoop >( ); + lPreinit.trace( ) << "UI & game loop created"; + + // Load main logging configuration + auto lc( loadLoggingConfigFile( lPreinit , "/logging" ) ); + if ( lc ) { + loggingSystem->reconfigure( std::move( lc ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +OP_LoggingConfiguration T_LWInternals_::getInitialLoggingConfig( ) +{ + auto logCfg( NewOwned< T_LoggingConfiguration >( ) ); + logCfg->setMinLevelFor( T_LogPath( ) , E_LogLevel::NOTICE ); + { + const T_String console( T_String::Pooled( "console" ) ); + logCfg->addLogWriter( T_LogPath( ) , console ); + auto writerConfig( LW::logWriters( ) + .get( console ) + ->createConfiguration( console ) ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + } + { + const T_String tfw( "preinit-file" ); + T_CWDFileLogWriterCfg* writerConfig( + dynamic_cast< T_CWDFileLogWriterCfg *>( + LW::logWriters( ) + .get( tfw ) + ->createConfiguration( tfw ) ) ); + writerConfig->setPath( T_String::Pooled( "legacyworlds-preinit.log" ) ); + writerConfig->setAppend( false ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + logCfg->addLogWriter( T_LogPath( ) , tfw ); + } + return logCfg; +} + +/*----------------------------------------------------------------------------*/ + +void T_LWInternals_::parseCommandLine( + T_String const& commandLine ) +{ + const T_SRDParserConfig clpConfig( ([]( T_CommandLine_* cl ) { + using namespace lw::SRD; + T_SRDParserDefs defs( "default" ); + + defs << OnStart( [cl]( T_SRDParserData const& d ) -> bool { + *( d.currentData ) = cl; + return true; + } ); + + defs.context( "default" ) + << ( Rule( ) << "ui" << Text( ) + << []( T_SRDParserData const& d ) -> bool { + auto cl( d.currentData->value< T_CommandLine_* >( ) ); + if ( cl->ui ) { + d.errors.add( "duplicate UI identifier" , (*d.input)[ 0 ] ); + } else { + cl->ui = (*d.input)[ 1 ].stringValue( ); + } + return true; + } ) + << ( Rule( ) << "home" << Text( ) + << []( T_SRDParserData const& d ) -> bool { + auto cl( d.currentData->value< T_CommandLine_* >( ) ); + if ( cl->homePath ) { + d.errors.add( "duplicate home directory" , (*d.input)[ 0 ] ); + } else { + cl->homePath = (*d.input)[ 1 ].stringValue( ); + } + return true; + } ) + << ( Rule( ) << "extra-paths" << ( AtLeast( 1 ) << Text( ) ) + << []( T_SRDParserData const& d ) -> bool { + auto cl( d.currentData->value< T_CommandLine_* >( ) ); + auto const& input( *d.input ); + const auto nPaths( input.size( ) ); + for ( uint32_t i = 1 ; i < nPaths ; i ++ ) { + auto const& tok( input[ i ] ); + if ( cl->extraPaths.contains( tok.stringValue( ) ) ) { + d.errors.add( "duplicate data path" , tok ); + } else { + cl->extraPaths.add( tok.stringValue( ) ); + } + } + return true; + } ); + + return T_SRDParserConfig( defs ); + })( &arguments_ ) ); + + T_SRDParser parser( clpConfig ); + T_SRDTextReader reader( parser ); + T_MemoryInputStream input( commandLine.data( ) , commandLine.size( ) ); + try { + reader.read( T_String( "command line" ) , input ); + } catch ( X_SRDErrors const& errors ) { + errors.log( lPreinit ); + fatalError( lPreinit , "Invalid command line arguments" ); + } +} + +/*----------------------------------------------------------------------------*/ + +OP_LoggingConfiguration T_LWInternals_::loadPreinitLoggingConfig( ) +{ + auto rv( loadLoggingConfigFile( lPreinit , "/logging-pre" ) ); + if ( !rv ) { + return getDefaultPreinitLoggingConfig( ); + } + return rv; +} + +OP_LoggingConfiguration T_LWInternals_::getDefaultPreinitLoggingConfig( ) +{ + auto logCfg( NewOwned< T_LoggingConfiguration >( ) ); + { + const T_String console( T_String::Pooled( "console" ) ); + auto writerConfig( LW::logWriters( ) + .get( console ) + ->createConfiguration( console ) ); + writerConfig->setMinLevel( E_LogLevel::NOTICE ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + logCfg->addLogWriter( T_LogPath( ) , console ); + } + { + const T_String tfw( "text-file" ); + T_TextFileLogWriterCfg* writerConfig( + dynamic_cast< T_TextFileLogWriterCfg *>( + LW::logWriters( ) + .get( tfw ) + ->createConfiguration( tfw ) ) ); + writerConfig->setMinLevel( E_LogLevel::INFO ); + writerConfig->setPath( "/preinit.log" ); + writerConfig->setAppend( false ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + logCfg->addLogWriter( T_LogPath( ) , tfw ); + } + return logCfg; +} + +/*----------------------------------------------------------------------------*/ + +T_ModsManagerConfiguration T_LWInternals_::getModsManagerConfiguration( ) +{ + using T_MMC = T_ModsManagerConfiguration; + using SP_MMC = SP_ModsManagerConfiguration; + + const T_String mmcfgf( "/mods.srd" ); + auto& vfs( LW::vfs( ) ); + if ( vfs.typeOf( mmcfgf ) == E_VFSEntryType::FILE ) { + auto pconf( T_MMC::GetParserConfig( ) ); + T_SRDParser parser( pconf ); + T_SRDTextReader reader( parser ); + try { + OP_InputStream input( vfs.read( mmcfgf ) ); + if ( !input ) { + throw X_StreamError( E_StreamError::UNAVAILABLE ); + } + reader.read( mmcfgf , *input ); + return std::move( *parser.getData< SP_MMC >( ) ); + + } catch ( X_StreamError const& e ) { + lPreinit.warning( ) << "could not load '" << mmcfgf << "': " + << e.what( ); + } catch ( X_SRDErrors const& errors ) { + errors.log( lPreinit ); + } + } + lPreinit.debug( ) << "Using default mods configuration"; + return T_MMC::DefaultConfiguration( ); +} + + +/*= INITIALISATION ===========================================================*/ + +void T_LWInternals_::init( ) +{ + // Initialise all the mods + const uint32_t nLoaded( components.mods->modsCount( ) ); + uint32_t initialised = 0; + components.mods->initialise( + [&]( RPC_ModInfo mi ) -> F_UpdateInitProgress { + const T_ProgressInfoPart main{ + ([&](){ + T_StringBuilder sb( "Initializing mod " ); + sb << mi->identifier.name; + return T_String{ std::move( sb ) }; + })( ) , initialised + 1 , nLoaded + 2 + }; + initialised ++; + components.ui->setInitProgress( + T_ProgressInfo{ main } ); + + return [&]( T_ProgressInfoPart part ) { + components.ui->setInitProgress( + T_ProgressInfo{ main , part } ); + }; + } ); + + // Initialise UI + const T_ProgressInfoPart uiInitMain{ + T_String( "Initializing user interface" ) , + initialised + 1 , nLoaded + 2 + }; + components.ui->setInitProgress( T_ProgressInfo{ uiInitMain } ); + if ( ! components.ui->init( [&]( T_ProgressInfoPart part ) { + components.ui->setInitProgress( + T_ProgressInfo{ uiInitMain , part } ); + }) ) { + fatalError( logger , "Failed to initialize user interface" ); + } + uiInitialised = true; + + // Start game loop + components.gameLoop->start( ); +} + +/*FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME*/ + +void T_LWInternals_::run( ) +{ + components.ui->run( ); +} + + +/*= SHUTDOWN =================================================================*/ + +void T_LWInternals_::shutdown( ) +{ + lShutdown.trace( ) << "Starting shutdown"; + + // Shutdown game loop if it's still active + if ( components.gameLoop ) { + components.gameLoop->shutdown( ); + } + + // If the UI is present and initialised, shut it down + if ( components.ui && uiInitialised ) { + components.ui->shutdown( ); + uiInitialised = false; + } + + // Shut down mods + if ( components.mods ) { + components.mods->shutdown( ); + } + + // Reset logging configuration + lShutdown.trace( ) << "Resetting logger configuration"; + loggingSystem->reconfigure( loadShutdownLoggingConfig( ) ); + + // Terminate the UI + if ( components.ui ) { + components.ui->postshutdown( ); + components.ui.clear( ); + components.gameLoop.clear( ); + lShutdown.trace( ) << "UI and game loop terminated"; + } + + // Clear mods + if ( components.mods ) { + components.mods->unload( ); + components.mods.clear( ); + lShutdown.trace( ) << "Mods unloaded"; + } + + // Clear remaining components + lShutdown.trace( ) << "Terminating core components"; + components.ppConfig.clear( ); + loggingSystem.clear( ); + components.logWriters.clear( ); + components.console.clear( ); + components.vfs.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +OP_LoggingConfiguration T_LWInternals_::loadShutdownLoggingConfig( ) +{ + auto rv( loadLoggingConfigFile( lShutdown , "/logging-post" ) ); + if ( !rv ) { + return getDefaultShutdownLoggingConfig( ); + } + return rv; +} + +OP_LoggingConfiguration T_LWInternals_::getDefaultShutdownLoggingConfig( ) +{ + auto logCfg( NewOwned< T_LoggingConfiguration >( ) ); + { + const T_String console( T_String::Pooled( "console" ) ); + auto writerConfig( LW::logWriters( ) + .get( console ) + ->createConfiguration( console ) ); + writerConfig->setMinLevel( E_LogLevel::NOTICE ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + logCfg->addLogWriter( T_LogPath( ) , console ); + } + { + const T_String tfw( "text-file" ); + T_TextFileLogWriterCfg* writerConfig( + dynamic_cast< T_TextFileLogWriterCfg *>( + LW::logWriters( ) + .get( tfw ) + ->createConfiguration( tfw ) ) ); + writerConfig->setMinLevel( E_LogLevel::INFO ); + writerConfig->setPath( "/shutdown.log" ); + writerConfig->setAppend( false ); + logCfg->putLogWriter( OwnRawPointer( writerConfig ) ); + logCfg->addLogWriter( T_LogPath( ) , tfw ); + } + return logCfg; +} + + +/*= MAIN CLASS ===============================================================*/ + +LW* LW::instance_ = nullptr; + +LW::LW( T_String const& commandLine , T_String const& executablePath ) +{ + assert( instance_ == nullptr ); + instance_ = this; + T_LWInternals_ pi( executablePath , components ); + try { + pi.preInit( commandLine ); + pi.init( ); + pi.run( ); + } catch ( X_FatalError const& ) { + pi.shutdown( ); + components.~T_LWComponents( ); + instance_ = nullptr; + throw; + } + pi.shutdown( ); + instance_ = nullptr; +} + +OP_VFSDriver LW::getLibData( ) noexcept +{ + return NewOwned< T_VFSRomDriver >( + lw_library_rom , sizeof( lw_library_rom ) ); +} diff --git a/src/Log.cc b/src/Log.cc new file mode 100644 index 0000000..20c65d5 --- /dev/null +++ b/src/Log.cc @@ -0,0 +1,1041 @@ +/******************************************************************************/ +/* LOGGING SYSTEM *************************************************************/ +/******************************************************************************/ + + +#include +#include +using namespace lw; + + +/*= IMPLEMENTATION CLASSES ===================================================*/ + +namespace { + +struct T_LoggerPrivate_ +{ + T_LogPath path_; + bool connected_; + uint32_t pathIndex_; + E_LogLevel minLevel_; + E_LogLevel maxLevel_; + + T_LoggerPrivate_( T_LogPath&& path ); + T_LoggerPrivate_( T_LoggerPrivate_ const& other ); + ~T_LoggerPrivate_( ); + + void connect( ); +}; + +struct T_LoggingSystemPrivate_ +{ + struct T_LogEntry + { + T_LogTimestamp time; + E_LogLevel level; + uint32_t path; + T_LogStringData string; + uint32_t size; + + T_LogEntry( ) = default; + + T_LogEntry( E_LogLevel level , uint32_t path , + T_LogStringData&& string , uint32_t size ); + + T_LogEntry( T_LogEntry const& ) = delete; + T_LogEntry( T_LogEntry&& ) noexcept = default; + + T_LogEntry& operator =( T_LogEntry const& ) = delete; + T_LogEntry& operator =( T_LogEntry&& ) noexcept = default; + }; + + static constexpr uint32_t C_MAX_REUSE_SIZE = 256; + static constexpr uint32_t C_MAX_REUSE_QUANTITY = 64; + + static T_LoggingSystemPrivate_ * instance_; + static T_Mutex loggersMutex_; + static T_Array< T_LoggerPrivate_ * > loggers_; + + RP_LoggingSystem main_; + + T_LogWriterRegistry const& registry_; + OP_LoggingConfiguration config_; + T_Array< OP_LogWriter > writers_; + + OP_Thread thread_; + + T_Mutex queueMutex_; + T_Condition queueCond_; + bool exit_; + T_RingBuffer< T_LogEntry > queue_; + T_Array< T_LogStringData > freeList_; + T_Array< T_LogPath > paths_; + T_Array< uint32_t > pathRefCount_; + + // --------------------------------------------------------------------- + + T_LoggingSystemPrivate_( + RP_LoggingSystem main , + T_LogWriterRegistry const& registry , + OP_LoggingConfiguration&& config ); + ~T_LoggingSystemPrivate_( ); + + // --------------------------------------------------------------------- + + void log( E_LogLevel level , uint32_t pathIndex , char const* data , uint32_t size ); + + void initLogWriters( ); + + // --------------------------------------------------------------------- + + void startLoggingThread( ); + void stopLoggingThread( ); + void expandQueue( ); + + // --------------------------------------------------------------------- + + void loggingThread( ); + void writeEntry( T_LogEntry const& entry ); +}; +} + + +/*= E_LogLevel ===============================================================*/ + +T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , E_LogLevel level ) +{ + switch ( level ) { + case E_LogLevel::TRACE: sb << "trace"; break; + case E_LogLevel::DEBUG: sb << "debug"; break; + case E_LogLevel::INFO: sb << "info"; break; + case E_LogLevel::NOTICE: sb << "notice"; break; + case E_LogLevel::WARNING: sb << "warning"; break; + case E_LogLevel::ERROR: sb << "error"; break; + case E_LogLevel::CRITICAL: sb << "critical"; break; + } + return sb; +} + + +/*= T_LogPath ================================================================*/ + +T_LogPath::T_LogPath( ) + : path_( 1 ) , valid_( true ) +{ } + +T_LogPath::T_LogPath( T_Array< T_String > const& path ) + : path_( path ) , valid_( checkValid( ) ) +{ } + +T_LogPath::T_LogPath( T_Array< T_String >&& path ) + : path_( std::move( path ) ) , valid_( checkValid( ) ) +{ } + +T_LogPath::T_LogPath( std::initializer_list< char const* > arguments ) + : path_( arguments.size( ) ) +{ + for ( char const* arg : arguments ) { + path_.addNew( T_String::Pooled( arg ) ); + } + valid_ = checkValid( ); +} + +/*----------------------------------------------------------------------------*/ + +T_LogPath T_LogPath::FromString( T_String const& string ) +{ + T_StringIterator it( string ); + T_Array< T_String > pieces; + uint32_t pieceStart( 0 ); + bool atStart( true ); + bool afterSlash( false ); + bool valid( true ); + + while ( !it.atEnd( ) ) { + const T_Character c( it ); + + if ( c == '/' ) { + if ( !( atStart || afterSlash ) ) { + pieces.add( string.range( pieceStart , it.index( ) - 1 ) ); + afterSlash = true; + } + } else { + if ( atStart ) { + atStart = false; + afterSlash = true; + } + if ( afterSlash ) { + afterSlash = false; + pieceStart = it.index( ); + } + valid = valid && !c.isControl( ); + } + + it.next( ); + } + if ( !( atStart || afterSlash ) ) { + pieces.add( string.range( pieceStart , it.index( ) ) ); + } + + if ( pieces.size( ) == 0 ) { + return T_LogPath( ); + } + + T_LogPath lp( pieces.size( ) , valid ); + lp.path_.addAll( std::move( pieces ) ); + return lp; +} + +/*----------------------------------------------------------------------------*/ + +T_LogPath T_LogPath::parent( ) const +{ + const auto sz( size( ) ); + if ( sz > 1 ) { + if ( valid_ ) { + T_LogPath lp( sz - 1 , true ); + for ( uint32_t i = 0 ; i < sz - 1 ; i ++ ) { + lp.path_.add( path_[ i ] ); + } + return lp; + } else { + return T_LogPath( path_.copyRange( 0 , sz - 2 ) ); + } + } else { + return T_LogPath( ); + } +} + +T_LogPath T_LogPath::child( T_String const& name ) const +{ + const auto sz( size( ) ); + T_LogPath lp( sz + 1 , valid_ && checkName( name ) ); + lp.path_.addAll( path_ ); + lp.path_.add( name ); + return lp; +} + +/*----------------------------------------------------------------------------*/ + +T_String T_LogPath::toString( ) const +{ + const auto sz( size( ) ); + if ( sz ) { + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + sb << '/' << path_[ i ]; + } + return T_String( std::move( sb ) ); + } else { + return T_String::Pooled( "/" ); + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_LogPath::isChildOf( T_LogPath const& other ) const +{ + const auto os( other.size( ) ); + if ( size( ) <= os ) { + return false; + } + for ( uint32_t i = 0 ; i < os ; i ++ ) { + if ( path_[ i ] != other.path_[ i ] ) { + return false; + } + } + return true; +} + +bool T_LogPath::operator ==( T_LogPath const& other ) const +{ + if ( this == &other ) { + return true; + } + + const auto ms( size( ) ); + if ( ms != other.size( ) ) { + return false; + } + for ( uint32_t i = 0 ; i < ms ; i ++ ) { + if ( path_[ i ] != other.path_[ i ] ) { + return false; + } + } + return true; +} + +/*----------------------------------------------------------------------------*/ + +T_LogPath::T_LogPath( uint32_t size , bool valid ) + : path_( size ) , valid_( valid ) +{ } + +bool T_LogPath::checkValid( ) const +{ + const auto s( path_.size( ) ); + for ( uint32_t i = 0 ; i < s ; i ++ ) { + if ( !checkName( path_[ i ] ) ) { + return false; + } + } + return true; +} + +bool T_LogPath::checkName( T_String const& name ) +{ + if ( !name ) { + return false; + } + + T_StringIterator it( name ); + while ( !it.atEnd( ) ) { + const T_Character c( it ); + it.next( ); + if ( c.isControl( ) || c == '/' ) { + return false; + } + } + return true; +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_HASH( T_LogPath ) +{ + const auto sz( item.size( ) ); + uint32_t hash = 1; + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + hash = hash * 47 + ComputeHash( item[ i ] ); + } + return hash; +} + + +/*= T_LogWriterConfiguration ================================================*/ + +T_LogWriterConfiguration::T_LogWriterConfiguration( T_String const& type ) + : writerType_( type ) +{ } + +T_LogWriterConfiguration::~T_LogWriterConfiguration( ) +{ } + +OP_LogWriterConfiguration T_LogWriterConfiguration::clone( ) +{ + RP_LogWriterConfiguration p( new T_LogWriterConfiguration( *this ) ); + return OwnRawPointer( p ); +} + +void T_LogWriterConfiguration::check( T_SRDErrors& errors , T_SRDList const& input ) +{ + if ( minLevel_ > maxLevel_ ) { + errors.add( "minimal level is higher than maximal level" , input[ 1 ] ); + } +} + + +/*= A_LogWriterFactory ==========================================================*/ + +void A_LogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& ) const +{ } + + +/*= T_LogWriterRegistryPrivate_ =================================================*/ + +namespace { +struct T_LogWriterRegistryPrivate_ +{ + T_ObjectTable< T_String , OP_LogWriterFactory > factories_{ + []( OP_LogWriterFactory const& f ) -> T_String const& { + return f->name( ); + } , 128 , 128 + }; + + T_RegisteredItem::SP_Unregister uf_{ + NewShared< T_RegisteredItem::F_Unregister >( + [this]( void* data ) { + factories_.remove( *reinterpret_cast< T_String* >( data ) ); + } + ) + }; + +}; +} // namespace + + +/*= T_LogWriterRegistry =========================================================*/ + +T_LogWriterRegistry::T_LogWriterRegistry( ) noexcept + : A_PrivateImplementation( new T_LogWriterRegistryPrivate_( ) ) +{ } + +T_RegisteredItem T_LogWriterRegistry::add( OP_LogWriterFactory&& factory ) noexcept +{ + auto& pi( p< T_LogWriterRegistryPrivate_ >( ) ); + if ( !factory || pi.factories_.contains( factory->name( ) ) ) { + return T_RegisteredItem( ); + } + + auto const& fn( factory->name( ) ); + pi.factories_.add( std::move( factory ) ); + return T_RegisteredItem( pi.uf_ , new T_String( fn ) ); +} + +RPC_LogWriterFactory T_LogWriterRegistry::get( T_String const& name ) const noexcept +{ + auto& pi( ); + OP_LogWriterFactory const* ptr( + p< T_LogWriterRegistryPrivate_ >( ).factories_.get( name ) ); + return ptr ? ptr->get( ) : nullptr; +} + +/*----------------------------------------------------------------------------*/ + +namespace { + +using T_PathCfg_ = std::pair< SP_LoggingConfiguration , T_LogPath >; + +// Start the parser +bool PCStart_( T_SRDParserData const& data ) +{ + *( data.currentData ) = NewShared< T_LoggingConfiguration >( ); + return true; +} + +// Entering a path configuration +bool PCEnterPath_( T_SRDParserData const& data ) +{ + auto const& tok( (*data.input)[ 0 ] ); + const T_LogPath lp( tok.type( ) == E_SRDTokenType::WORD + ? T_LogPath( ) + : T_LogPath::FromString( tok.stringValue( ) ) ); + auto cfg( data.currentData->value< SP_LoggingConfiguration >( ) ); + if ( !lp.isValid( ) ) { + data.errors.add( "invalid logging path" , tok ); + } else { + if ( lp.size( ) && cfg->isPathConfigured( lp ) ) { + data.errors.add( "duplicate path entry" , tok ); + } + } + + if ( !cfg->isPathConfigured( lp ) ) { + cfg->addPathConfiguration( lp ); + } + *( data.targetData ) = std::make_pair< SP_LoggingConfiguration , T_LogPath >( + SP_LoggingConfiguration( std::move( cfg ) ) , + T_LogPath( std::move( lp ) ) ); + return true; +} + +// End of path configuration - make sure it's valid +bool PCCheckPath_( T_SRDParserData const& data ) +{ + auto const& input( *data.input ); + T_PathCfg_& pcfg( data.currentData->value< T_PathCfg_ >( ) ); + if ( pcfg.first->logWritersFor( pcfg.second ).size( ) == 0 ) { + data.errors.add( "no loggers for this path" , input[ 1 ] ); + } + const auto min( pcfg.first->minLevelFor( pcfg.second ) ); + const auto max( pcfg.first->maxLevelFor( pcfg.second ) ); + if ( min > max ) { + data.errors.add( "minimal level is higher than maximal level" , input[ 1 ] ); + } + return true; +} + +// Set the minimal or maximal log level for a path +bool PCPathLevel_( T_SRDParserData const& data ) +{ + auto const& input( *data.input ); + T_SRDEnum const& levels( *data.config.enums.get( T_String::Pooled( "log-level" ) ) ); + const E_LogLevel level( (E_LogLevel) levels[ input[ 1 ].stringValue( ) ] ); + + T_PathCfg_& pcfg( data.targetData->value< T_PathCfg_ >( ) ); + if ( input[ 0 ].stringValue( ) == "min-level" ) { + pcfg.first->setMinLevelFor( pcfg.second , level ); + } else { + pcfg.first->setMaxLevelFor( pcfg.second , level ); + } + + return true; +} + +// Add a logger to a path +bool PCPathLogger_( T_SRDParserData const& data ) +{ + auto const& input( *data.input ); + T_String const& logger( input[ 1 ].stringValue( ) ); + T_PathCfg_& pcfg( data.targetData->value< T_PathCfg_ >( ) ); + if ( pcfg.first->hasLogWriter( pcfg.second , logger ) ) { + data.errors.add( "duplicate logger for this path" , input[ 1 ] ); + } else { + pcfg.first->addLogWriter( pcfg.second , logger ); + } + return true; +} + +// Prepare for a logger's configuration +bool PCEnterLogger_( T_SRDParserData const& data , OP_LogWriterFactory const& factory ) +{ + auto const& input( *data.input ); + auto cfg( data.currentData->value< SP_LoggingConfiguration >( ) ); + + auto lconf( factory->createConfiguration( input[ 1 ].stringValue( ) ) ); + if ( cfg->hasLogWriter( input[ 1 ].stringValue( ) ) ) { + data.errors.add( "duplicate logger" , input[ 1 ] ); + } + *( data.targetData ) = lconf; + return true; +} + +// Check a logger's configuration and add it to the list +bool PCCheckLogger_( T_SRDParserData const& data ) +{ + auto lconf( data.currentData->value< RP_LogWriterConfiguration >( ) ); + auto cfg( data.targetData->value< SP_LoggingConfiguration >( ) ); + lconf->check( data.errors , *data.input ); + if ( cfg->hasLogWriter( lconf->name( ) ) ) { + delete lconf; + } else { + cfg->putLogWriter( OwnRawPointer( lconf ) ); + } + return true; +} + +// Set the minimal or maximal log level for a logger +bool PCLoggerLevel_( T_SRDParserData const& data ) +{ + auto const& input( *data.input ); + T_SRDEnum const& levels( *data.config.enums.get( T_String::Pooled( "log-level" ) ) ); + const E_LogLevel level( (E_LogLevel) levels[ input[ 1 ].stringValue( ) ] ); + + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + if ( input[ 0 ].stringValue( ) == "min-level" ) { + lconf->setMinLevel( level ); + } else { + lconf->setMaxLevel( level ); + } + + return true; +} + +}; + +T_SRDParserConfig T_LogWriterRegistry::getParserConfiguration( ) noexcept +{ + auto& pi( p< T_LogWriterRegistryPrivate_ >( ) ); + assert( pi.factories_.size( ) != 0 && "no registered factories" ); + + using namespace lw::SRD; + + T_SRDParserDefs defs( "default" ); + defs << OnStart( PCStart_ ); + + defs.enumeration( "log-level" ) + << "trace" << "debug" << "info" << "notice" + << "warning" << "error" << "critical"; + + defs.context( "default" ) + << ( Rule( ) << "paths" << EnterContext( "paths" ) ) + << ( Rule( ) << "loggers" << EnterContext( "loggers" ) ); + + defs.context( "paths" ) + << ( Rule( ) << ( Alt( ) << String( ) << Word( "root" ) ) + << EnterContext( "path" ) + << OnEnter( PCEnterPath_ ) << OnExit( PCCheckPath_ ) ); + defs.context( "path" ) + << ( Rule( ) << "min-level" << Enum( "log-level" ) << PCPathLevel_ ) + << ( Rule( ) << "max-level" << Enum( "log-level" ) << PCPathLevel_ ) + << ( Rule( ) << "logger" << Word( ) << PCPathLogger_ ); + + T_Array< OP_LogWriterFactory > const& factories( pi.factories_.values( ) ); + const auto nf( factories.size( ) ); + for ( uint32_t i = 0 ; i < nf ; i ++ ) { + OP_LogWriterFactory const& f( factories[ i ] ); + + T_StringBuilder sb( "logger-" ); + sb << f->name( ); + const T_String ctxName( std::move( sb ) ); + + defs.context( "loggers" ) + << ( Rule( ) << Word( f->name( ) ) << Word( ) + << EnterContext( ctxName ) + << OnEnter( [&f]( auto const& data ) { + return PCEnterLogger_( data , f ); + } ) + << OnExit( PCCheckLogger_ ) ); + + auto& ctx( defs.context( ctxName ) ); + ctx << ( Rule( ) << "min-level" << Enum( "log-level" ) << PCLoggerLevel_ ); + ctx << ( Rule( ) << "max-level" << Enum( "log-level" ) << PCLoggerLevel_ ); + f->initializeSyntax( defs , ctx ); + } + + return T_SRDParserConfig( defs ); +} + + +/*= T_LoggingConfiguration ===================================================*/ + +T_LoggingConfiguration::T_PathInfo_::T_PathInfo_( T_LogPath const& path ) + : path( path ) , writers( 8 ) +{ } + +/*----------------------------------------------------------------------------*/ + +T_LoggingConfiguration::T_LoggingConfiguration( ) + : writers_( + []( OP_LogWriterConfiguration const& cfg ) -> T_String const& { + return cfg->name( ); + } , 128 , 128 ) , + paths_( + []( T_PathInfo_ const& pi ) -> T_LogPath const& { + return pi.path; + } , 128 , 128 ) +{ + paths_.add( T_PathInfo_( T_LogPath( ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_LoggingConfiguration::hasLogWriter( T_String const& name ) const +{ + return writers_.contains( name ); +} + +void T_LoggingConfiguration::putLogWriter( OP_LogWriterConfiguration&& configuration ) +{ + const bool ok( writers_.add( std::move( configuration ) ) ); + assert( ok ); +} + +T_Array< OP_LogWriterConfiguration > const& T_LoggingConfiguration::logWriters( ) const +{ + return writers_.values( ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_LoggingConfiguration::isPathConfigured( T_LogPath const& path ) const +{ + return paths_.contains( path ); +} + +void T_LoggingConfiguration::addPathConfiguration( T_LogPath const& path ) +{ + const bool ok( paths_.add( T_PathInfo_( path ) ) ); + assert( ok ); +} + +void T_LoggingConfiguration::setMinLevelFor( T_LogPath const& path , E_LogLevel minLevel ) +{ + T_PathInfo_* const pi( paths_.get( path ) ); + assert( pi != nullptr ); + pi->minLevel = minLevel; +} + +void T_LoggingConfiguration::setMaxLevelFor( T_LogPath const& path , E_LogLevel maxLevel ) +{ + T_PathInfo_* const pi( paths_.get( path ) ); + assert( pi != nullptr ); + pi->maxLevel = maxLevel; +} + +bool T_LoggingConfiguration::hasLogWriter( T_LogPath const& path , T_String const& name ) const +{ + T_PathInfo_ const* const pi( paths_.get( path ) ); + assert( pi != nullptr ); + return pi->writers.contains( name ); +} + +void T_LoggingConfiguration::addLogWriter( T_LogPath const& path , T_String const& name ) +{ + T_PathInfo_ * const pi( paths_.get( path ) ); + assert( pi != nullptr ); + assert( !pi->writers.contains( name ) ); + pi->writers.add( name ); +} + +/*----------------------------------------------------------------------------*/ + +T_LoggingConfiguration::T_PathInfo_ const& T_LoggingConfiguration::getPathInfo( + T_LogPath const& path ) const +{ + T_PathInfo_ const* const pi( paths_.get( path ) ); + if ( pi ) { + return *pi; + } + + T_Array< T_PathInfo_ > const& all( paths_.values( ) ); + const auto sz( all.size( ) ); + uint32_t bestMatch( 0 ); + uint32_t bmLength( 0 ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + auto const& p( all[ i ] ); + if ( p.path.isParentOf( path ) && p.path.size( ) > bmLength ) { + bmLength = p.path.size( ); + bestMatch = i; + } + } + + return paths_[ bestMatch ]; +} + + +/*= T_LoggingSystemPrivate_ ==================================================*/ + +constexpr uint32_t T_LoggingSystemPrivate_::C_MAX_REUSE_SIZE; +constexpr uint32_t T_LoggingSystemPrivate_::C_MAX_REUSE_QUANTITY; +T_LoggingSystemPrivate_ * T_LoggingSystemPrivate_::instance_ = nullptr; +T_Mutex T_LoggingSystemPrivate_::loggersMutex_; +T_Array< T_LoggerPrivate_ * > T_LoggingSystemPrivate_::loggers_( 32 ); + +/*----------------------------------------------------------------------------*/ + +T_LoggingSystemPrivate_::T_LogEntry::T_LogEntry( E_LogLevel level , + uint32_t path , T_LogStringData&& string , uint32_t size ) + : time( std::chrono::high_resolution_clock::now( ) ) , + level( level ) , path( path ) , string( std::move( string ) ) , + size( size ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_LoggingSystemPrivate_::T_LoggingSystemPrivate_( + RP_LoggingSystem main , + T_LogWriterRegistry const& registry , + OP_LoggingConfiguration&& config ) + : main_( main ) , registry_( registry ) , + config_( std::move( config ) ) , writers_( 16 ) , + thread_( ) , queueMutex_( ) , queueCond_( ) , exit_( false ) , + queue_( 16 ) , freeList_( C_MAX_REUSE_QUANTITY ) , + paths_( 8 ) , pathRefCount_( 8 ) +{ + assert( config_ ); + assert( instance_ == nullptr ); + initLogWriters( ); + { + T_ScopeLock lk( loggersMutex_ ); + instance_ = this; + const auto nLoggers( loggers_.size( ) ); + for ( uint32_t i = 0 ; i < nLoggers ; i ++ ) { + loggers_[ i ]->connect( ); + } + } + startLoggingThread( ); +} + +inline T_LoggingSystemPrivate_::~T_LoggingSystemPrivate_( ) +{ + assert( instance_ == this ); + { + T_ScopeLock lk( loggersMutex_ ); + instance_ = nullptr; + const auto nLoggers( loggers_.size( ) ); + for ( uint32_t i = 0 ; i < nLoggers ; i ++ ) { + loggers_[ i ]->connected_ = false; + } + } + stopLoggingThread( ); + writers_.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_LoggingSystemPrivate_::log( E_LogLevel level , uint32_t pathIndex , + char const* data , uint32_t size ) +{ + assert( size ); + assert( pathIndex < paths_.size( ) ); + assert( pathRefCount_[ pathIndex ] > 0 ); + + T_LogStringData lsd; + if ( size > C_MAX_REUSE_SIZE ) { + lsd = NewOwned< T_Buffer< char > >( data , size ); + } + + T_ScopeLock lk( queueMutex_ ); + if ( !lsd ) { + const auto fls( freeList_.size( ) ); + if ( fls == 0 ) { + lsd = NewOwned< T_Buffer< char > >( C_MAX_REUSE_SIZE ); + } else { + lsd = std::move( freeList_[ fls - 1 ] ); + freeList_.remove( fls - 1 ); + } + memcpy( lsd->data( ) , data , size ); + } + + assert( lsd ); + pathRefCount_[ pathIndex ] ++; + queue_.putNew( level , pathIndex , std::move( lsd ) , size ); + queueCond_.notify_one( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_LoggingSystemPrivate_::initLogWriters( ) +{ + auto const& lws( config_->logWriters( ) ); + const auto lwc( lws.size( ) ); + writers_.ensureCapacity( lwc ); + for ( uint32_t i = 0 ; i < lwc ; i ++ ) { + const RPC_LogWriterFactory factory( registry_.get( lws[ i ]->writerType( ) ) ); + writers_.add( factory->createLogWriter( lws[ i ]->clone( ) ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_LoggingSystemPrivate_::startLoggingThread( ) +{ + assert( !thread_ ); + exit_ = false; + thread_ = NewOwned< T_Thread >( [this]() { loggingThread( ); } ); +} + +void T_LoggingSystemPrivate_::stopLoggingThread( ) +{ + assert( thread_ ); + + { + T_ScopeLock lk( queueMutex_ ); + exit_ = true; + } + queueCond_.notify_one( ); + thread_->join( ); + thread_ = T_OwnPtr< T_Thread >( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_LoggingSystemPrivate_::loggingThread( ) +{ + T_Array< T_LogEntry > entries( queue_.growth( ) ); + T_ExclusiveLock lock( queueMutex_ ); + do { + T_LogEntry entry; + + queueCond_.wait( lock , [this]( ) { + return exit_ || queue_.size( ); + } ); + if ( queue_.readAll( entries ) ) { + lock.unlock( ); + } + + uint32_t nEntries( 0 ); + while ( nEntries < entries.size( ) ) { + const auto nne( entries.size( ) ); + for ( uint32_t i = nEntries ; i < nne ; i ++ ) { + writeEntry( entries[ i ] ); + } + + // Try to read new entries + nEntries = nne; + lock.lock( ); + if ( queue_.readAll( entries ) ) { + lock.unlock( ); + } + } + + // Decrease path reference counters + for ( uint32_t i = 0 ; i < nEntries ; i ++ ) { + pathRefCount_[ entries[ i ].path ] --; + } + + // Return string data buffers to the free list + for ( uint32_t i = 0 ; i < nEntries + && freeList_.size( ) < C_MAX_REUSE_QUANTITY ; i ++ ) { + auto& entry( entries[ i ] ); + if ( entry.string->size( ) == C_MAX_REUSE_SIZE ) { + freeList_.add( std::move( entry.string ) ); + } + } + + entries.clear( ); + } while ( !exit_ ); + lock.unlock( ); +} + +void T_LoggingSystemPrivate_::writeEntry( T_LogEntry const& entry ) +{ + T_LogPath const& path( paths_[ entry.path ] ); + + const E_LogLevel min( config_->minLevelFor( path ) ); + const E_LogLevel max( config_->maxLevelFor( path ) ); + if ( min > entry.level || max < entry.level ) { + return; + } + + const T_Array< T_String > writers( config_->logWritersFor( path ) ); + const auto nw( writers.size( ) ); + const auto nwi( writers_.size( ) ); + for ( uint32_t i = 0 ; i < nw ; i ++ ) { + auto const& name( writers[ i ] ); + for ( uint32_t j = 0 ; j < nwi ; j ++ ) { + auto& writer( writers_[ j ] ); + auto const& cfg( writer->configuration< T_LogWriterConfiguration >( ) ); + if ( cfg.name( ) != name ) { + continue; + } + if ( cfg.maxLevel( ) >= entry.level + && cfg.minLevel( ) <= entry.level ) { + writer->log( entry.time , entry.level , path , + entry.string , entry.size ); + } + break; + } + } +} + + +/*= T_LoggerPrivate_ =========================================================*/ + +inline T_LoggerPrivate_::T_LoggerPrivate_( T_LogPath&& path ) + : path_( std::move( path ) ) , connected_( false ) , + pathIndex_( 0 ) , minLevel_( E_LogLevel::TRACE ) , + maxLevel_( E_LogLevel::CRITICAL ) +{ + T_ScopeLock lk( T_LoggingSystemPrivate_::loggersMutex_ ); + T_LoggingSystemPrivate_::loggers_.add( this ); + if ( T_LoggingSystemPrivate_::instance_ ) { + connect( ); + } +} + +inline T_LoggerPrivate_::T_LoggerPrivate_( T_LoggerPrivate_ const& other ) + : path_( other.path_ ) , connected_( other.connected_ ) , + pathIndex_( other.pathIndex_ ) , minLevel_( other.minLevel_ ) , + maxLevel_( other.maxLevel_ ) +{ + T_ScopeLock lk( T_LoggingSystemPrivate_::loggersMutex_ ); + T_LoggingSystemPrivate_::loggers_.add( this ); + assert( connected_ == bool( T_LoggingSystemPrivate_::instance_ ) ); + if ( T_LoggingSystemPrivate_::instance_ ) { + T_ScopeLock lk2( T_LoggingSystemPrivate_::instance_->queueMutex_ ); + T_LoggingSystemPrivate_::instance_->pathRefCount_[ pathIndex_ ] ++; + } +} + +inline T_LoggerPrivate_::~T_LoggerPrivate_( ) +{ + T_ScopeLock lk( T_LoggingSystemPrivate_::loggersMutex_ ); + assert( connected_ == bool( T_LoggingSystemPrivate_::instance_ ) ); + if ( T_LoggingSystemPrivate_::instance_ ) { + T_ScopeLock lk2( T_LoggingSystemPrivate_::instance_->queueMutex_ ); + T_LoggingSystemPrivate_::instance_->pathRefCount_[ pathIndex_ ] --; + } + T_LoggingSystemPrivate_::loggers_.removeSwap( + T_LoggingSystemPrivate_::loggers_.indexOf( this ) ); +} + +/*----------------------------------------------------------------------------*/ + +void T_LoggerPrivate_::connect( ) +{ + T_LoggingSystemPrivate_ * ins( T_LoggingSystemPrivate_::instance_ ); + connected_ = true; + minLevel_ = ins->config_->minLevelFor( path_ ); + maxLevel_ = ins->config_->maxLevelFor( path_ ); + + T_ScopeLock lk2( ins->queueMutex_ ); + const auto found( ins->paths_.indexOf( path_ ) ); + if ( found != -1 ) { + pathIndex_ = found; + ins->pathRefCount_[ found ] ++; + } else { + const auto empty( ins->pathRefCount_.indexOf( 0 ) ); + if ( empty == -1 ) { + pathIndex_ = ins->paths_.add( path_ ); + ins->pathRefCount_.add( 1 ); + } else { + ins->paths_[ empty ] = path_; + ins->pathRefCount_[ empty ] = 1; + } + } +} + + +/*= T_Logger =================================================================*/ + +T_Logger::T_Logger( T_LogPath path ) + : A_PrivateImplementation( new T_LoggerPrivate_( std::move( path ) ) ) +{ } + +T_Logger::T_Logger( T_Logger const& other ) + : A_PrivateImplementation( new T_LoggerPrivate_( + other.p< T_LoggerPrivate_ >( ) ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_Logger::hasLevel( E_LogLevel level ) const +{ + auto const& pi( p< T_LoggerPrivate_ >( ) ); + return pi.connected_ && level >= pi.minLevel_ && level <= pi.maxLevel_; +} + +/*----------------------------------------------------------------------------*/ + +void T_Logger::log( E_LogLevel level , T_String const& string ) +{ + auto const& pi( p< T_LoggerPrivate_ >( ) ); + if ( T_LoggingSystemPrivate_::instance_ && pi.connected_ && string ) { + T_LoggingSystemPrivate_::instance_->log( level , pi.pathIndex_ , + string.data( ) , string.size( ) ); + } +} + +void T_Logger::log( E_LogLevel level , T_StringBuilder const& string ) +{ + auto const& pi( p< T_LoggerPrivate_ >( ) ); + if ( T_LoggingSystemPrivate_::instance_ && pi.connected_ && string ) { + T_LoggingSystemPrivate_::instance_->log( level , pi.pathIndex_ , + string.data( ) , string.size( ) ); + } +} + +void T_Logger::log( E_LogLevel level , char const* string ) +{ + assert( string ); + const auto size( strlen( string ) ); + + auto const& pi( p< T_LoggerPrivate_ >( ) ); + if ( T_LoggingSystemPrivate_::instance_ && pi.connected_ && size ) { + T_LoggingSystemPrivate_::instance_->log( + level , pi.pathIndex_ , string , size ); + } +} + + +/*= T_LoggingSystem ==========================================================*/ + +T_LoggingSystem::T_LoggingSystem( + T_LogWriterRegistry const& registry , + OP_LoggingConfiguration&& config ) + : A_PrivateImplementation( new T_LoggingSystemPrivate_( + this , registry , std::move( config ) ) ) +{ } + +void T_LoggingSystem::reconfigure( OP_LoggingConfiguration&& configuration ) +{ + assert( configuration ); + auto& pi( p< T_LoggingSystemPrivate_>( ) ); + pi.stopLoggingThread( ); + { + T_ScopeLock lk( pi.loggersMutex_ ); + pi.config_ = std::move( configuration ); + + pi.paths_.clear( ); + pi.pathRefCount_.clear( ); + pi.writers_.clear( ); + + pi.initLogWriters( ); + const auto nLoggers( pi.loggers_.size( ) ); + for ( uint32_t i = 0 ; i < nLoggers ; i ++ ) { + pi.loggers_[ i ]->connect( ); + } + } + pi.startLoggingThread( ); +} diff --git a/src/MemoryStreams.cc b/src/MemoryStreams.cc new file mode 100644 index 0000000..52646fb --- /dev/null +++ b/src/MemoryStreams.cc @@ -0,0 +1,60 @@ +/******************************************************************************/ +/* MEMORY STREAMS *************************************************************/ +/******************************************************************************/ + +#include +using namespace lw; + + +/*= T_MemoryInputStream ======================================================*/ + + +T_MemoryInputStream::T_MemoryInputStream( void const* buffer , size_t size ) + : A_InputStream( 0 , size ) , + buffer_( reinterpret_cast< uint8_t const* >( buffer ) ) +{ } + + +size_t T_MemoryInputStream::read( void* data , size_t size ) +{ + if ( position_ == size_ ) { + throw X_StreamError( E_StreamError::END ); + } + + const size_t readCount( position_ + size <= size_ ? size + : ( size_ - position_ ) ); + memcpy( data , buffer_ + position_ , readCount ); + position_ += readCount; + return readCount; +} + + +/*= T_MemoryOutputStream =====================================================*/ + + +T_MemoryOutputStream::T_MemoryOutputStream( void* buffer , size_t size , F_Resizer resizer ) + : A_OutputStream( 0 , size ) , + buffer_( reinterpret_cast< uint8_t* >( buffer ) ) , + resizer_( resizer ) +{ } + + +size_t T_MemoryOutputStream::write( void const* data , size_t size ) +{ + if ( position_ == size_ && !resizer_ ) { + throw X_StreamError( E_StreamError::END ); + } + + size_t ok( size_ - position_ ); + if ( resizer_ && ok < size ) { + size_ = size_ + size - ok; + buffer_ = resizer_( buffer_ , size_ ); + ok = size; + } + + const size_t count( ok > size ? size : ok ); + memcpy( buffer_ + position_ , data , count ); + position_ += count; + return count; +} + diff --git a/src/Messages.cc b/src/Messages.cc new file mode 100644 index 0000000..8012c72 --- /dev/null +++ b/src/Messages.cc @@ -0,0 +1,86 @@ +/******************************************************************************/ +/* UI<=>GAME MESSAGES *********************************************************/ +/******************************************************************************/ + +#include + +using namespace lw; + +#define M_ENUM_OUT_( Type , Value ) \ + case Type::Value: obj << #Value; break + + +/*= A_GameView ===============================================================*/ + +A_GameView::~A_GameView( ) +{ } + + +/*= A_ViewBuilder ============================================================*/ + +A_ViewBuilder::~A_ViewBuilder( ) +{ } + + +/*= E_GameState ==============================================================*/ + +namespace lw { +M_LSHIFT_OP( T_StringBuilder , E_GameState ) +{ + switch ( value ) { + M_ENUM_OUT_( E_GameState , NO_GAME ); + M_ENUM_OUT_( E_GameState , GAME_PAUSED ); + M_ENUM_OUT_( E_GameState , GAME_SLOW ); + M_ENUM_OUT_( E_GameState , GAME_NORMAL ); + M_ENUM_OUT_( E_GameState , GAME_FAST ); + } + return obj; +} +} // namespace lw + + +/*= E_GameUIMessage ==========================================================*/ + +namespace lw { +M_LSHIFT_OP( T_StringBuilder , E_GameUIMessage ) +{ + switch ( value ) { + M_ENUM_OUT_( E_GameUIMessage , NEW ); + M_ENUM_OUT_( E_GameUIMessage , LOAD ); + M_ENUM_OUT_( E_GameUIMessage , SAVE ); + M_ENUM_OUT_( E_GameUIMessage , STOP ); + M_ENUM_OUT_( E_GameUIMessage , QUIT ); + M_ENUM_OUT_( E_GameUIMessage , ABORT ); + M_ENUM_OUT_( E_GameUIMessage , DELETE ); + M_ENUM_OUT_( E_GameUIMessage , COPY_OR_RENAME ); + M_ENUM_OUT_( E_GameUIMessage , SET_SPEED ); + M_ENUM_OUT_( E_GameUIMessage , STEPS ); + M_ENUM_OUT_( E_GameUIMessage , SET_VIEW ); + M_ENUM_OUT_( E_GameUIMessage , VIEW_DISPLAYED ); + M_ENUM_OUT_( E_GameUIMessage , QUERY ); + M_ENUM_OUT_( E_GameUIMessage , COMMAND ); + } + return obj; +} +} // namespace lw + + +/*= E_GameLoopMessage ========================================================*/ + +namespace lw { +M_LSHIFT_OP( T_StringBuilder , E_GameLoopMessage ) +{ + switch ( value ) { + M_ENUM_OUT_( E_GameLoopMessage , TERMINATED ); + M_ENUM_OUT_( E_GameLoopMessage , PROGRESS ); + M_ENUM_OUT_( E_GameLoopMessage , DONE ); + M_ENUM_OUT_( E_GameLoopMessage , STATE_CHANGED ); + M_ENUM_OUT_( E_GameLoopMessage , VIEW_AVAILABLE ); + M_ENUM_OUT_( E_GameLoopMessage , QUERY_RESPONSE ); + M_ENUM_OUT_( E_GameLoopMessage , COMMAND_OK ); + M_ENUM_OUT_( E_GameLoopMessage , COMMAND_SYNTAX ); + M_ENUM_OUT_( E_GameLoopMessage , COMMAND_ERROR ); + } + return obj; +} +} // namespace lw diff --git a/src/ModInterface.cc b/src/ModInterface.cc new file mode 100644 index 0000000..8404c9b --- /dev/null +++ b/src/ModInterface.cc @@ -0,0 +1,20 @@ +/******************************************************************************/ +/* MODDING SYSTEM INTERFACES **************************************************/ +/******************************************************************************/ + +#include +using namespace lw; + + +/*= A_ModBase ===============================================================*/ + +A_ModBase::~A_ModBase( ) noexcept +{ } + + +/*= A_NativeMod ==============================================================*/ + +OP_UserInterface A_NativeMod::getUserInterface( ) const noexcept +{ + return {}; +} diff --git a/src/Mods.cc b/src/Mods.cc new file mode 100644 index 0000000..b24f927 --- /dev/null +++ b/src/Mods.cc @@ -0,0 +1,2646 @@ +/******************************************************************************/ +/* MODDING SYSTEM *************************************************************/ +/******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +using namespace lw; + + +/*= T_ModIdentifier ==========================================================*/ + +int T_ModIdentifier::compare( + T_ModIdentifier const& other ) const noexcept +{ + const int nc( name.compare( other.name ) ); + if ( nc != 0 ) { + return nc; + } + return T_Comparator< uint32_t >::compare( version , other.version ); +} + + +/*= MODULE CONFIGURATION DATA ================================================*/ + +namespace { + +struct T_ModConfig_ +{ + T_String name; + E_ModMode mode; + uint32_t version; + T_Optional< uint32_t > revision; + + explicit T_ModConfig_( + T_String name , + bool exclude ) noexcept; + T_ModConfig_( + T_String name , + uint32_t version ) noexcept; + T_ModConfig_( + T_String name , + uint32_t version , + uint32_t revision ) noexcept; +}; + +struct T_MMConfig_ +{ + T_ObjectTable< T_String , T_ModConfig_ > mods{ + []( T_ModConfig_ const& m ) -> T_String const& { + return m.name; + } , 64 + }; + T_KeyValueTable< T_String , int32_t > ui{ 64 }; +}; + +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_ModConfig_::T_ModConfig_( + T_String name , + const bool exclude ) noexcept + : name( std::move( name ) ) , + mode( exclude ? E_ModMode::EXCLUDE : E_ModMode::REQUIRE ) +{ } + +T_ModConfig_::T_ModConfig_( + T_String name , + const uint32_t version ) noexcept + : name( std::move( name ) ) , + mode( E_ModMode::VERSION ) , + version( version ) +{ } + +T_ModConfig_::T_ModConfig_( + T_String name , + const uint32_t version , + const uint32_t revision ) noexcept + : name( std::move( name ) ) , + mode( E_ModMode::VERSION ) , + version( version ) , + revision( revision ) +{ } + + +/*= MAIN CONFIGURATION PARSER ================================================*/ + +namespace { + +bool MMCModAuto_( + T_SRDParserData const& data ) noexcept +{ + auto const& input( *( data.input ) ); + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + cfg.setAuto( input[ 1 ].stringValue( ) ); + return true; +} + +bool MMCModRequire_( + T_SRDParserData const& data ) noexcept +{ + auto const& input( *( data.input ) ); + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + cfg.setRequired( input[ 1 ].stringValue( ) ); + return true; +} + +bool MMCModExclude_( + T_SRDParserData const& data ) noexcept +{ + auto const& input( *( data.input ) ); + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + cfg.setExcluded( input[ 1 ].stringValue( ) ); + return true; +} + +bool MMCModVersion_( + T_SRDParserData const& data ) noexcept +{ + auto const& input( *( data.input ) ); + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + const bool hasRevision( input.size( ) == 4 ); + + const uint32_t version( ([&](){ + const auto v( input[ 2 ].longValue( ) ); + if ( v < 0 || v > int64_t( UINT32_MAX ) ) { + data.errors.add( "invalid version number" , input[ 2 ] ); + return 0u; + } + return uint32_t( v ); + })( ) ); + + const uint32_t revision( ([&](){ + if ( !hasRevision ) { + return 0u; + } + const auto v( input[ 3 ].longValue( ) ); + if ( v < 0 || v > int64_t( UINT32_MAX ) ) { + data.errors.add( "invalid revision number" , input[ 2 ] ); + return 0u; + } + return uint32_t( v ); + })( ) ); + + if ( hasRevision ) { + cfg.setVersion( input[ 1 ].stringValue( ) , version , revision ); + } else { + cfg.setVersion( input[ 1 ].stringValue( ) , version ); + } + + return true; +} + +bool MMCSetUIPreference_( + T_SRDParserData const& data ) noexcept +{ + auto const& input( *( data.input ) ); + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + const int32_t pref( input.size( ) == 3 ? input[ 2 ].longValue( ) : 0 ); + cfg.setUIPreference( input[ 1 ].stringValue( ) , pref ); + return true; +} + +bool MMCClearUIPreference_( + T_SRDParserData const& data ) noexcept +{ + auto& cfg( *data.currentData->value< SP_ModsManagerConfiguration >( ) ); + cfg.clearUIPreference( (*data.input)[ 1 ].stringValue( ) ); + return true; +} + +} // namespace + + +/*= T_ModsManagerConfiguration ===============================================*/ + +T_ModsManagerConfiguration::T_ModsManagerConfiguration( ) noexcept + : A_PrivateImplementation( new T_MMConfig_( ) ) +{ } + +T_ModsManagerConfiguration::T_ModsManagerConfiguration( + T_ModsManagerConfiguration&& other ) noexcept + : A_PrivateImplementation( std::move( other ) ) +{ } + +T_ModsManagerConfiguration& T_ModsManagerConfiguration::operator =( + T_ModsManagerConfiguration&& other ) noexcept +{ + A_PrivateImplementation::operator =( std::move( other ) ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void T_ModsManagerConfiguration::setAuto( + T_String const& name ) noexcept +{ + p< T_MMConfig_ >( ).mods.remove( name ); +} + +void T_ModsManagerConfiguration::setRequired( + T_String const& name ) noexcept +{ + p< T_MMConfig_ >( ).mods.set( T_ModConfig_{ name , false } ); +} + +void T_ModsManagerConfiguration::setExcluded( + T_String const& name ) noexcept +{ + p< T_MMConfig_ >( ).mods.set( T_ModConfig_{ name , true } ); +} + +void T_ModsManagerConfiguration::setVersion( + T_String const& name , + const uint32_t version ) noexcept +{ + p< T_MMConfig_ >( ).mods.set( T_ModConfig_{ name , version } ); +} + +void T_ModsManagerConfiguration::setVersion( + T_String const& name , + const uint32_t version , + const uint32_t revision ) noexcept +{ + p< T_MMConfig_ >( ).mods.set( + T_ModConfig_{ name , version , revision } ); +} + +/*----------------------------------------------------------------------------*/ + +T_Array< T_String > T_ModsManagerConfiguration::configured( ) const noexcept +{ + return p< T_MMConfig_ >( ).mods.keys( ); +} + +E_ModMode T_ModsManagerConfiguration::modeFor( + T_String const& name ) const noexcept +{ + auto const* const mc( p< T_MMConfig_ >( ).mods.get( name ) ); + return mc ? mc->mode : E_ModMode::AUTO; +} + +uint32_t T_ModsManagerConfiguration::requiredVersion( + T_String const& name ) const noexcept +{ + auto const* const mc( p< T_MMConfig_ >( ).mods.get( name ) ); + assert( mc != nullptr && mc->mode == E_ModMode::VERSION ); + return mc->version; +} + +T_Optional< uint32_t > T_ModsManagerConfiguration::requiredRevision( + T_String const& name ) const noexcept +{ + auto const* const mc( p< T_MMConfig_ >( ).mods.get( name ) ); + assert( mc != nullptr && mc->mode == E_ModMode::VERSION ); + return mc->revision; +} + +/*----------------------------------------------------------------------------*/ + +void T_ModsManagerConfiguration::setUIPreference( + T_String const& name , + const int32_t weight ) noexcept +{ + p< T_MMConfig_ >( ).ui.set( name , weight ); +} + +void T_ModsManagerConfiguration::clearUIPreference( + T_String const& name ) noexcept +{ + p< T_MMConfig_ >( ).ui.remove( name ); +} + +T_Array< T_String > T_ModsManagerConfiguration::uiPreferences( ) const noexcept +{ + auto const& ui( p< T_MMConfig_ >( ).ui ); + T_Array< T_String > names( ui.keys( ) ); + names.sort( [&]( T_String const& a , T_String const& b ) { + return T_Comparator< int32_t >::compare( ui[ a ] , ui[ b ] ); + } ); + return names; +} + +int32_t T_ModsManagerConfiguration::uiPreference( + T_String const& name ) const noexcept +{ + int32_t const* const pref( p< T_MMConfig_ >( ).ui.get( name ) ); + if ( pref == nullptr ) { + return 0; + } + return *pref; +} + + +/*----------------------------------------------------------------------------*/ + +namespace { +/* Add a mod to the list if the specific name/version is not present. + * If it is present, replace the entry if the new record's revision + * number is higher than the one in the list. + */ +void addOrReplaceMod_( + T_Array< RPC_ModInfo >& list , + RPC_ModInfo const& mod ) +{ + auto const nMods( list.size( ) ); + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + auto const& record( *list[ i ] ); + if ( record.identifier != mod->identifier ) { + continue; + } + if ( record.revision < mod->revision ) { + list[ i ] = mod; + } + return; + } + list.add( mod ); +} +} // namespace + +T_Array< RPC_ModInfo > T_ModsManagerConfiguration::filterMods( + T_Array< RPC_ModInfo > const& mods ) const noexcept +{ + const auto nMods( mods.size( ) ); + T_Array< RPC_ModInfo > output{ std::max( 1u , nMods ) }; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + auto const& mod( mods[ i ] ); + auto const& mn( mod->identifier.name ); + auto const m( modeFor( mn ) ); + + if ( m != E_ModMode::VERSION ) { + if ( m != E_ModMode::EXCLUDE ) { + addOrReplaceMod_( output , mod ); + } + continue; + } + + // Right version? + if ( mod->identifier.version != requiredVersion( mn ) ) { + continue; + } + auto const rv( requiredRevision( mn ) ); + if ( rv.present( ) && mod->revision != rv ) { + continue; + } + addOrReplaceMod_( output , mod ); + } + return output; +} + +T_Array< T_String > T_ModsManagerConfiguration::requiredMods( ) const noexcept +{ + auto const& pi( p< T_MMConfig_ >( ) ); + auto const nConfig( pi.mods.size( ) ); + uint32_t nRequired( 0 ); + for ( uint32_t i = 0 ; i < nConfig ; i ++ ) { + auto const m( pi.mods[ i ].mode ); + if ( m == E_ModMode::VERSION || m == E_ModMode::REQUIRE ) { + nRequired ++; + } + } + + T_Array< T_String > output{ std::max( 1u , nRequired ) }; + for ( uint32_t i = 0 ; i < nConfig ; i ++ ) { + auto const m( pi.mods[ i ].mode ); + if ( m == E_ModMode::VERSION || m == E_ModMode::REQUIRE ) { + output.add( pi.mods[ i ].name ); + if ( output.size( ) == nRequired ) { + break; + } + } + } + return output; +} + +/*----------------------------------------------------------------------------*/ + +T_ModsManagerConfiguration T_ModsManagerConfiguration::DefaultConfiguration( ) noexcept +{ + const T_String mGame( T_String::Pooled( "lw-game" ) ); + const T_String mConsoleUI( T_String::Pooled( "lw-ui-console" ) ); + const T_String mGLUI( T_String::Pooled( "lw-ui-gl" ) ); + + T_ModsManagerConfiguration config; + config.setRequired( mGame ); + config.setUIPreference( mConsoleUI , 100 ); + config.setUIPreference( mGLUI , 1000 ); + return config; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDParserConfig T_ModsManagerConfiguration::GetParserConfig( ) +{ + using namespace lw::SRD; + T_SRDParserDefs defs( "default" ); + defs << OnStart( []( T_SRDParserData const& data ) -> bool { + *( data.currentData ) = NewShared< T_ModsManagerConfiguration >( ); + return true; + } ); + + defs.context( "default" ) + << ( Rule( ) << "auto" << Word( ) + << MMCModAuto_ ) + << ( Rule( ) << "require" << Word( ) + << MMCModRequire_ ) + << ( Rule( ) << "exclude" << Word( ) + << MMCModExclude_ ) + << ( Rule( ) << "version" << Word( ) << Int32( ) << Opt( Int32( ) ) + << MMCModVersion_ ) + << ( Rule( ) << "ui" << Word( ) << Opt( Int32( ) ) + << MMCSetUIPreference_ ) + << ( Rule( ) << "clear-ui" << Word( ) + << MMCClearUIPreference_ ) + ; + + return defs; +} + + +/*= T_MDData_ - Private data for T_ModsDependencies ==========================*/ + +using T_ModsList_ = T_Array< RPC_ModInfo >; + +namespace { + +struct T_UIMods_ +{ + T_String name; + T_ModsList_ mods{ 1 }; +}; + +struct T_MDData_ +{ + bool success = false; + bool ambiguous = false; + T_ModsList_ commonMods{ 1 }; + T_Array< T_UIMods_ > uiMods{ 1 }; +}; + +} // namespace + + +/*= MODS DEPENDENCIES RESOLUTION =============================================*/ + +namespace { + +using T_ModsTable_ = T_ObjectTable< T_ModIdentifier , RPC_ModInfo >; + +T_ModsTable_ makeModsTable_( + T_Array< RPC_ModInfo > const& mods ) noexcept +{ + const uint32_t nMods{ mods.size( ) }; + T_ModsTable_ mt{ + []( RPC_ModInfo mi ) -> T_ModIdentifier const& { + return mi->identifier; + } , nMods , 128 + }; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + mt.add( mods[ i ] ); + } + return mt; +} + +/*----------------------------------------------------------------------------*/ + +using T_MinMaxVersions_ = std::pair< uint32_t , uint32_t >; +using T_ModVersions_ = T_KeyValueTable< T_String , T_MinMaxVersions_ >; + +T_ModVersions_ makeModVersions_( + T_Array< RPC_ModInfo > const& mods ) noexcept +{ + const uint32_t nMods( mods.size( ) ); + T_ModVersions_ versions{ nMods , 128 }; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + auto const& mi( mods[ i ]->identifier ); + auto* previous( versions.get( mi.name ) ); + const auto v( mi.version ); + if ( previous == nullptr ) { + versions.add( mi.name , T_MinMaxVersions_( v , v ) ); + } else { + versions.update( mi.name , T_MinMaxVersions_( + std::min( previous->first , v ) , + std::max( previous->second , v ) ) ); + } + } + return versions; +} + +/*----------------------------------------------------------------------------*/ + +/* Structure used to represent graph nodes while building the + * dependency graph. + */ +struct T_MDRNode_ +{ + RPC_ModInfo mod; + bool valid; + T_Array< RPC_ModIdentifier > deps{ 16 }; + T_Array< T_String > uiDeps{ 4 }; + T_Optional< T_Array< RPC_ModIdentifier > > fullDeps; + T_Array< RPC_ModIdentifier > revDeps{ 16 }; + T_Optional< uint32_t > sgId; + + struct T_Dup {}; + + T_MDRNode_( + RPC_ModInfo mod , + bool valid , + T_Array< RPC_ModIdentifier >&& deps , + T_Array< T_String >&& uiDeps ) noexcept; + + T_MDRNode_( T_Dup , + T_MDRNode_ const& node ) noexcept; +}; + +/*----------------------------------------------------------------------------*/ + +T_MDRNode_::T_MDRNode_( + const RPC_ModInfo mod , + const bool valid , + T_Array< RPC_ModIdentifier >&& deps , + T_Array< T_String >&& uiDeps ) noexcept + : mod( mod ) , valid( valid ) , + deps( std::move( deps ) ) , uiDeps( std::move( uiDeps ) ) , + fullDeps( ) , revDeps( 16 ) , sgId( ) +{ } + + +T_MDRNode_::T_MDRNode_( + T_Dup , + T_MDRNode_ const& node ) noexcept + : mod( node.mod ) , valid( true ) , + deps( node.deps ) , fullDeps( node.fullDeps ) , + revDeps( 16 ) , sgId( ) +{ } + +/*----------------------------------------------------------------------------*/ + +struct T_MDRGraph_ +{ + T_Array< T_MDRNode_ > nodes; + T_String key; + float score; + uint32_t weight; + + explicit T_MDRGraph_( + const uint32_t nodesGrowth = 64 ) noexcept; + + /* Find a node based on a mod identifier. Returns INVALID_INDEX + * if no such node exists. + */ + uint32_t findNode( + T_ModIdentifier const& id ) const noexcept; + /* Remove invalid nodes from the graph */ + void removeBadNodes( ) noexcept; + /* Checks whether the graph is final (i.e. only has one + * version for each mod name). + */ + bool isFinal( ) const noexcept; + /* Generates the graph's "key", a sorted string of mod names. */ + void genKey( ) noexcept; + /* Compute the graph's score and weight */ + void computeScore( + T_ModVersions_ const& versions ) noexcept; +}; + +/*----------------------------------------------------------------------------*/ + +T_MDRGraph_::T_MDRGraph_( + const uint32_t nodesGrowth ) noexcept + : nodes( std::max( 1u , nodesGrowth ) ) +{ } + +uint32_t T_MDRGraph_::findNode( + T_ModIdentifier const& id ) const noexcept +{ + const uint32_t nn( nodes.size( ) ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + if ( nodes[ i ].mod->identifier == id ) { + return i; + } + } + return T_HashIndex::INVALID_INDEX; +} + +void T_MDRGraph_::removeBadNodes( ) noexcept +{ + uint32_t i = 0; + while ( i < nodes.size( ) ) { + if ( nodes[ i ].valid ) { + i ++; + } else { + nodes.removeSwap( i ); + } + } +} + +bool T_MDRGraph_::isFinal( ) const noexcept +{ + const uint32_t nn( nodes.size( ) ); + assert( nn > 0 ); + for ( uint32_t i = 0 ; i < nn - 1 ; i ++ ) { + T_String const& tnn( nodes[ i ].mod->identifier.name ); + for ( uint32_t j = i + 1 ; j < nn ; j ++ ) { + if ( tnn == nodes[ j ].mod->identifier.name ) { + return false; + } + } + } + return true; +} + +void T_MDRGraph_::genKey( ) noexcept +{ + const uint32_t nn( nodes.size( ) ); + assert( nn > 0 ); + + RPC_ModIdentifier ids[ nn ]; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + ids[ i ] = &( nodes[ i ].mod->identifier ); + } + Sort< RPC_ModIdentifier >( ids , nn , + []( RPC_ModIdentifier a , RPC_ModIdentifier b ) -> int { + return a->compare( *b ); + } ); + + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + if ( i != 0 ) { + sb << ' '; + } + sb << *ids[ i ]; + } + key = std::move( sb ); +} + +void T_MDRGraph_::computeScore( + T_ModVersions_ const& versions ) noexcept +{ + const uint32_t nn( nodes.size( ) ); + assert( nn > 0 ); + + float sum = 0; + weight = 0; + + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& mi( nodes[ i ].mod->identifier ); + auto const& mmv( *( versions.get( mi.name ) ) ); + const uint32_t nWeight( 1 + nodes[ i ].revDeps.size( ) ); + if ( mmv.first == mmv.second ) { + sum += nWeight; + } else { + sum += ( mi.version - mmv.first ) * nWeight + / float( mmv.second - mmv.first ); + } + weight += nWeight; + } + score = sum / float( weight ); +} + +/*----------------------------------------------------------------------------*/ + +int LoadOrderCmp_( RPC_ModInfo a , RPC_ModInfo b ) +{ + if ( a->modDeps.contains( b->identifier ) ) { + return 1; + } else if ( b->modDeps.contains( a->identifier ) ) { + return -1; + } + return a->identifier.compare( b->identifier ); +} + +/*----------------------------------------------------------------------------*/ + +struct T_MDResolver_ +{ + T_Logger& logger; + T_Array< T_String > const& required; + const T_ModsTable_ mods; + const T_ModVersions_ versions; + T_MDData_& output; + + T_MDResolver_( + T_Logger& logger , + T_Array< RPC_ModInfo > const& mods , + T_Array< T_String > const& required , + T_MDData_& output ) noexcept; + + /* Add a mod to the initial graph of mods and dependencies. + * All dependencies for the mod will be added. Will return + * true on success, or false if there is a cyclic dependency + * OR a dependency is missing OR a mod depends on more than + * one UI mod (this also prevents UI mods dependending on + * other UI mods). + */ + bool addModToInitialGraph( + T_MDRGraph_& graph , + T_ModIdentifier const& mod , + T_Array< T_ModIdentifier >& stack ) noexcept; + + /* Generate the list of transitive dependencies for a node */ + void generateFullDependencies( + T_MDRGraph_& graph , + const uint32_t nid ) noexcept; + /* Generate the list of transitive reverse dependencies for a node */ + void generateReverseDependencies( + T_MDRGraph_& graph , + const uint32_t nid ) noexcept; + /* Generate all transitive dependencies for a graph */ + void generateTransDepedencies( + T_MDRGraph_& graph ) noexcept; + + /* Build the initial graph from tha table of mods */ + T_MDRGraph_ buildInitialGraph( ) noexcept; + + /* Gather subgraph identifiers for a set of mods */ + void gatherSubgraphIds( + T_MDRGraph_& graph , + T_Array< RPC_ModIdentifier > const& deps , + T_Array< uint32_t >& ids ) noexcept; + /* Replace listed subgraph ids with another id */ + void replaceSubgraphIds( + T_Array< T_MDRNode_ >& nodes , + const uint32_t newId , + T_Array< uint32_t > const& replace ) noexcept; + /* Set the subgraph id for a set of nodes */ + void setSubgraphIds( + T_MDRGraph_& graph , + T_Array< RPC_ModIdentifier > const& ids , + const uint32_t sgId ) noexcept; + /* Find disjoint subgraphs in a graph, returning the array + * of subgraph identifiers. + */ + T_Array< uint32_t > findSubgraphs( + T_MDRGraph_& graph ) noexcept; + + /* Checks whether a subgraph contains all required mods + * (and at least one UI mod) + */ + bool checkSubgraph( + T_MDRGraph_ const& graph , + const uint32_t sgId ) noexcept; + /* Remove nodes that have the specified subgraph ID */ + void removeSubgraphNodes( + T_MDRGraph_& graph , + const uint32_t sgId ) noexcept; + /* Create a new graph based on a subgraph */ + T_MDRGraph_ subgraphToGraph( + T_MDRGraph_ const& graph , + const uint32_t sgId ) noexcept; + /* Create new graphs based on the specified subgraphs */ + T_Array< T_MDRGraph_ > subgraphsToGraphs( + T_MDRGraph_ const& graph , + T_Array< uint32_t> const& subgraphs ) noexcept; + + /* Generate a graph that will contain only one version of a mod. Once + * the graph is generated, it will be checked against the requirements + * and eliminated if it doesn't match. + */ + T_Optional< T_MDRGraph_ > reduceGraph( + T_MDRGraph_ const& source , + T_ModIdentifier const& wanted ) noexcept; + /* Generate reduced graphs from the first item in a list of graphs. + * The resulting graphs will be added to the list. They may or may not + * be final. + */ + void generateReducedGraphs( + T_Array< T_MDRGraph_ >& graphs ) noexcept; + /* Generate all final graphs from a list of graphs. */ + void generateFinalGraphs( + T_Array< T_MDRGraph_ >& graphs ) noexcept; + + /* Get the graph that will be used from the initial + * graph and the list of subgraphs. */ + T_Optional< T_MDRGraph_ > getGraph( + T_MDRGraph_ const& initialGraph , + T_Array< uint32_t > const& subgraphs ) noexcept; + + /* Copy a node and its dependencies from a graph to another. */ + void copyNodes( + T_MDRGraph_ const& source , + uint32_t node , + T_MDRGraph_& dest , + bool copyReverseDeps ) const noexcept; + + /* Split the final graph into UI-specific graphs */ + T_Array< T_MDRGraph_ > getUIGraphs( + T_MDRGraph_ const& graph ) noexcept; + /* Extract common mods from the various graphs */ + T_Array< RPC_ModInfo > findCommonMods( + T_MDRGraph_ const& graph , + T_Array< T_MDRGraph_ > const& uiGraphs ) noexcept; + /* Extract mods for a given UI */ + T_UIMods_ extractUIMods( + T_MDRGraph_ const& uiGraph , + T_ModsList_ const& commonMods ) noexcept; + /* Generate the list of UI mods */ + T_Array< T_UIMods_ > generateUILists( + T_Array< T_MDRGraph_ > const& uiGraphs , + T_ModsList_ const& commonMods ) noexcept; +}; + +/*----------------------------------------------------------------------------*/ + +T_MDResolver_::T_MDResolver_( + T_Logger& logger , + T_Array< RPC_ModInfo > const& mods , + T_Array< T_String > const& required , + T_MDData_& output ) noexcept + : logger( logger ) , required( required ) , + mods( makeModsTable_( mods ) ) , + versions( makeModVersions_( mods ) ) , + output( output ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_MDResolver_::addModToInitialGraph( + T_MDRGraph_& graph , + T_ModIdentifier const& mod , + T_Array< T_ModIdentifier >& stack ) noexcept +{ + if ( stack.contains( mod ) ) { + logger.warning( ) << "Mod " << mod + << " is part of a cycle."; + return false; + } + + const uint32_t initialIndex( graph.findNode( mod ) ); + if ( initialIndex != T_HashIndex::INVALID_INDEX ) { + return graph.nodes[ initialIndex ].valid; + } + + RPC_ModInfo const* const miPtr( mods.get( mod ) ); + if ( miPtr == nullptr ) { + logger.warning( ) << "Mod " << mod + << " is missing."; + return false; + } + T_ModInfo const& mi( **miPtr ); + + bool valid( true ); + T_Array< RPC_ModIdentifier > deps{ 16 }; + T_Array< T_String > uiDeps{ 4 }; + if ( mi.isUserInterface( ) ) { + uiDeps.add( mi.identifier.name ); + } + + // Push to stack + const uint32_t stackIndex( stack.size( ) ); + stack.add( mod ); + + // Handle dependencies + const uint32_t nDeps( mi.modDeps.size( ) ); + for ( uint32_t i = 0 ; i < nDeps ; i ++ ) { + T_ModIdentifier const& di( mi.modDeps[ i ] ); + logger.trace( ) << "Checking " + << mod << " -> " << di + << " dependency"; + + const bool validDep( addModToInitialGraph( + graph , di , stack ) ); + assert( stack.size( ) == stackIndex + 1 ); + assert( stack[ stackIndex ] == mod ); + if ( !validDep ) { + valid = false; + continue; + } + + const uint32_t didx( graph.findNode( di ) ); + assert( didx != T_HashIndex::INVALID_INDEX ); + const RPC_ModIdentifier miPtr( &( graph.nodes[ didx ].mod->identifier ) ); + assert( !deps.contains( miPtr ) ); + deps.add( miPtr ); + + auto const& dr( graph.nodes[ didx ] ); + auto const nuid( dr.uiDeps.size( ) ); + for ( uint32_t j = 0 ; j < nuid ; j ++ ) { + if ( !uiDeps.contains( dr.uiDeps[ j ] ) ) { + uiDeps.add( dr.uiDeps[ j ] ); + } + } + } + if ( uiDeps.size( ) > 1 ) { + valid = false; + T_StringBuilder sb( "Mod " ); + sb << mod << " has more than 1 UI dependency:"; + for ( uint32_t i = 0 ; i < uiDeps.size( ) ; i ++ ) { + sb << ' ' << uiDeps[ i ]; + } + logger.warning( std::move( sb ) ); + } + + // Pop stack + stack.remove( stackIndex ); + + // Add the node + graph.nodes.addNew( *miPtr , valid , std::move( deps ) , + std::move( uiDeps ) ); + logger.trace( ) << "Added " + << ( valid ? "valid" : "invalid" ) + << " mod " << mod << " to the graph"; + return valid; +} + +/*----------------------------------------------------------------------------*/ + +void T_MDResolver_::generateFullDependencies( + T_MDRGraph_& graph , + const uint32_t nid ) noexcept +{ + auto& node( graph.nodes[ nid ] ); + if ( node.fullDeps.present( ) ) { + return; + } + + const uint32_t nd( node.deps.size( ) ); + T_Array< RPC_ModIdentifier > fd{ std::max( 1u , nd * 2 ) }; + for ( uint32_t i = 0 ; i < nd ; i ++ ) { + const auto did( node.deps[ i ] ); + if ( !fd.contains( did ) ) { + fd.add( did ); + } + + const auto didx( graph.findNode( *did ) ); + generateFullDependencies( graph , didx ); + + const auto& dep( graph.nodes[ didx ] ); + const auto& dfd( ( T_Array< RPC_ModIdentifier > const&) dep.fullDeps ); + const uint32_t nfd( dfd.size( ) ); + for ( uint32_t j = 0 ; j < nfd ; j ++ ) { + if ( !fd.contains( dfd[ j ] ) ) { + fd.add( dfd[ j ] ); + } + } + } + node.fullDeps = fd; + + if ( logger.hasLevel( E_LogLevel::TRACE ) ) { + if ( fd.size( ) == 0 ) { + logger.trace( ) << "No transitive dependencies for " + << node.mod->identifier; + } else { + T_StringBuilder sb( "Transitive dependencies for " ); + sb << node.mod->identifier << ':'; + const auto nfd( fd.size( ) ); + for ( uint32_t i = 0 ; i < nfd ; i ++ ) { + sb << ' ' << graph.nodes[ + graph.findNode( *fd[ i ] ) + ].mod->identifier; + } + logger.trace( std::move( sb ) ); + } + } +} + +void T_MDResolver_::generateReverseDependencies( + T_MDRGraph_& graph , + const uint32_t nid ) noexcept +{ + auto const& node( graph.nodes[ nid ] ); + const auto identifier{ &( node.mod->identifier ) }; + + auto const& dfd( ( T_Array< RPC_ModIdentifier > const&) node.fullDeps ); + const uint32_t nfd( dfd.size( ) ); + for ( uint32_t i = 0 ; i < nfd ; i ++ ) { + auto& tgt( graph.nodes[ graph.findNode( *dfd[ i ] ) ] ); + if ( !tgt.revDeps.contains( identifier ) ) { + tgt.revDeps.add( identifier ); + } + } +} + +void T_MDResolver_::generateTransDepedencies( + T_MDRGraph_& graph ) noexcept +{ + const uint32_t nn( graph.nodes.size( ) ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + generateFullDependencies( graph , i ); + } + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + generateReverseDependencies( graph , i ); + } + if ( !logger.hasLevel( E_LogLevel::TRACE ) ) { + return; + } + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& node( graph.nodes[ i ] ); + const auto& rd( node.revDeps ); + if ( rd.size( ) == 0 ) { + logger.trace( ) << "No transitive reverse dependencies for " + << node.mod->identifier; + } else { + T_StringBuilder sb( "Transitive reverse dependencies for " ); + sb << node.mod->identifier << ':'; + const auto nrd( rd.size( ) ); + for ( uint32_t i = 0 ; i < nrd ; i ++ ) { + sb << ' ' << *rd[ i ]; + } + logger.trace( std::move( sb ) ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +T_MDRGraph_ T_MDResolver_::buildInitialGraph( ) noexcept +{ + T_MDRGraph_ graph; + const uint32_t nMods( mods.size( ) ); + T_Array< T_ModIdentifier > stack; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + addModToInitialGraph( graph , mods[ i ]->identifier , stack ); + assert( stack.size( ) == 0 ); + } + graph.removeBadNodes( ); + if ( graph.nodes.size( ) ) { + generateTransDepedencies( graph ); + } + return graph; +} + +/*----------------------------------------------------------------------------*/ + +void T_MDResolver_::gatherSubgraphIds( + T_MDRGraph_& graph , + T_Array< RPC_ModIdentifier > const& deps , + T_Array< uint32_t >& ids ) noexcept +{ + const uint32_t nd{ deps.size( ) }; + for ( uint32_t j = 0 ; j < nd ; j ++ ) { + auto const& dn( graph.nodes[ graph.findNode( *deps[ j ] ) ] ); + if ( dn.sgId.present( ) ) { + const uint32_t v( dn.sgId ); + if ( !ids.contains( v ) ) { + ids.add( v ); + } + } + } +} + +void T_MDResolver_::replaceSubgraphIds( + T_Array< T_MDRNode_ >& nodes , + const uint32_t newId , + T_Array< uint32_t > const& replace ) noexcept +{ + const uint32_t nn( nodes.size( ) ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto& node( nodes[ i ] ); + if ( !node.sgId.present( ) ) { + continue; + } + const uint32_t oldId( node.sgId ); + if ( replace.contains( oldId ) ) { + node.sgId = newId; + } + } +} + +void T_MDResolver_::setSubgraphIds( + T_MDRGraph_& graph , + T_Array< RPC_ModIdentifier > const& ids , + const uint32_t sgId ) noexcept +{ + const auto ni( ids.size( ) ); + for ( uint32_t i = 0 ; i < ni ; i ++ ) { + graph.nodes[ graph.findNode( *ids[ i ] ) ].sgId = sgId; + } +} + +/*----------------------------------------------------------------------------*/ + +T_Array< uint32_t > T_MDResolver_::findSubgraphs( + T_MDRGraph_& graph ) noexcept +{ + uint32_t nextId( 0 ); + T_Array< uint32_t > subgraphIds{ 16 }; + T_Array< uint32_t > curIds{ 16 }; + const auto nn( graph.nodes.size( ) ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto& node( graph.nodes[ i ] ); + if ( node.sgId.present( ) ) { + continue; + } + + // Find existing identifiers + curIds.clear( ); + gatherSubgraphIds( graph , node.fullDeps , curIds ); + gatherSubgraphIds( graph , node.revDeps , curIds ); + + // Get the nodes' new subgraph identifier + const uint32_t assign( ([&](){ + if ( curIds.size( ) != 0 ) { + const uint32_t temp( curIds[ 0 ] ); + curIds.removeSwap( 0 ); + return temp; + } else { + subgraphIds.add( nextId ); + return nextId ++; + } + })( ) ); + + // Replace other identifiers if there's more than 1 + if ( curIds.size( ) != 0 ) { + replaceSubgraphIds( graph.nodes , assign , curIds ); + for ( uint32_t j = 0 ; j < curIds.size( ) ; j ++ ) { + subgraphIds.removeSwap( subgraphIds.indexOf( curIds[ j ] ) ); + } + } + + // Assign new ID to node, deps and rdeps + node.sgId = assign; + setSubgraphIds( graph , node.fullDeps , assign ); + setSubgraphIds( graph , node.revDeps , assign ); + } + + // XXX assert all nodes have a subgraph ID + logger.trace( ) << "Generated " << subgraphIds.size( ) << " subgraphs"; + return subgraphIds; +} + +/*----------------------------------------------------------------------------*/ + +bool T_MDResolver_::checkSubgraph( + T_MDRGraph_ const& graph , + const uint32_t sgId ) noexcept +{ + bool rFound[ required.size( ) ]; + for ( uint32_t i = 0 ; i < required.size( ) ; i ++ ) { + rFound[ i ] = false; + } + + const uint32_t nn( graph.nodes.size( ) ); + bool hasUI( false ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& node( graph.nodes[ i ] ); + if ( node.sgId != sgId ) { + continue; + } + hasUI = hasUI || node.mod->isUserInterface( ); + + const auto rIdx( required.indexOf( node.mod->identifier.name ) ); + if ( rIdx != -1 ) { + rFound[ rIdx ] = true; + } + } + if ( !hasUI ) { + logger.trace( ) << "Subgraph #" << sgId + << ": no user interface"; + } + + bool ok( hasUI ); + for ( uint32_t i = 0 ; i < required.size( ) ; i ++ ) { + if ( !rFound[ i ] ) { + logger.trace( ) << "Subgraph #" << sgId + << ": missing required mod " << required[ i ]; + ok = false; + } + } + return ok; +} + +void T_MDResolver_::removeSubgraphNodes( + T_MDRGraph_& graph , + const uint32_t sgId ) noexcept +{ + uint32_t i = 0; + while ( i < graph.nodes.size( ) ) { + if ( graph.nodes[ i ].sgId == sgId ) { + graph.nodes.removeSwap( i ); + } else { + i ++; + } + } +} + +T_MDRGraph_ T_MDResolver_::subgraphToGraph( + T_MDRGraph_ const& graph , + const uint32_t sgId ) noexcept +{ + const uint32_t nn( graph.nodes.size( ) ); + const uint32_t ntn( ([&]() { + uint32_t ntn = 0; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + if ( graph.nodes[ i ].sgId == sgId ) { + ntn ++; + } + } + return ntn; + })( ) ); + assert( ntn != 0 ); + + T_MDRGraph_ ng{ ntn }; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& node( graph.nodes[ i ] ); + if ( node.sgId == sgId ) { + const uint32_t idx( ng.nodes.add( node ) ); + ng.nodes[ idx ].sgId.clear( ); + } + } + return ng; +} + +T_Array< T_MDRGraph_ > T_MDResolver_::subgraphsToGraphs( + T_MDRGraph_ const& graph , + T_Array< uint32_t> const& subgraphs ) noexcept +{ + const uint32_t nsg( subgraphs.size( ) ); + T_Array< T_MDRGraph_ > output{ std::max( 1u , 3 * nsg / 2 ) }; + for ( uint32_t i = 0 ; i < nsg ; i ++ ) { + output.add( subgraphToGraph( graph , subgraphs[ i ] ) ); + } + return output; +} + +/*----------------------------------------------------------------------------*/ + +T_Optional< T_MDRGraph_ > T_MDResolver_::reduceGraph( + T_MDRGraph_ const& source , + T_ModIdentifier const& wanted ) noexcept +{ + T_MDRGraph_ output( source ); + uint32_t i = 0; + while ( i < output.nodes.size( ) ) { + auto const& node( output.nodes[ i ] ); + auto const& nid( node.mod->identifier ); + if ( nid == wanted ) { + continue; + } + + bool remove; + if ( nid.name == wanted.name ) { + remove = true; + } else { + T_Array< RPC_ModIdentifier > const& fd( node.fullDeps ); + const uint32_t nfd( fd.size( ) ); + remove = false; + for ( uint32_t i = 0 ; i < nfd && !remove ; i ++ ) { + auto const& mi( *fd[ i ] ); + remove = mi.name == nid.name && mi.version != nid.version; + } + } + + if ( remove ) { + output.nodes.removeSwap( i ); + } else { + i ++; + } + } + + // Clear full reverse deps and regenerate them + const uint32_t nn( output.nodes.size( ) ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + output.nodes[ i ].revDeps.clear( ); + } + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + generateReverseDependencies( output , i ); + } + + // Now extract subgraphs and find the one that contains our target + findSubgraphs( output ); + auto const nid( output.findNode( wanted ) ); + uint32_t const sg( output.nodes[ nid ].sgId ); + if ( checkSubgraph( output , sg ) ) { + return subgraphToGraph( output , sg ); + } else { + return {}; + } +} + +void T_MDResolver_::generateReducedGraphs( + T_Array< T_MDRGraph_ >& graphs ) noexcept +{ + T_MDRGraph_ const& initial( graphs[ 0 ] ); + const uint32_t nn( initial.nodes.size( ) ); + + // Count the amount of different versions for each mod name + RPC_String names[ nn ]; + uint32_t counters[ nn ]; + uint32_t nNames = 0; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& nid( initial.nodes[ i ].mod->identifier ); + bool found( false ); + for ( uint32_t j = 0 ; j < nNames && !found ; j ++ ) { + if ( *names[ j ] == nid.name ) { + counters[ j ] ++; + found = true; + } + } + if ( !found ) { + names[ nNames ] = &nid.name; + counters[ nNames ] = 0; + nNames ++; + } + } + + // Generate graphs + for ( uint32_t i = 0 ; i < nNames ; i ++ ) { + if ( counters[ i ] == 0 ) { + continue; + } + for ( uint32_t j = 0 ; j < nn ; j ++ ) { + auto const& nid( initial.nodes[ j ].mod->identifier ); + if ( nid.name != *names[ i ] ) { + continue; + } + + auto gen( reduceGraph( initial , nid ) ); + if ( gen.present( ) ) { + logger.trace( ) + << "Generated new graph using " + << nid; + graphs.add( std::move( gen ) ); + } + } + } +} + +void T_MDResolver_::generateFinalGraphs( + T_Array< T_MDRGraph_ >& graphs ) noexcept +{ + T_Array< T_MDRGraph_ > output{ graphs.growth( ) }; + while ( graphs.size( ) != 0 ) { + T_MDRGraph_& initial( graphs[ 0 ] ); + if ( initial.isFinal( ) ) { + initial.genKey( ); + const uint32_t os( output.size( ) ); + bool found( false ); + for ( uint32_t i = 0 ; i < os ; i ++ ) { + if ( output[ i ].key == initial.key ) { + found = true; + break; + } + } + if ( !found ) { + logger.trace( ) + << "Adding final graph " + << initial.key; + output.add( std::move( initial ) ); + } + } else { + generateReducedGraphs( graphs ); + } + + graphs.removeSwap( 0 ); + } + logger.trace( ) << output.size( ) << " final graphs"; + graphs = output; +} + +/*----------------------------------------------------------------------------*/ + +T_Optional< T_MDRGraph_ > T_MDResolver_::getGraph( + T_MDRGraph_ const& initialGraph , + T_Array< uint32_t > const& subgraphs ) noexcept +{ + T_Array< T_MDRGraph_ > graphs{ subgraphsToGraphs( initialGraph , subgraphs ) }; + generateFinalGraphs( graphs ); + const uint32_t nGraphs( graphs.size( ) ); + if ( nGraphs == 0 ) { + logger.warning( ) << "No set of mods satisfies the configuration"; + return {}; + } + + const bool trace( logger.hasLevel( E_LogLevel::TRACE ) ); + uint32_t keepIndex = T_HashIndex::INVALID_INDEX , + maxWeight = 0 , + ambiguous = 0; + float maxScore = 0; + for ( uint32_t j = 0 ; j < nGraphs ; j ++ ) { + graphs[ j ].computeScore( versions ); + if ( trace ) { + logger.trace( ) << "Score/Weight ( " + << graphs[ j ].key << " ) = " + << graphs[ j ].score << " / " + << graphs[ j ].weight; + } + if ( graphs[ j ].score > maxScore ) { + keepIndex = j; + maxScore = graphs[ j ].score; + maxWeight = graphs[ j ].weight; + ambiguous = 0; + } else if ( graphs[ j ].score == maxScore + && graphs[ j ].weight > maxWeight ) { + keepIndex = j; + maxWeight = graphs[ j ].weight; + ambiguous = 0; + } else if ( graphs[ j ].score == maxScore + && graphs[ j ].weight == maxWeight ) { + ambiguous ++; + } + } + assert( keepIndex != T_HashIndex::INVALID_INDEX ); + output.ambiguous = ( ambiguous != 0 ); + if ( ambiguous ) { + logger.notice( ) << "Selected graph (" + << graphs[ keepIndex ].key + << ") but there were ambiguities."; + } else if ( trace ) { + logger.trace( ) << "Using graph (" + << graphs[ keepIndex ].key << ')'; + } + return std::move( graphs[ keepIndex ] ); +} + +/*----------------------------------------------------------------------------*/ + +void T_MDResolver_::copyNodes( + T_MDRGraph_ const& source , + const uint32_t nid , + T_MDRGraph_& dest , + const bool copyReverseDeps ) const noexcept +{ + auto const& nsrc( source.nodes[ nid ] ); + if ( dest.findNode( nsrc.mod->identifier ) != T_HashIndex::INVALID_INDEX ) { + return; + } + + dest.nodes.addNew( T_MDRNode_::T_Dup( ) , nsrc ); + + T_Array< RPC_ModIdentifier > const& deps( nsrc.fullDeps ); + const uint32_t nDeps( deps.size( ) ); + for ( uint32_t i = 0 ; i < nDeps ; i ++ ) { + copyNodes( source , source.findNode( *deps[ i ] ) , + dest , false ); + } + + if ( !copyReverseDeps ) { + return; + } + + const uint32_t nRevDeps( nsrc.revDeps.size( ) ); + for ( uint32_t i = 0 ; i < nRevDeps ; i ++ ) { + copyNodes( source , source.findNode( *nsrc.revDeps[ i ] ) , + dest , false ); + } +} + +T_Array< T_MDRGraph_ > T_MDResolver_::getUIGraphs( + T_MDRGraph_ const& graph ) noexcept +{ + const uint32_t nn( graph.nodes.size( ) ); + const uint32_t nui( ([&]() { + // How many UI mods? + uint32_t c = 0; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + if ( graph.nodes[ i ].mod->isUserInterface( ) ) { + c ++; + } + } + return c; + })( ) ); + logger.trace( ) << "About to generate " << nui << " per-UI graphs"; + T_Array< T_MDRGraph_ > output{ nui }; + for ( uint32_t i = 0 ; output.size( ) < nui ; i ++ ) { + assert( i < nn ); + auto const& node( graph.nodes[ i ] ); + if ( !node.mod->isUserInterface( ) ) { + continue; + } + + output.addNew( nn ); + auto& ng( output[ output.size( ) - 1 ] ); + copyNodes( graph , i , ng , true ); + + const uint32_t ngn( ng.nodes.size( ) ); + logger.trace( ) + << "Generated graph for UI mod " << *node.mod << " (" + << ngn << " nodes)"; + } + + return output; +} + +/*----------------------------------------------------------------------------*/ + +T_Array< RPC_ModInfo > T_MDResolver_::findCommonMods( + T_MDRGraph_ const& graph , + T_Array< T_MDRGraph_ > const& uiGraphs ) noexcept +{ + const uint32_t nn( graph.nodes.size( ) ); + const uint32_t nui( uiGraphs.size( ) ); + const uint32_t minMods( ([&](){ + uint32_t c = 0u; + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + c = std::min( uiGraphs[ i ].nodes.size( ) , c ); + } + return std::max( 1u , c ); + })( ) ); + + RPC_ModIdentifier common[ minMods ]; + uint32_t nc = 0; + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& mgNode( graph.nodes[ i ] ); + if ( mgNode.uiDeps.size( ) != 0 ) { + continue; + } + + auto const& id( mgNode.mod->identifier ); + bool ok( true ); + for ( uint32_t j = 0 ; j < nui && ok ; j ++ ) { + ok = uiGraphs[ j ].findNode( id ) != T_HashIndex::INVALID_INDEX; + } + if ( ok ) { + common[ nc++ ] = &id; + } + } + + T_ModsList_ cml{ std::max( 1u , nc ) }; + for ( uint32_t i = 0 ; i < nc ; i ++ ) { + cml.add( *mods.get( *common[ i ] ) ); + } + cml.sort( LoadOrderCmp_ ); + + if ( logger.hasLevel( E_LogLevel::TRACE ) ) { + if ( nc != 0 ) { + T_StringBuilder sb( "Common mods:" ); + for ( uint32_t i = 0 ; i < nc ; i ++ ) { + sb << ' ' << *cml[ i ]; + } + logger.trace( std::move( sb ) ); + } else { + logger.trace( ) << "No common mods"; + } + } + + return cml; +} + +T_UIMods_ T_MDResolver_::extractUIMods( + T_MDRGraph_ const& uiGraph , + T_ModsList_ const& commonMods ) noexcept +{ + const uint32_t nn( uiGraph.nodes.size( ) ); + RPC_ModIdentifier ui( nullptr ); + RPC_ModInfo uiMods[ nn ]; + uint32_t nm = 0; + + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + RPC_ModInfo nodeMod( uiGraph.nodes[ i ].mod ); + if ( commonMods.contains( nodeMod ) ) { + continue; + } + uiMods[ nm ++ ] = nodeMod; + if ( nodeMod->isUserInterface( ) ) { + assert( ui == nullptr ); + ui = &( nodeMod->identifier ); + } + } + assert( ui != nullptr ); + assert( nm > 0 ); + + T_ModsList_ uiml{ nm }; + for ( uint32_t i = 0 ; i < nm ; i ++ ) { + uiml.add( uiMods[ i ] ); + } + uiml.sort( LoadOrderCmp_ ); + + if ( logger.hasLevel( E_LogLevel::TRACE ) ) { + T_StringBuilder sb( "Mods for UI " ); + sb << ui->name << ':'; + for ( uint32_t i = 0 ; i < nm ; i ++ ) { + sb << ' ' << *uiml[ i ]; + } + logger.trace( std::move( sb ) ); + } + + return T_UIMods_{ ui->name , std::move( uiml ) }; +} + +T_Array< T_UIMods_ > T_MDResolver_::generateUILists( + T_Array< T_MDRGraph_ > const& uiGraphs , + T_ModsList_ const& commonMods ) noexcept +{ + const uint32_t nui( uiGraphs.size( ) ); + T_Array< T_UIMods_ > result{ nui }; + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + result.add( extractUIMods( uiGraphs[ i ] , commonMods ) ); + } + return result; +} + +} // namespace + + +/*= T_ModsDependencies =======================================================*/ + +T_ModsDependencies::T_ModsDependencies( ) noexcept + : A_PrivateImplementation( new T_MDData_( ) ) +{ } + +T_ModsDependencies::T_ModsDependencies( + T_Logger& logger , + T_Array< RPC_ModInfo > const& mods , + T_ModsManagerConfiguration const& config ) noexcept + : A_PrivateImplementation( new T_MDData_( ) ) +{ + const bool trace( logger.hasLevel( E_LogLevel::TRACE ) ); + auto& pi( p< T_MDData_ >( ) ); + pi.success = false; + + const T_Array< RPC_ModInfo > matches{ config.filterMods( mods ) }; + if ( matches.size( ) == 0 ) { + logger.warning( "No mods left after applying filters" ); + return; + } + if ( trace ) { + T_StringBuilder sb( "Mods matching configuration:" ); + auto const nMods( mods.size( ) ); + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + sb << ' ' << *mods[ i ]; + } + logger.trace( std::move( sb ) ); + } + + const T_Array< T_String > required( config.requiredMods( ) ); + if ( trace ) { + if ( required.size( ) != 0 ) { + T_StringBuilder sb( "Required mods:" ); + for ( uint32_t i = 0 ; i < required.size( ) ; i ++ ) { + sb << ' ' << required[ i ]; + } + logger.trace( std::move( sb ) ); + } else { + logger.trace( ) << "No mod requirements"; + } + } + + logger.debug( ) << "Resolving mod dependencies"; + T_MDResolver_ resolver{ logger , matches , required , pi }; + T_MDRGraph_ initialGraph{ resolver.buildInitialGraph( ) }; + if ( initialGraph.nodes.size( ) == 0 ) { + logger.warning( ) << "No mods left after invalid dependencies removal"; + return; + } + logger.trace( ) << "Full graph: " << initialGraph.nodes.size( ) << " valid nodes"; + + T_Array< uint32_t > subgraphs( resolver.findSubgraphs( initialGraph ) ); + uint32_t i = 0; + while ( i < subgraphs.size( ) ) { + if ( resolver.checkSubgraph( initialGraph , subgraphs[ i ] ) ) { + i ++; + } else { + resolver.removeSubgraphNodes( initialGraph , subgraphs[ i ] ); + subgraphs.removeSwap( i ); + } + } + if ( subgraphs.size( ) == 0 ) { + logger.warning( ) << "No set of mods satisfies the configuration"; + return; + } + + auto graph( resolver.getGraph( initialGraph , subgraphs ) ); + if ( !graph.present( ) ) { + return; + } + auto uiGraphs( resolver.getUIGraphs( graph ) ); + pi.commonMods = resolver.findCommonMods( graph , uiGraphs ); + pi.uiMods = resolver.generateUILists( uiGraphs , pi.commonMods ); + pi.uiMods.sort( [&]( T_UIMods_ const& a , T_UIMods_ const& b ) -> int { + const auto pa( config.uiPreference( a.name ) ); + const auto pb( config.uiPreference( b.name ) ); + if ( pa == pb ) { + return a.name.compare( b.name ); + } + return -T_Comparator< int32_t >::compare( pa , pb ); + } ); + if ( trace ) { + T_StringBuilder sb( "User interfaces:" ); + for ( uint32_t i = 0 ; i < pi.uiMods.size( ) ; i ++ ) { + sb << ' ' << pi.uiMods[ i ].name; + } + logger.trace( std::move( sb ) ); + } + pi.success = true; +} + +// --------------------------------------------------------------------- + +T_ModsDependencies::T_ModsDependencies( + T_ModsDependencies const& other ) noexcept + : A_PrivateImplementation( new T_MDData_( other.p< T_MDData_ >( ) ) ) +{ } + +T_ModsDependencies& T_ModsDependencies::operator =( + T_ModsDependencies const& other ) noexcept +{ + p< T_MDData_ >( ) = other.p< T_MDData_ >( ); + return *this; +} + +T_ModsDependencies::T_ModsDependencies( + T_ModsDependencies&& other ) noexcept + : A_PrivateImplementation( std::move( other ) ) +{ } + +T_ModsDependencies& T_ModsDependencies::operator =( + T_ModsDependencies&& other ) noexcept +{ + A_PrivateImplementation::operator =( std::move( other ) ); + return *this; +} + +M_DECLARE_SWAP( T_ModsDependencies ) +{ + T_ModsDependencies temp( std::move( rhs ) ); + rhs = std::move( lhs ); + lhs = std::move( temp ); +} + +// --------------------------------------------------------------------- + +bool T_ModsDependencies::resolved( ) const +{ + return p< T_MDData_ >( ).success; +} + +bool T_ModsDependencies::ambiguous( ) const +{ + return p< T_MDData_ >( ).ambiguous; +} + +T_Array< T_String > T_ModsDependencies::userInterfaces( ) const +{ + auto const& pi( p< T_MDData_ >( ) ); + const auto nui( pi.uiMods.size( ) ); + T_Array< T_String > result{ nui }; + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + result.add( pi.uiMods[ i ].name ); + } + return result; +} + +T_Array< RPC_ModInfo > const& T_ModsDependencies::commonMods( ) const +{ + return p< T_MDData_ >( ).commonMods; +} + +T_Array< RPC_ModInfo >::RPC T_ModsDependencies::forUserInterface( + T_String const& name ) const +{ + auto const& pi( p< T_MDData_ >( ) ); + const auto nui( pi.uiMods.size( ) ); + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + auto const& uim( pi.uiMods[ i ] ); + if ( uim.name == name ) { + return &uim.mods; + } + } + return nullptr; +} + + +/*= DATA MOD DESCRIPTION FILE ================================================*/ + +namespace { +struct T_ModInfoFromDescr_ +{ + T_ModInfo modInfo; + bool hasVersion; + bool hasRevision; + bool hasLibVersion; +}; +using SP_MIFD = T_SharedPtr< T_ModInfoFromDescr_ >; + +bool DMDFInitialise_( T_SRDParserData const& d ) +{ + auto desc( NewShared< T_ModInfoFromDescr_ >( ) ); + desc->hasVersion = desc->hasRevision = desc->hasLibVersion = false; + desc->modInfo.type = E_ModType::DATA; + *( d.currentData ) = desc; + return true; +} + +bool DMDFSetName_( T_SRDParserData const& d ) +{ + auto const& tok( (*d.input)[ 1 ] ); + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + if ( mi->modInfo.identifier.name ) { + d.errors.add( "duplicate module name" , tok ); + } else { + mi->modInfo.identifier.name = tok.stringValue( ); + } + return true; +} + +bool DMDFSetVersion_( T_SRDParserData const& d ) +{ + auto const& tok( (*d.input)[ 1 ] ); + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + if ( mi->hasVersion ) { + d.errors.add( "duplicate version number" , tok ); + } + + const auto v( tok.longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + d.errors.add( "invalid version number" , tok ); + } else { + mi->modInfo.identifier.version = tok.longValue( ); + } + mi->hasVersion = true; + return true; +} + +bool DMDFSetRevision_( T_SRDParserData const& d ) +{ + auto const& tok( (*d.input)[ 1 ] ); + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + if ( mi->hasRevision ) { + d.errors.add( "duplicate revision number" , tok ); + } + + const auto v( tok.longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + d.errors.add( "invalid revision number" , tok ); + } else { + mi->modInfo.revision = tok.longValue( ); + } + mi->hasRevision = true; + return true; +} + +bool DMDFSetLibVersion_( T_SRDParserData const& d ) +{ + auto const& tok( (*d.input)[ 1 ] ); + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + if ( mi->hasLibVersion ) { + d.errors.add( "duplicate library version number" , tok ); + } + + const auto v( tok.longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + d.errors.add( "invalid library version number" , tok ); + } else { + mi->modInfo.libVersion = tok.longValue( ); + } + mi->hasLibVersion = true; + return true; +} + +bool DMDFAddDependency_( T_SRDParserData const& d ) +{ + auto const& input( *d.input ); + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + + bool add( true ); + auto const& name( input[ 1 ].stringValue( ) ); + auto& deps( mi->modInfo.modDeps ); + const auto nDeps( deps.size( ) ); + for ( uint32_t i = 0 ; i < nDeps ; i ++ ) { + if ( deps[ i ].name == name ) { + d.errors.add( "duplicate dependency" , input[ 1 ] ); + add = false; + break; + } + } + + const auto v( input[ 2 ].longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + d.errors.add( "invalid version number" , input[ 2 ] ); + add = false; + } + + if ( add ) { + deps.add( T_ModIdentifier{ name , uint32_t( v ) } ); + } + return true; +} + +bool DMDFCheck_( T_SRDParserData const& d ) +{ + auto const& mi( d.currentData->value< SP_MIFD >( ) ); + if ( !mi->hasLibVersion ) { + d.errors.add( "no required library version" , T_SRDLocation( ) ); + } + if ( !mi->modInfo.identifier.name ) { + d.errors.add( "no name" , T_SRDLocation( ) ); + } + if ( !mi->hasVersion ) { + d.errors.add( "no version number" , T_SRDLocation( ) ); + } + if ( !mi->hasRevision ) { + d.errors.add( "no revision number" , T_SRDLocation( ) ); + } + *( d.currentData ) = NewShared< T_ModInfo >( std::move( mi->modInfo ) ); + return true; +} + +T_SRDParserConfig DMDFConfig_( ) +{ + using namespace lw::SRD; + + T_SRDParserDefs defs( "default" ); + defs << OnStart( DMDFInitialise_ ) << OnFinish( DMDFCheck_ ); + defs.context( "default" ) + << ( Rule( ) << "name" << Word( ) << DMDFSetName_ ) + << ( Rule( ) << "version" << Integer( ) << DMDFSetVersion_ ) + << ( Rule( ) << "revision" << Integer( ) << DMDFSetRevision_ ) + << ( Rule( ) << "library-version" << Integer( ) << DMDFSetLibVersion_ ) + << ( Rule( ) << "depends-on" << Word( ) << Integer( ) << DMDFAddDependency_ ) + ; + return T_SRDParserConfig( defs ); +} +} // namespace + + +/*= NATIVE MOD LOADING =======================================================*/ + +namespace { +class T_LoadedNativeMod_ : public A_NativeMod +{ + private: + T_Logger logger_; + T_String path_; + T_DynLib lib_; + OP_NativeMod mod_; + bool initialised_; + + public: + explicit T_LoadedNativeMod_( + T_Logger& logger , + T_String const& path ) noexcept; + ~T_LoadedNativeMod_( ) noexcept override; + + bool preinit( ) noexcept override; + bool init( F_UpdateInitProgress const& uip ) noexcept override; + void shutdown( ) noexcept override; + void postshutdown( ) noexcept override; + OP_UserInterface getUserInterface( ) const noexcept override; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_LoadedNativeMod_::T_LoadedNativeMod_( + T_Logger& logger , + T_String const& path ) noexcept + : logger_( logger ) , path_( path ) , + lib_{ path } , mod_{} +{} + +T_LoadedNativeMod_::~T_LoadedNativeMod_( ) noexcept +{ + shutdown( ); + postshutdown( ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_LoadedNativeMod_::preinit( ) noexcept +{ + assert( !mod_ ); + if ( !lib_.load( ) ) { + logger_.error( ) << "Native mod " << path_ + << " could not be loaded"; + return false; + } + + auto loader{ lib_.getFunction< void*( ) >( "LWMod_GetMod" ) }; + if ( !loader ) { + lib_.unload( ); + logger_.error( ) << "Cannot find mod entry point in " << path_; + return false; + } + + auto mod{ reinterpret_cast< RP_NativeMod >( loader( ) ) }; + if ( !mod ) { + lib_.unload( ); + logger_.error( ) << "Mod entry point returned NULL in " << path_; + return false; + } + + if ( !mod->preinit( ) ) { + delete mod; + lib_.unload( ); + return false; + } + + mod_ = OwnRawPointer( mod ); + initialised_ = false; + return true; +} + +/*----------------------------------------------------------------------------*/ + +bool T_LoadedNativeMod_::init( + F_UpdateInitProgress const& uip ) noexcept +{ + assert( mod_ && !initialised_ ); + initialised_ = mod_->init( uip ); + return initialised_; +} + +void T_LoadedNativeMod_::shutdown( ) noexcept +{ + if ( mod_ && initialised_ ) { + mod_->shutdown( ); + initialised_ = false; + } +} + +void T_LoadedNativeMod_::postshutdown( ) noexcept +{ + if ( mod_ && !initialised_ ) { + mod_->postshutdown( ); + mod_.clear( ); + lib_.unload( ); + } +} + +OP_UserInterface T_LoadedNativeMod_::getUserInterface( ) const noexcept +{ + assert( mod_ && !initialised_ ); + return mod_->getUserInterface( ); +} + + +/*= DATA MOD LOADING =========================================================*/ + +namespace { +class T_DataMod_ : public A_NativeMod +{ + private: + T_Logger logger_; + T_String path_; + T_RegisteredItem drv_; + + public: + explicit T_DataMod_( + T_Logger& logger , + T_String const& path ) noexcept; + ~T_DataMod_( ) noexcept override; + + bool preinit( ) noexcept override; + bool init( F_UpdateInitProgress const& ) noexcept override; + void shutdown( ) noexcept override; + void postshutdown( ) noexcept override; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_DataMod_::T_DataMod_( + T_Logger& logger , + T_String const& path ) noexcept + : logger_( logger ) , path_( path ) , drv_{} +{ } + +T_DataMod_::~T_DataMod_( ) noexcept +{ + shutdown( ); + postshutdown( ); +} + +bool T_DataMod_::preinit( ) noexcept +{ + assert( !drv_ ); + drv_ = LW::vfs( ).addDriver( NewOwned< T_VFSDataFileDriver >( + NewOwned< T_File >( path_ , E_FileMode::READ_ONLY ) ) ); + if ( !drv_ ) { + logger_.error( ) << "Failed to initialise VFS data file driver from " + << T_String( path_ ); + return false; + } + return true; +} + +bool T_DataMod_::init( + F_UpdateInitProgress const& ) noexcept +{ + assert( drv_ ); + return true; +} + +void T_DataMod_::shutdown( ) noexcept +{ } + +void T_DataMod_::postshutdown( ) noexcept +{ + if ( drv_ ) { + drv_ = T_RegisteredItem( ); + } +} + + +/*= MODS MANAGER DATA ========================================================*/ + +namespace { + +struct T_MMData_ +{ + using T_ModIDs = T_Array< T_ModIdentifier >; + + const T_SRDParserConfig dmdfConfig_{ DMDFConfig_( ) }; + + T_ModsManagerConfiguration config_; + T_Logger logger{ "/core/mods" }; + + T_Array< OP_ModInfo > modsList{ 128 }; + T_ModsDependencies deps; + + T_Array< OP_NativeMod > mods; + T_Array< bool > states; + T_Array< RPC_ModInfo > loadedMods; + + // --------------------------------------------------------------------- + + T_MMData_( T_ModsManagerConfiguration&& config ) noexcept; + + // --------------------------------------------------------------------- + + /* Find all available mods. */ + bool scanForMods( ); + + /* Try to extract mod information from a file */ + OP_ModInfo getModInfo( + T_VFSPath const& path ) noexcept; + /* Try to extract mod information from a data file mod */ + OP_ModInfo getModInfoData( + T_VFSPath const& path , + OP_File& modFile ) noexcept; + /* Try to extract mod information from a native mod */ + OP_ModInfo getModInfoNative( + T_VFSPath const& path , + T_String const& fsPath ) noexcept; + + // --------------------------------------------------------------------- + + /* Set up the arrays for the mods' instances */ + void setupMods( ) noexcept; + + /* Load and pre-initialise a mod */ + bool loadMod( RPC_ModInfo mi ) noexcept; + /* "Rewind" the mod loader until only the specified amount of mods + * are left. This is meant for mods that have only been preinitialised. + */ + void rewindMods( uint32_t target ) noexcept; + + /* Load and preinitialise the UI mods, and try initialising the UI + * itself. + */ + OP_UserInterface loadUserInterface( + T_ModsList_ const& ml ) noexcept; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_MMData_::T_MMData_( + T_ModsManagerConfiguration&& config ) noexcept + : config_( std::move( config ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline bool T_MMData_::scanForMods( ) +{ + const T_Array< T_VFSPath > paths( ([this]() { + T_Array< T_VFSPath > output; + LW::vfs( ).list( "/mods" , output ); + output.sort( []( T_VFSPath const& a , T_VFSPath const& b ) { + return T_String( a ).compare( T_String( b ) ); + } ); + return output; + })( ) ); + + T_Array< T_String > remove( 8 ); + const uint32_t nPaths( paths.size( ) ); + modsList.clear( ); + for ( uint32_t i = 0 ; i < nPaths ; i ++ ) { + const T_VFSPath path( "/mods" , paths[ i ] ); + logger.trace( ) << "Checking " << T_String( path ); + + if ( LW::vfs( ).typeOf( path ) != E_VFSEntryType::FILE ) { + logger.trace( ) << T_String( path ) << " is not a file"; + continue; + } + + OP_ModInfo info( getModInfo( path ) ); + if ( !info ) { + continue; + } + if ( remove.contains( info->identifier.name ) ) { + continue; + } + + const auto nMods( modsList.size( ) ); + bool badMod( false ); + for ( uint32_t j = 0 ; j < nMods ; j ++ ) { + auto const& mod( *modsList[ j ] ); + badMod = mod.identifier == info->identifier + && mod.revision == info->revision; + if ( badMod ) { + logger.notice( ) << T_String( path ) << " and " + << T_String( mod.path ) + << " contain the same mod (" + << *info << ')'; + break; + } + + badMod = mod.identifier.name == info->identifier.name + && mod.isUserInterface( ) != info->isUserInterface( ); + if ( badMod ) { + remove.add( mod.identifier.name ); + auto const& uiMod( mod.isUserInterface( ) ? mod : *info ); + auto const& otherMod( mod.isUserInterface( ) ? *info : mod ); + logger.notice( ) << "Mod " << uiMod + << " is an UI mod, " << otherMod + << " isn't; " << uiMod.identifier.name + << " will be discarded."; + break; + } + } + if ( !badMod ) { + logger.info( ) << "Found mod " << *info + << " in " << T_String( path ); + modsList.add( std::move( info ) ); + } + } + + // Remove "bad" mods + auto const nRemove( remove.size( ) ); + for ( uint32_t i = 0 ; i < nRemove ; i ++ ) { + auto const& name( remove[ i ] ); + for ( uint32_t j = 0 ; j < modsList.size( ) ; j ++ ) { + if ( modsList[ i ]->identifier.name == name ) { + modsList.removeSwap( j ); + j --; + } + } + } + + return modsList.size( ) > 0; +} + +/*----------------------------------------------------------------------------*/ + +OP_ModInfo T_MMData_::getModInfo( + T_VFSPath const& path ) noexcept +{ + OP_File modFile( LW::vfs( ).file( path ) ); + if ( !modFile ) { + logger.error( ) << "couldn't access " << T_String( path ); + return OP_ModInfo( ); + } + const T_String fsPath( modFile->path( ) ); + + OP_ModInfo result( getModInfoData( path , modFile ) ); + if ( !result ) { + modFile.clear( ); + result = getModInfoNative( path , fsPath ); + } + if ( result ) { + result->path = fsPath; + } + return result; +} + +OP_ModInfo T_MMData_::getModInfoData( + T_VFSPath const& path , + OP_File& modFile ) noexcept +{ + const T_String p( path ); + auto drv( NewOwned< T_VFSDataFileDriver >( std::move( modFile ) ) ); + if ( !drv->init( ) ) { + logger.trace( ) << p << " is not a data file"; + return OP_ModInfo( ); + } + + OP_ModInfo modInfo; + OP_InputStream stream( drv->read( "/mods/modinfo.srd" ) ); + if ( stream ) { + T_SRDParser parser( dmdfConfig_ ); + T_SRDTextReader reader( parser ); + try { + reader.read( T_String( "modinfo.srd" ) , *stream ); + modInfo = NewOwned< T_ModInfo >( std::move( + *parser.getData< SP_ModInfo >( ) ) ); + } catch ( X_StreamError const& e ) { + logger.error( ) << "Error while reading modinfo.srd from " + << p << ": " << e.what( ); + } catch ( X_SRDErrors const& e ) { + logger.error( ) << "Couldn't read mod description from " << p; + e.log( logger ); + } + } else { + logger.error( ) << "No module description in " << p; + } + drv->shutdown( ); + + if ( modInfo ) { + if ( modInfo->libVersion != LibVersion( ) ) { + logger.info( ) << p << " is incompatible with this version"; + modInfo.clear( ); + } else { + logger.trace( ) << p << " is a data mod"; + } + } + return modInfo; +} + +OP_ModInfo T_MMData_::getModInfoNative( + T_VFSPath const& path , + T_String const& fsPath ) noexcept +{ + const T_String p( path ); + + T_DynLib lib( fsPath ); + if ( !lib.load( ) ) { + logger.trace( ) << p << " is not a library; loader error: " + << lib.lastError( ); + return OP_ModInfo( ); + } + + uint32_t const* const pLibVersion( lib.getPointer< uint32_t >( + "LWMod_RequiredLibVersion" ) ); + if ( pLibVersion == nullptr ) { + logger.trace( ) << p << " is not a LW mod"; + return OP_ModInfo( ); + } + if ( *pLibVersion != LibVersion( ) ) { + logger.info( ) << p << " is incompatible with this version"; + return OP_ModInfo( ); + } + + char const* const pModName( lib.getPointer< char >( "LWMod_Name" ) ); + auto const* const pModVersion( lib.getPointer< uint32_t >( "LWMod_Version" ) ); + auto const* const pModRevision( lib.getPointer< uint32_t >( "LWMod_Revision" ) ); + bool const* const pIsUI( lib.getPointer< bool >( "LWMod_IsUserInterface" ) ); + if ( pModName == nullptr || pModVersion == nullptr + || pModRevision == nullptr || pIsUI == nullptr ) { + logger.error( ) << p << " is missing some mod information"; + return OP_ModInfo( ); + } + if ( lib.getRawSymbol( "LWMod_GetMod" ) == nullptr ) { + logger.error( ) << p << " does not have an entry point"; + return OP_ModInfo( ); + } + + auto mi( NewOwned< T_ModInfo >( ) ); + mi->type = *pIsUI ? E_ModType::UI : E_ModType::NATIVE; + mi->identifier.name = T_String( pModName ); + mi->identifier.version = *pModVersion; + mi->revision = *pModRevision; + mi->libVersion = *pLibVersion; + + auto const* const pDepCount( lib.getPointer< uint32_t >( "LWMod_DepCount" ) ); + if ( pDepCount && *pDepCount ) { + logger.trace( ) << p << " contains " << *pDepCount << " dependency record(s)"; + char const* const* const pDepNames( + lib.getPointer< char const* const >( "LWMod_DepNames" ) ); + auto const* const pDepVersions( + lib.getPointer< uint32_t >( "LWMod_DepVersions" ) ); + if ( pDepNames == nullptr || pDepVersions == nullptr ) { + logger.notice( ) << p << " has missing dependency fields"; + return OP_ModInfo( ); + } + for ( uint32_t i = 0 ; i < *pDepCount ; i ++ ) { + T_String dName( pDepNames[ i ] ); + for ( uint32_t j = 0 ; j < i ; j ++ ) { + if ( mi->modDeps[ j ].name == dName ) { + logger.notice( ) << p + << " contains duplicate dependency on " + << dName; + return {}; + } + } + mi->modDeps.add( T_ModIdentifier{ + std::move( dName ) , + pDepVersions[ i ] } ); + } + } + logger.trace( ) << p << " is a valid " + << ( mi->type == E_ModType::NATIVE ? "native" : "user interface" ) + << " mod"; + return mi; +} + +/*----------------------------------------------------------------------------*/ + +void T_MMData_::setupMods( ) noexcept +{ + assert( mods.size( ) == 0 ); + + const uint32_t nCommon( deps.commonMods( ).size( ) ); + const auto uiSets( deps.userInterfaces( ) ); + const auto nui( uiSets.size( ) ); + uint32_t nUIMods = 0; + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + auto mptr( deps.forUserInterface( uiSets[ i ] ) ); + assert( mptr != nullptr ); + nUIMods = std::max( nUIMods , mptr->size( ) ); + } + + const uint32_t nMods( nCommon + nUIMods ); + assert( nMods > 0 ); + mods = T_Array< OP_NativeMod >{ nMods }; + states = T_Array< bool >{ nMods }; + loadedMods = T_Array< RPC_ModInfo >{ nMods }; +} + +bool T_MMData_::loadMod( + const RPC_ModInfo mi ) noexcept +{ + logger.trace( ) << "Loading mod " << *mi; + OP_NativeMod mod( ([&]() -> OP_NativeMod { + if ( mi->type == E_ModType::DATA ) { + return NewOwned< T_DataMod_ >( + logger , mi->path ); + } + return NewOwned< T_LoadedNativeMod_ >( + logger , mi->path ); + })( ) ); + + if ( mod->preinit( ) ) { + mods.add( std::move( mod ) ); + states.add( false ); + loadedMods.add( mi ); + logger.info( ) << "Loaded mod " << *mi; + return true; + } else { + return false; + } +} + +void T_MMData_::rewindMods( + const uint32_t target ) noexcept +{ + assert( target <= mods.size( ) ); + if ( target == mods.size( ) ) { + return; + } + + if ( logger.hasLevel( E_LogLevel::DEBUG ) ) { + T_StringBuilder sb( "Unloading the previous " ); + const uint32_t nUnload( mods.size( ) - target ); + if ( nUnload > 1 ) { + sb << nUnload << " mods"; + } else { + sb << "mod"; + } + logger.debug( std::move( sb ) ); + } + + uint32_t left; + while ( ( left = mods.size( ) ) != target ) { + const uint32_t index( left - 1 ); + auto& mod( mods[ index ] ); + assert( !states[ index ] ); + mod->postshutdown( ); + mods.remove( index ); + states.remove( index ); + loadedMods.remove( index ); + } +} + +/*----------------------------------------------------------------------------*/ + +OP_UserInterface T_MMData_::loadUserInterface( + T_ModsList_ const& ml ) noexcept +{ + const uint32_t rewindTarget( mods.size( ) ); + const uint32_t nMods( mods.size( ) ); + + T_Optional< uint32_t > uiModIndex{ }; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + if ( loadMod( ml[ i ] ) ) { + if ( ml[ i ]->isUserInterface( ) ) { + assert( !uiModIndex.present( ) ); + uiModIndex = mods.size( ) - 1; + } + continue; + } + rewindMods( rewindTarget ); + return {}; + } + assert( uiModIndex.present( ) ); + + OP_UserInterface uiInst( mods[ uiModIndex ]->getUserInterface( ) ); + if ( uiInst && uiInst->preinit( ) ) { + return std::move( uiInst ); + } + uiInst.clear( ); + rewindMods( rewindTarget ); + return {}; +} + + +/*= T_ModsManager ============================================================*/ + +T_ModsManager::T_ModsManager( + T_ModsManagerConfiguration&& config ) noexcept + : A_PrivateImplementation( new T_MMData_( std::move( config ) ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_ModsManager::scanForMods( ) +{ + return p< T_MMData_ >( ).scanForMods( ); +} + +T_Array< RPC_ModInfo > T_ModsManager::availableMods( ) const noexcept +{ + auto const& ml( p< T_MMData_ >( ).modsList ); + const auto nm( ml.size( ) ); + T_Array< RPC_ModInfo > result{ std::max( 1u , nm ) }; + for ( uint32_t i = 0 ; i < nm ; i ++ ) { + result.add( ml[ i ].get( ) ); + } + return result; +} + +bool T_ModsManager::resolveDependencies( ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + pi.deps = T_ModsDependencies{ pi.logger , availableMods( ) , pi.config_ }; + return pi.deps.resolved( ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_ModsManager::preinitCommon( ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + pi.setupMods( ); + + T_ModsList_ const& common( pi.deps.commonMods( ) ); + const uint32_t ncm( common.size( ) ); + for ( uint32_t i = 0 ; i < ncm ; i ++ ) { + if ( !pi.loadMod( common[ i ] ) ) { + pi.rewindMods( 0 ); + return false; + } + } + return true; +} + +OP_UserInterface T_ModsManager::preinitUIMods( + T_String const& ui ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + auto mods( pi.deps.forUserInterface( ui ) ); + if ( mods == nullptr ) { + pi.logger.error( ) << "No mods for UI '" << ui << '\''; + return {}; + } + return pi.loadUserInterface( *mods ); +} + +OP_UserInterface T_ModsManager::preinitUIMods( ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + auto uis( pi.deps.userInterfaces( ) ); + const auto nui( uis.size( ) ); + for ( uint32_t i = 0 ; i < nui ; i ++ ) { + pi.logger.debug( ) + << "Attempting to load & initialise UI " + << uis[ i ]; + + auto mods( pi.deps.forUserInterface( uis[ i ] ) ); + assert( mods != nullptr ); + + OP_UserInterface ui( pi.loadUserInterface( *mods ) ); + if ( ui ) { + return ui; + } + pi.logger.notice( ) + << "Could not initialise UI " << uis[ i ]; + } + pi.logger.error( ) << "All attempts at initialising an UI failed"; + return {}; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t T_ModsManager::modsCount( ) const noexcept +{ + return p< T_MMData_ >( ).loadedMods.size( ); +} + +bool T_ModsManager::initialise( + F_CreateInitUpdater const& ciuf ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + + const auto nMods( pi.loadedMods.size( ) ); + pi.logger.trace( ) << "Initialising mods"; + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + auto& mod( pi.mods[ i ] ); + auto desc( pi.loadedMods[ i ] ); + auto& state( pi.states[ i ] ); + if ( state ) { + pi.logger.error( ) << "Mod " << *desc + << " is already initialised"; + shutdown( ); + return false; + } + pi.logger.trace( ) << "Initialising mod " << *desc; + if ( !mod->init( ciuf( desc ) ) ) { + pi.logger.error( ) << "Mod " << *desc + << " failed to initialise"; + shutdown( ); + return false; + } + state = true; + } + return true; +} + +/*----------------------------------------------------------------------------*/ + +void T_ModsManager::shutdown( ) noexcept +{ + auto& pi( p< T_MMData_ >( ) ); + + pi.logger.trace( ) << "Mods shutdown"; + const uint32_t nMods( pi.mods.size( ) ); + for ( uint32_t i = 0 ; i < nMods ; i ++ ) { + const uint32_t index( nMods - ( i + 1 ) ); + auto& mod( pi.mods[ index ] ); + auto& state( pi.states[ index ] ); + if ( state ) { + pi.logger.trace( ) << "Shutting down mod " + << *( pi.loadedMods[ index ] ); + mod->shutdown( ); + state = false; + } else { + pi.logger.debug( ) << "Mod " + << *( pi.loadedMods[ index ] ) + << " is not initialised, skipping shutdown"; + } + } +} + +void T_ModsManager::unload( ) noexcept +{ + p< T_MMData_ >( ).rewindMods( 0 ); +} diff --git a/src/Pointers.cc b/src/Pointers.cc new file mode 100644 index 0000000..e58b73d --- /dev/null +++ b/src/Pointers.cc @@ -0,0 +1,103 @@ +/******************************************************************************/ +/* POINTERS *******************************************************************/ +/******************************************************************************/ + +#include +#include +#include +using namespace lw; + + +/*= T_WeakChain_ =============================================================*/ + +T_WeakChain_::T_WeakChain_( T_Reference_*& ref ) + : ref( &ref ) , prev( nullptr ) , next( nullptr ) +{ } + +void T_WeakChain_::unchain( ) +{ + if ( prev != nullptr ) { + prev->next = next; + prev = nullptr; + } else if ( (*ref) != nullptr ) { + assert( this == (*ref)->weaks_ ); + (*ref)->weaks_ = next; + } + if ( next != nullptr ) { + next->prev = prev; + next = nullptr; + } +} + +void T_WeakChain_::init( ) +{ + if ( (*ref) == nullptr ) { + return; + } + next = (*ref)->weaks_; + if ( next != nullptr ) { + next->prev = this; + } + (*ref)->weaks_ = this; +} + + +/*= T_Reference_ =============================================================*/ + +namespace { + +static T_ThreadedPoolAllocator< + sizeof( T_Reference_ ) , alignof( T_Reference_ ) , + LW_CFG_SHAREDPTR_REFPOOL_SIZE , + LW_CFG_SHAREDPTR_REFPOOL_MAXFREE + > ReferenceAllocator_; + +} + +void* T_Reference_::operator new( + const size_t count ) noexcept +{ + return ReferenceAllocator_.allocate( count ); +} + +void T_Reference_::operator delete( + void* const object ) noexcept +{ + return ReferenceAllocator_.free( object ); +} + +/*----------------------------------------------------------------------------*/ + +T_Reference_::T_Reference_( + void* const ptr , + F_Destr_ destructor ) + : pointer_( ptr ) , destr_( std::move( destructor ) ) , + count_( 1 ) , weaks_( nullptr ) +{ + assert( pointer_ != nullptr ); +} + +T_Reference_::~T_Reference_( ) +{ + assert( count_ == 0 ); + T_WeakChain_* wr = weaks_; + while ( wr != nullptr ) { + T_WeakChain_* const next = wr->next; + wr->unchain( ); + *( wr->ref ) = nullptr; + wr = next; + } + destr_( pointer_ ); +} + +void* T_Reference_::extract( ) +{ + if ( count_ > 1 ) { + throw X_TooManyReferences( ); + } + void* p( pointer_ ); + count_ = 0; + pointer_ = nullptr; + delete this; + return p; +} diff --git a/src/SRDBinary.cc b/src/SRDBinary.cc new file mode 100644 index 0000000..26d723c --- /dev/null +++ b/src/SRDBinary.cc @@ -0,0 +1,567 @@ +/******************************************************************************/ +/* SRD PARSER AND PREPROCESSOR - BINARY FORM **********************************/ +/******************************************************************************/ + +#include +using namespace lw; + + +/*= MAGIC NUMBERS, VERSIONS AND TAGS =========================================*/ + +namespace { + +// Magic number for binary SRD +static constexpr uint32_t C_MAGIC_ = 0xea7ca1ce; + +// E_Versions_ - Binary SRD version numbers +enum class E_Version_ : uint32_t { + V1 = 0xc0ffee00 , +}; + +// E_V1Tag_ - Version 1 SRD tags +enum class E_V1Tag_ : uint8_t { + END = 0x00 , + LIST = 0x01 , + WORD_NEW = 0x02 , + WORD_KNOWN = 0x03 , + VAR_WORD = 0x04 , + STRING = 0x05 , + INT = 0x06 , + LONG = 0x07 , + FLOAT = 0x08 , + COMMENT = 0x09 , + BINARY = 0x0a , +}; + +} // namespace + + +namespace lw { + +// Writer for version numbers +template< > +struct T_ObjectWriter< E_Version_ > +{ + static void write( T_BinaryWriter const& writer , E_Version_ const& v ) + { + writer.write( ( uint32_t ) v ); + } +}; + +// Writer for version 1 tags +template< > +struct T_ObjectWriter< E_V1Tag_ > +{ + static void write( T_BinaryWriter const& writer , E_V1Tag_ const& v ) + { + writer.write( ( uint8_t ) v ); + } +}; + +// Reader for version 1 tags +template< > +struct T_ObjectReader< E_V1Tag_ > +{ + static E_V1Tag_ read( T_BinaryReader const& reader ) + { + return ( E_V1Tag_ ) reader.read< uint8_t >( ); + } +}; + +} // namespace + + +/*= VERSION 1 BINARY READER ==================================================*/ + +namespace { + +class T_SRDBRVersion1_ +{ + private: + T_BinaryReader& reader_; + T_String const& name_; + T_SRDErrors& errors_; + A_SRDReaderTarget& target_; + + T_HashIndex wordsIndex_; + T_Array< T_String > words_; + uint32_t lastTagPos_; + + typedef std::function< T_SRDToken( T_String const& ) > F_StringTok; + + size_t curPos( ) const + { + return reader_.stream( ).position( ); + } + + void push( T_SRDToken&& token ) + { + token.location( name_ , lastTagPos_ ); + target_.push( errors_ , std::move( token ) ); + } + + void readNewWord( F_StringTok mt ); + void readKnownWord( F_StringTok mt ); + void readString( F_StringTok mt ); + + void readLoop( ); + + public: + T_SRDBRVersion1_( T_BinaryReader& reader , T_String const& name , T_SRDErrors& errors , + A_SRDReaderTarget& target ) + : reader_( reader ) , name_( name ) , errors_( errors ) , + target_( target ) + { } + + void read( ); +}; + +/*----------------------------------------------------------------------------*/ + +void T_SRDBRVersion1_::readNewWord( F_StringTok mt ) +{ + const auto wordPos( curPos( ) ); + const T_String word( reader_.read< T_String >( ).usePool( ) ); + if ( word.size( ) == 0 ) { + errors_.add( "empty word" , name_ , uint32_t( wordPos ) ); + return; + } + + const bool valid = T_SRDToken::IsWord( word ); + if ( !valid ) { + errors_.add( "invalid word" , name_ , wordPos ); + } + + const auto hash( ComputeHash( word ) ); + uint32_t index( wordsIndex_.first( hash ) ); + while ( index != T_HashIndex::INVALID_INDEX ) { + if ( words_[ index ] == word ) { + errors_.add( "duplicate word" , name_ , wordPos ); + return; + } + index = wordsIndex_.next( index ); + } + + wordsIndex_.add( hash ); + words_.add( word ); + if ( valid ) { + push( mt( word ) ); + } +} + +void T_SRDBRVersion1_::readKnownWord( F_StringTok mt ) +{ + const auto pos( curPos( ) ); + const auto widx( reader_.read< uint32_t >( ) ); + + if ( widx >= words_.size( ) ) { + errors_.add( "unregistered word" , name_ , pos ); + return; + } + + T_String const& word( words_[ widx ] ); + if ( T_SRDToken::IsWord( word ) ) { + push( mt( word ) ); + } +} + +void T_SRDBRVersion1_::readString( F_StringTok mt ) +{ + const auto str( reader_.read< T_String >( ).usePool( ) ); + push( mt( str ) ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDBRVersion1_::readLoop( ) +{ + bool pendingVariable = false; + uint32_t depth = 1; + while ( depth > 0 ) { + lastTagPos_ = reader_.stream( ).position( ); + const auto tag( reader_.read< E_V1Tag_ >( ) ); + + if ( pendingVariable && tag != E_V1Tag_::WORD_NEW && tag != E_V1Tag_::WORD_KNOWN ) { + pendingVariable = false; + errors_.add( "spurious variable tag" , name_ , lastTagPos_ - 1 ); + } + + switch ( tag ) { + + case E_V1Tag_::LIST: + depth ++; + push( T_SRDToken::ListStart( ) ); + break; + + case E_V1Tag_::END: + depth --; + if ( depth > 0 ) { + push( T_SRDToken::ListEnd( ) ); + } + break; + + case E_V1Tag_::WORD_NEW: + if ( pendingVariable ) { + lastTagPos_ --; + readNewWord( T_SRDToken::Variable ); + pendingVariable = false; + } else { + readNewWord( T_SRDToken::Word ); + } + break; + + case E_V1Tag_::WORD_KNOWN: + if ( pendingVariable ) { + lastTagPos_ --; + readKnownWord( T_SRDToken::Variable ); + pendingVariable = false; + } else { + readKnownWord( T_SRDToken::Word ); + } + break; + + case E_V1Tag_::VAR_WORD: + pendingVariable = true; + break; + + case E_V1Tag_::STRING: + readString( T_SRDToken::String ); + break; + + case E_V1Tag_::COMMENT: + readString( T_SRDToken::Comment ); + break; + + case E_V1Tag_::INT: + push( T_SRDToken::Integer( reader_.read< int32_t >( ) ) ); + break; + + case E_V1Tag_::LONG: + push( T_SRDToken::Long( reader_.read< int64_t >( ) ) ); + break; + + case E_V1Tag_::FLOAT: + push( T_SRDToken::Float( reader_.read< double >( ) ) ); + break; + + case E_V1Tag_::BINARY: + { + const uint32_t size( reader_.read< uint32_t >( ) ); + T_SharedPtr< T_Buffer< uint8_t > > buffer( + NewShared< T_Buffer< uint8_t > >( size ) ); + if ( size ) { + reader_.stream( ).read( buffer->data( ) , size ); + } + push( T_SRDToken::Binary( buffer ) ); + } + break; + } + } +} + +void T_SRDBRVersion1_::read( ) +{ + T_SRDReaderTargetHelper rth( target_ , errors_ ); + try { + readLoop( ); + } catch ( X_StreamError const& e ) { + switch ( e.code( ) ) { + + case E_StreamError::BAD_DATA: + errors_.add( "invalid data" , name_ , reader_.stream( ).position( ) ); + break; + + case E_StreamError::END: + errors_.add( "unexpected end of file" , name_ , reader_.stream( ).position( ) ); + break; + + default: + errors_.add( "read error" , name_ , reader_.stream( ).position( ) ); + break; + } + } +} + + +} // namespace + + +/*= T_SRDBinaryWriter ========================================================*/ + +T_SRDBinaryWriter::T_SRDBinaryWriter( A_OutputStream& output ) + : writer_( output , E_Endian::LITTLE ) , depth_( 0 ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDBinaryWriter::flushComment( ) +{ + if ( comment_.size( ) != 0 ) { + writer_.write( E_V1Tag_::COMMENT ); + writer_.write( comment_ ); + comment_.clear( ); + } +} + +void T_SRDBinaryWriter::writeWord( T_String const& word ) +{ + // Try to find the word in the index + const uint32_t hash = ComputeHash( word ); + uint32_t index = wordsIndex_.first( hash ); + while ( index != T_HashIndex::INVALID_INDEX ) { + if ( word == words_[ index ] ) { + break; + } + index = wordsIndex_.next( index ); + } + + if ( index == T_HashIndex::INVALID_INDEX ) { + // New word + wordsIndex_.add( hash ); + words_.add( word ); + writer_.write( E_V1Tag_::WORD_NEW ); + writer_.write( word ); + } else { + // Known word, use the index + writer_.write( E_V1Tag_::WORD_KNOWN ); + writer_.write( index ); + } +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::start( ) +{ + if ( depth_ != 0 ) { + throw X_SRDWriterError( "already started" ); + } + writer_.write( C_MAGIC_ ); + writer_.write( E_Version_::V1 ); + depth_ = 1; + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::end( ) +{ + if ( depth_ != 1 ) { + throw X_SRDWriterError( depth_ == 0 ? "already ended" : "unterminated lists" ); + } + + flushComment( ); + writer_.write( E_V1Tag_::END ); + depth_ = 0; + + comment_.free( ); + wordsIndex_.free( ); + words_.free( ); + + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::startList( ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::LIST ); + depth_ ++; + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::endList( ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::END ); + depth_ --; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::putText( T_String const& text ) +{ + if ( T_SRDToken::IsWord( text ) ) { + return putWord( text ); + } else { + return putString( text ); + } +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putWord( T_String const& word ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writeWord( word ); + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putString( T_String const& string ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::STRING ); + writer_.write( string ); + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putComment( T_String const& comment ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + comment_ << comment; + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putVariable( T_String const& variable ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::VAR_WORD ); + writeWord( variable ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::putBinary( + T_Buffer< uint8_t > const& binary ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::BINARY ); + writer_.write( uint32_t( binary.bytes( ) ) ); + if ( binary.size( ) != 0 ) { + writer_.stream( ).write( binary.data( ) , binary.bytes( ) ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::putInteger( int64_t value ) +{ + if ( value >= INT32_MIN && value <= INT32_MAX ) { + return putInt32( int32_t( value ) ); + } else { + return putInt64( int64_t( value ) ); + } +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putInt32( int32_t value ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::INT ); + writer_.write( value ); + return *this; +} + +T_SRDBinaryWriter& T_SRDBinaryWriter::putInt64( int64_t value ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::LONG ); + writer_.write( value ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDBinaryWriter& T_SRDBinaryWriter::putFloat( double value ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "not started" ); + } + flushComment( ); + writer_.write( E_V1Tag_::FLOAT ); + writer_.write( value ); + return *this; +} + + +/*= T_SRDBinaryReader ========================================================*/ + +void T_SRDBinaryReader::read( T_String const& name , A_InputStream& input ) +{ + T_BinaryReader rd( input , E_Endian::LITTLE ); + T_SRDErrors errors; + + // Read/check magic number + const auto magicPos( input.position( ) ); + const uint32_t magic( ([&]( ) { + try { + return rd.read< uint32_t >( ); + } catch ( X_StreamError& e ) { + switch ( e.code( ) ) { + + case E_StreamError::BAD_DATA: + case E_StreamError::END: + errors.add( "missing magic" , name , input.position( ) ); + break; + + default: + errors.add( "read error" , name , input.position( ) ); + break; + } + throw X_SRDErrors( errors ); + } + })( ) ); + if ( magic != C_MAGIC_ ) { + errors.add( "invalid magic number" , name , magicPos ); + throw X_SRDErrors( errors ); + } + + // Read version number + const uint32_t versionPos( input.position( ) ); + const uint32_t version( ([&]( ) { + try { + return rd.read< uint32_t >( ); + } catch ( X_StreamError& e ) { + switch ( e.code( ) ) { + + case E_StreamError::BAD_DATA: + case E_StreamError::END: + errors.add( "missing version ID" , name , input.position( ) ); + break; + + default: + errors.add( "read error" , name , input.position( ) ); + break; + } + throw X_SRDErrors( errors ); + } + })( ) ); + switch ( version ) { + + case uint32_t( E_Version_::V1 ): + T_SRDBRVersion1_( rd , name , errors , target_ ).read( ); + break; + + default: + errors.add( "invalid version ID" , name , versionPos ); + break; + } + + if ( errors.size( ) != 0 ) { + throw X_SRDErrors( errors ); + } +} diff --git a/src/SRDData.cc b/src/SRDData.cc new file mode 100644 index 0000000..af665b0 --- /dev/null +++ b/src/SRDData.cc @@ -0,0 +1,657 @@ +/******************************************************************************/ +/* SRD PARSER AND PREPROCESSOR - DATA REPRESENTATION **************************/ +/******************************************************************************/ + +#include +#include +#include +using namespace lw; + + +/*= T_SRDLocation ============================================================*/ + +static thread_local T_ThreadedPoolAllocator< + sizeof( T_SRDLocation ) , alignof( T_SRDLocation ) , + 128 , 2 + > LocationPool_; + +void* T_SRDLocation::operator new( + const size_t count ) noexcept +{ + return LocationPool_.allocate( count ); +} + +void T_SRDLocation::operator delete( + void* const object ) noexcept +{ + LocationPool_.free( object ); +} + +/*----------------------------------------------------------------------------*/ + +T_SRDLocation::T_SRDLocation( + T_String const& source , + const uint32_t line , + const size_t character ) noexcept + : source_( source ) , line_( line ) , character_( character ) +{ + assert( line > 0 ); +} + +T_SRDLocation::T_SRDLocation( + T_String const& source , + const size_t byte ) noexcept + : source_( source ) , line_( 0 ) , character_( byte ) +{ } + +T_SRDLocation::T_SRDLocation( + T_SRDLocation const& other ) noexcept + : source_( other.source_ ) , line_( other.line_ ) , + character_( other.character_ ) , + chaining_( other.chaining_ ) +{ } + +T_SRDLocation::T_SRDLocation( + T_SRDLocation&& other ) noexcept + : T_SRDLocation( ) +{ + swap( *this , other ); +} + +namespace lw { + M_DECLARE_SWAP( T_SRDLocation ) + { + using std::swap; + swap( lhs.source_ , rhs.source_ ); + swap( lhs.line_ , rhs.line_ ); + swap( lhs.character_ , rhs.character_ ); + swap( lhs.chaining_ , rhs.chaining_ ); + } +} + +T_SRDLocation& T_SRDLocation::operator= ( + T_SRDLocation const& other ) noexcept +{ + source_ = other.source_; + line_ = other.line_; + character_ = other.character_; + chaining_ = other.chaining_; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& operator<< ( + T_StringBuilder& sb , + T_SRDLocation const& location ) noexcept +{ + if ( location.unknown( ) ) { + sb << "unknown location"; + } else { + sb << location.source( ) << ' '; + if ( location.binary( ) ) { + sb << 'b' << location.character( ); + } else { + sb << 'l' << location.line( ) + << ' ' << 'c' << location.character( ); + } + } + return sb; +} + + +/*= T_SRDErrors ============================================================*/ + +void T_SRDErrors::checkAdded( + T_SRDError const& last ) +{ + if ( last.isDetails( ) || errCount_ >= MAX_ERRORS ) { + return; + } + errCount_ ++; + if ( errCount_ == T_SRDErrors::MAX_ERRORS ) { + errors_.addNew( T_String::Pooled( "too many errors" ) , + last.location( ) ); + throw X_SRDErrors( *this ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDErrors::addAll( + T_SRDErrors const& source ) +{ + if ( errCount_ >= MAX_ERRORS ) { + return; + } + + const uint32_t nErrors( source.size( ) ); + RPC_SRDLocation lastLocation = nullptr; + for ( uint32_t i = 0 ; i < nErrors ; i ++ ) { + errors_.add( source[ i ] ); + if ( !source[ i ].isDetails( ) ) { + lastLocation = &source[ i ].location( ); + } + } + errCount_ += source.errCount_; + if ( errCount_ >= MAX_ERRORS ) { + errors_.addNew( T_String::Pooled( "too many errors" ) , + lastLocation ? *lastLocation : T_SRDLocation( ) ); + throw X_SRDErrors( *this ); + } +} + + +/*= X_SRDErrors ============================================================*/ + +char const* X_SRDErrors::what( ) const noexcept +{ + return "SRD read/parse errors"; +} + +#ifndef LW_MINLIB +void X_SRDErrors::log( T_Logger& logger ) const +{ + const auto nErrors( errors.size( ) ); + for ( size_t i = 0 ; i < nErrors ; i ++ ) { + auto sb( logger.error( ) ); + auto const& e( errors[ i ] ); + auto const& l( e.location( ) ); + + if ( l.unknown( ) ) { + sb << "unknown location"; + } else { + sb << l.source( ); + if ( l.binary( ) ) { + sb << ", b" << l.byte( ); + } else { + sb << ", l" << l.line( ) << ", c" << l.character( ); + } + } + sb << ": " << e.error( ); + } +} +#endif // LW_MINLIB + + +/*= T_SRDToken =============================================================*/ + +T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , E_SRDTokenType tt ) +{ + static char const* const C_TOKEN_TYPES_[] = { + "(...)" , + "(" , ")" , + "WORD" , + "$WORD" , + "STRING" , + "BINARY" , + "INT" , + "LONG" , + "FLOAT" , + "COMMENT" + }; + return sb << C_TOKEN_TYPES_[ int( tt ) ]; +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDToken::IsWord( T_String const& text ) +{ + enum { + INIT_ , + AFTER_DASH_ , + IN_WORD_ + } state = INIT_; + + T_StringIterator it( text ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + + if ( state == INIT_ ) { + if ( c == '-' ) { + state = AFTER_DASH_; + } else if ( c.isAlpha( ) ) { + state = IN_WORD_; + } else { + return false; + } + + } else if ( state == AFTER_DASH_ ) { + if ( c.isAlpha( ) ) { + state = IN_WORD_; + } else { + return false; + } + } else if ( !c.isAlphanumeric( ) ) { + if ( c == '-' ) { + state = AFTER_DASH_; + } else { + return false; + } + } + } + + return state == IN_WORD_; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::ListStart( ) +{ + return T_SRDToken( E_SRDTokenType::START ); +} + +T_SRDToken T_SRDToken::ListEnd( ) +{ + return T_SRDToken( E_SRDTokenType::END ); +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::List( ) +{ + T_SRDToken token( E_SRDTokenType::LIST ); + token.list_ = NewOwned< T_SRDList >( 16 ); + return token; +} + +T_SRDToken T_SRDToken::List( T_SRDList const& list ) +{ + T_SRDToken token( E_SRDTokenType::LIST ); + token.list_ = NewOwned< T_SRDList >( list ); + return token; +} + +T_SRDToken T_SRDToken::List( T_SRDList&& list ) +{ + T_SRDToken token( E_SRDTokenType::LIST ); + token.list_ = NewOwned< T_SRDList >( std::move( list ) ); + return token; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::AutoText( T_String text ) +{ + const bool w = IsWord( text ); + T_SRDToken token( w ? E_SRDTokenType::WORD : E_SRDTokenType::STRING ); + token.stringValue_ = std::move( text ); + return token; +} + +T_SRDToken T_SRDToken::Word( T_String word ) +{ + assert( IsWord( word ) ); + T_SRDToken token( E_SRDTokenType::WORD ); + token.stringValue_ = std::move( word ); + return token; +} + +T_SRDToken T_SRDToken::String( T_String string ) +{ + T_SRDToken token( E_SRDTokenType::STRING ); + token.stringValue_ = std::move( string ); + return token; +} + +T_SRDToken T_SRDToken::Variable( T_String variable ) +{ + assert( IsWord( variable ) ); + T_SRDToken token( E_SRDTokenType::VAR ); + token.stringValue_ = std::move( variable ); + return token; +} + +T_SRDToken T_SRDToken::Comment( T_String text ) +{ + T_SRDToken token( E_SRDTokenType::COMMENT ); + token.stringValue_ = std::move( text ); + return token; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::Binary( + T_SharedPtr< T_Buffer< uint8_t > > const& data ) noexcept +{ + T_SRDToken token( E_SRDTokenType::BINARY ); + assert( bool( data ) ); + token.binary_ = data; + return token; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::Flush( ) noexcept +{ + return T_SRDToken( E_SRDTokenType::FLUSH ); +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken T_SRDToken::AutoInteger( int64_t value ) +{ + const bool isInt = ( value >= INT32_MIN && value <= INT32_MAX ); + const E_SRDTokenType type = ([ = ]( ) { + if ( isInt ) { + return E_SRDTokenType::INT; + } else { + return E_SRDTokenType::LONG; + } + } )( ); + + T_SRDToken token( type ); + token.longValue_ = value; + token.floatValue_ = value; + + T_StringBuilder sb; + sb << value; + token.stringValue_ = std::move( sb ); + + return token; +} + +T_SRDToken T_SRDToken::Integer( int32_t value ) +{ + T_SRDToken token( E_SRDTokenType::INT ); + token.longValue_ = value; + token.floatValue_ = value; + + T_StringBuilder sb; + sb << value; + token.stringValue_ = std::move( sb ); + + return token; +} + +T_SRDToken T_SRDToken::Long( int64_t value ) +{ + T_SRDToken token( E_SRDTokenType::LONG ); + token.longValue_ = value; + token.floatValue_ = value; + + T_StringBuilder sb; + sb << value; + token.stringValue_ = std::move( sb ); + + return token; +} + +T_SRDToken T_SRDToken::Float( double value ) +{ + T_SRDToken token( E_SRDTokenType::FLOAT ); + token.longValue_ = value; + token.floatValue_ = value; + + T_StringBuilder sb; + sb << value; + token.stringValue_ = std::move( sb ); + + return token; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken::T_SRDToken( T_SRDToken const& other ) + : type_( other.type_ ) , text_( other.text_ ) , + longValue_( other.longValue_ ) , + floatValue_( other.floatValue_ ) , + stringValue_( other.stringValue_ ) , + binary_( other.binary_ ) , + location_( other.location_ ) +{ + if ( other.list_ ) { + list_ = NewOwned< T_SRDList >( *other.list_ ); + } +} + +T_SRDToken& T_SRDToken::operator= ( T_SRDToken const& other ) +{ + type_ = other.type_; + text_ = other.text_; + if ( other.list_ ) { + list_ = NewOwned< T_SRDList >( *other.list_ ); + } else { + list_.clear( ); + } + longValue_ = other.longValue_; + floatValue_ = other.floatValue_; + stringValue_ = other.stringValue_; + location_ = other.location_; + binary_ = other.binary_; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_SRDToken& lhs , T_SRDToken& rhs ) noexcept +{ + using std::swap; + swap( lhs.type_ , rhs.type_ ); + swap( lhs.text_ , rhs.text_ ); + swap( lhs.list_ , rhs.list_ ); + swap( lhs.longValue_ , rhs.longValue_ ); + swap( lhs.floatValue_ , rhs.floatValue_ ); + swap( lhs.stringValue_ , rhs.stringValue_ ); + swap( lhs.binary_ , rhs.binary_ ); + swap( lhs.location_ , rhs.location_ ); +} + +/*----------------------------------------------------------------------------*/ + +int T_SRDToken::compare( T_SRDToken const& other ) const +{ + // Numeric values can be compared even if they don't have the exact same type + if ( isInteger( ) && other.isInteger( ) ) { + return T_Comparator< int64_t >::compare( longValue_ , other.longValue_ ); + } + if ( isNumeric( ) && other.isNumeric( ) ) { + return T_Comparator< double >::compare( floatValue_ , other.floatValue_ ); + } + + // We need to have the same type to keep going + if ( type_ != other.type_ ) { + return T_Comparator< int >::compare( int( type_ ) , int( other.type_ ) ); + } + + // List start/end -> nothing else to do + if ( type_ == E_SRDTokenType::START || type_ == E_SRDTokenType::END ) { + return 0; + } + + // List -> compare all tokens + if ( type_ == E_SRDTokenType::LIST ) { + auto const& l0( *list_ ); + auto const& l1( *( other.list_ ) ); + const auto lim( std::min( l0.size( ) , l1.size( ) ) ); + for ( size_t i = 0 ; i < lim ; i ++ ) { + const auto tcmp( l0[ i ].compare( l1[ i ] ) ); + if ( tcmp != 0 ) { + return tcmp; + } + } + return T_Comparator< size_t >::compare( l0.size( ) , l1.size( ) ); + } + + + // Everything else: text compare + return T_Comparator< T_String >::compare( stringValue_ , other.stringValue_ ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDToken::hasFullText( ) const +{ + return text_ + || type_ == E_SRDTokenType::WORD + || type_ == E_SRDTokenType::START + || type_ == E_SRDTokenType::END; +} + +T_String T_SRDToken::fullText( ) const +{ + switch ( type_ ) { + + case E_SRDTokenType::WORD: + return stringValue_; + + case E_SRDTokenType::START: + return T_String::Pooled( "(" ); + + case E_SRDTokenType::END: + return T_String::Pooled( ")" ); + + default: + return text_; + + } +} + +T_SRDToken& T_SRDToken::setFullText( T_String text ) +{ + if ( type_ != E_SRDTokenType::WORD && type_ != E_SRDTokenType::START + && type_ != E_SRDTokenType::END ) { + text_ = std::move( text ); + } + return *this; +} + +T_SRDToken& T_SRDToken::generateFullText( ) +{ + if ( hasFullText( ) ) { + return *this; + } + + T_StringBuilder sb; + switch ( type_ ) { + + case E_SRDTokenType::LIST: + sb << '('; + if ( list_->size( ) != 0 ) { + sb << ' '; + } + for ( size_t i = 0 ; i < list_->size( ) ; i ++ ) { + sb << (*list_)[ i ].generateFullText( ) + .fullText( ) + << ' '; + } + sb << ')'; + break; + + case E_SRDTokenType::VAR: + sb << '$' << stringValue_; + break; + + case E_SRDTokenType::STRING: + { + T_StringIterator it( stringValue_ ); + sb << '"'; + while ( !it.atEnd( ) ) { + T_Character c( it ); + if ( c.isControl( ) ) { + if ( c == '\n' ) { + sb << "\\n"; + } else if ( c == '\t' ) { + sb << "\\t"; + } else { + sb << "\\x"; + if ( c < 16 ) { + sb << '0'; + } + sb << c.codepoint; + } + } else if ( c == '\\' ) { + sb << "\\\\"; + } else if ( c == '"' ) { + sb << "\\\""; + } else { + sb << c; + } + it.next( ); + } + sb << '"'; + + break; + } + + case E_SRDTokenType::COMMENT: + { + T_StringIterator it( stringValue_ ); + bool atStart = true; + while ( !it.atEnd( ) ) { + T_Character c( it ); + if ( atStart ) { + sb << '#'; + atStart = false; + } + sb << c; + if ( c == '\n' ) { + atStart = true; + } + it.next( ); + } + break; + } + + case E_SRDTokenType::INT: + case E_SRDTokenType::LONG: + sb << longValue_; + break; + + case E_SRDTokenType::FLOAT: + sb << floatValue_; + break; + + case E_SRDTokenType::BINARY: + { + sb << '['; + const uint32_t sz( binary_->size( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + sb << ' '; + sb.appendNumeric( uint64_t( binary_->operator[]( i ) ) , 16 ); + } + sb << " ]"; + break; + } + + + default: + assert( 0 && "This shouldn't be happening" ); + } + text_ = std::move( sb ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDToken& T_SRDToken::location( T_String const& source , size_t byte ) +{ + location_ = NewShared< T_SRDLocation >( source , byte ); + return *this; +} + +T_SRDToken& T_SRDToken::location( T_String const& source , uint32_t line , size_t character ) +{ + location_ = NewShared< T_SRDLocation >( source , line , character ); + return *this; +} + +T_SRDToken& T_SRDToken::location( T_SRDLocation const& other ) +{ + location_ = NewShared< T_SRDLocation >( other ); + return *this; +} + +T_SRDToken& T_SRDToken::location( T_SRDLocation && other ) +{ + location_ = NewShared< T_SRDLocation >( std::move( other ) ); + return *this; +} + +T_SRDToken& T_SRDToken::copyLocationOf( T_SRDToken const& other ) +{ + location_ = other.location_; + return *this; +} + diff --git a/src/SRDDefinitions.cc b/src/SRDDefinitions.cc new file mode 100644 index 0000000..06c60cd --- /dev/null +++ b/src/SRDDefinitions.cc @@ -0,0 +1,297 @@ +/******************************************************************************/ +/* SRD - PARSER DEFINITIONS ***************************************************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +/*= T_SRDEnum ================================================================*/ + +T_SRDEnum& T_SRDEnum::operator<< ( T_String&& word ) +{ + if ( (*this)[ word ] != T_HashIndex::INVALID_INDEX ) { + throw std::invalid_argument( "duplicate enum item" ); + } + + const uint32_t hash( ComputeHash( word ) ); + index_.add( hash ); + words_.add( std::move( word ) ); + return *this; +} + +uint32_t T_SRDEnum::operator[] ( T_String const& word ) const +{ + const uint32_t hash( ComputeHash( word ) ); + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( words_[ idx ] == word ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + +uint32_t T_SRDEnum::operator[] ( char const* word ) const +{ + const auto len = strlen( word ); + const uint32_t hash( HashData( (uint8_t const*)word , len ) ); + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + auto const& w( words_[ idx ] ); + if ( w.size( ) == len && !memcmp( w.data( ) , word , len ) ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + + +/*= T_SRDInputItem ===========================================================*/ + +void lw::swap( T_SRDInputItem& lhs , T_SRDInputItem& rhs ) noexcept +{ + using std::swap; + swap( lhs.type_ , rhs.type_ ); + swap( lhs.word_ , rhs.word_ ); + swap( lhs.token_ , rhs.token_ ); + swap( lhs.items_ , rhs.items_ ); + swap( lhs.min_ , rhs.min_ ); + swap( lhs.max_ , rhs.max_ ); +} + +T_SRDInputItem& T_SRDInputItem::operator<< ( T_SRDInputItem item ) +{ + if ( type_ != E_SRDInputItem::ALTERNATIVE + && type_ != E_SRDInputItem::REPETITION ) { + throw std::invalid_argument( + "not an ALTERNATIVE or REPETITION" ); + } + if ( item.type( ) == type_ ) { + items_.addAll( item.items( ) ); + } else { + items_.add( std::move( item ) ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , T_SRDInputItem const& item ) +{ + switch ( item.type( ) ) { + + case E_SRDInputItem::WORD: + sb << item.word( ); + break; + + case E_SRDInputItem::ENUM: + sb << '[' << item.word( ) << "::*]"; + break; + + case E_SRDInputItem::TOKEN: + sb << item.token( ); + break; + + case E_SRDInputItem::ALTERNATIVE: + { + auto const& alts( item.items( ) ); + sb << "[ "; + for ( uint32_t i = 0 ; i < alts.size( ) ; i ++ ) { + if ( i != 0 ) { + sb << " | "; + } + sb << alts[ i ]; + } + sb << " ]"; + break; + } + + case E_SRDInputItem::REPETITION: + { + auto const& seq( item.items( ) ); + const bool listBits( seq.size( ) != 1 && ( item.min( ) != 1 || item.max( ) != 1 ) ); + + if ( listBits ) { + sb << "[ "; + } + for ( uint32_t i = 0 ; i < seq.size( ) ; i ++ ) { + if ( i != 0 ) { + sb << ' '; + } + sb << seq[ i ]; + } + if ( listBits ) { + if ( seq.size( ) ) { + sb << ' '; + } + sb << ']'; + } + if ( item.min( ) == 0 && item.max( ) == 1 ) { + sb << '?'; + } else if ( item.max( ) == UINT32_MAX ) { + if ( item.min( ) == 0 ) { + sb << '*'; + } else { + if ( item.min( ) != 1 ) { + sb << '{' << item.min( ); + } + sb << '+'; + if ( item.min( ) != 1 ) { + sb << '}'; + } + } + } else if ( item.min( ) == item.max( ) && item.min( ) != 1 ) { + sb << '{' << item.min( ) << '}'; + } else if ( item.min( ) != item.max( ) ) { + sb << '{' << item.min( ) << ',' + << item.max( ) << '}'; + } + break; + } + } + + return sb; +} + + +/*= T_SRDInputRule ===========================================================*/ + +T_SRDInputRule& T_SRDInputRule::operator<< ( T_SRDInputItem item ) +{ + items_.add( std::move( item ) ); + return *this; +} + +T_StringBuilder& lw::operator<< ( T_StringBuilder& sb , T_SRDInputRule const& item ) +{ + auto const& seq( item.rule( ) ); + sb << '('; + for ( uint32_t i = 0 ; i < seq.size( ) ; i ++ ) { + sb << ' ' << seq[ i ]; + } + if ( item.context( ) ) { + sb << " ..." << item.context( ) << "..."; + } + sb << " )"; + return sb; +} + + +/*= T_SRDContext =============================================================*/ + +T_SRDContext::T_SRDContext( T_String name , T_String parent ) + : name_( std::move( name ) ) , parent_( std::move( parent ) ) , + rules_( 16 ) +{ } + + +T_SRDContext& T_SRDContext::operator<< ( T_SRDInputRule rule ) +{ + rules_.add( std::move( rule ) ); + return *this; +} + +T_StringBuilder& T_SRDContext::dump( T_StringBuilder& sb , T_String const& separator ) +{ + const auto nr( rules_.size( ) ); + for ( uint32_t i = 0 ; i < nr ; i ++ ) { + if ( i > 0 ) { + sb << separator; + } + sb << rules_[ i ]; + } + return sb; +} + + +/*= T_SRDParserDefs ==========================================================*/ + +T_SRDParserDefs::T_SRDParserDefs( T_String defaultContext ) + : dfCtx_( std::move( defaultContext ) ) , + ctx_( []( T_SRDContext const& c ) -> T_String const& { + return c.name( ); + } , 64 , 64 , 64 ) , + enums_( []( T_SRDEnum const& e ) -> T_String const& { + return e.name( ); + } , 64 , 64 , 64 ) +{ + ctx_.add( T_SRDContext( dfCtx_ ) ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDParserDefs::defaultContext( T_String const& name ) +{ + if ( name == dfCtx_ ) { + return; + } + if ( !ctx_.contains( name ) ) { + throw std::invalid_argument( "default context must exist" ); + } + dfCtx_ = name; +} + +uint32_t T_SRDParserDefs::contextId( T_String const& name ) const +{ + return ctx_.indexOf( name ); +} + +T_SRDParserDefs& T_SRDParserDefs::operator<< ( SetHandler sh ) +{ + if ( sh.start ) { + onStart_ = sh.handler; + } else { + onFinish_ = sh.handler; + } + return *this; +} + +T_SRDContext& T_SRDParserDefs::context( T_String const& name ) +{ + if ( !ctx_.contains( name ) ) { + ctx_.add( T_SRDContext( name ) ); + } + return *( ctx_.get( name ) ); +} + +T_SRDContext& T_SRDParserDefs::context( T_String const& name , T_String const& parent ) +{ + if ( !ctx_.contains( name ) ) { + ctx_.add( T_SRDContext( name , parent ) ); + } + + T_SRDContext& c( *( ctx_.get( name ) ) ); + if ( c.parent( ) != parent ) { + throw std::invalid_argument( "incorrect parent name" ); + } + + return c; +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDParserDefs::hasEnum( T_String const& name ) const +{ + return enums_.contains( name ); +} + +T_SRDEnum& T_SRDParserDefs::enumeration( T_String const& name ) +{ + if ( !enums_.contains( name ) ) { + enums_.add( T_SRDEnum( name ) ); + } + return *( enums_.get( name ) ); +} + +T_SRDEnum const& T_SRDParserDefs::enumeration( T_String const& name ) const +{ + auto eptr( enums_.get( name ) ); + if ( eptr == nullptr ) { + throw std::invalid_argument( "unknown enum" ); + } + return *eptr; +} diff --git a/src/SRDIO.cc b/src/SRDIO.cc new file mode 100644 index 0000000..61460a4 --- /dev/null +++ b/src/SRDIO.cc @@ -0,0 +1,231 @@ +/******************************************************************************/ +/* SRD PARSER AND PREPROCESSOR - COMMON INPUT / OUTPUT CODE *******************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +/*= X_SRDWriterError =========================================================*/ + +char const* X_SRDWriterError::what( ) const noexcept +{ + return msg_; +} + + +/*= A_SRDWriter ==============================================================*/ + +A_SRDWriter& A_SRDWriter::putList( T_SRDList const& list ) +{ + const auto sz = list.size( ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + putToken( list[ i ] ); + } + return *this; +} + +A_SRDWriter& A_SRDWriter::putToken( T_SRDToken const& token ) +{ + switch ( token.type( ) ) { + + case E_SRDTokenType::START: + return startList( ); + + case E_SRDTokenType::LIST: + startList( ); + putList( token.list( ) ); + + case E_SRDTokenType::END: + return endList( ); + + case E_SRDTokenType::WORD: + return putWord( token.stringValue( ) ); + + case E_SRDTokenType::VAR: + return putVariable( token.stringValue( ) ); + + case E_SRDTokenType::STRING: + return putString( token.stringValue( ) ); + + case E_SRDTokenType::BINARY: + return putBinary( token.binary( ) ); + + case E_SRDTokenType::INT: + return putInt32( int32_t( token.longValue( ) ) ); + + case E_SRDTokenType::LONG: + return putInt64( token.longValue( ) ); + + case E_SRDTokenType::FLOAT: + return putFloat( token.floatValue( ) ); + + case E_SRDTokenType::COMMENT: + return putComment( token.stringValue( ) ); + + case E_SRDTokenType::FLUSH: + return *this; + } + throw std::range_error( "unknown token type" ); +} + + +/*= T_SRDListFixer ===========================================================*/ + +T_SRDListFixer::T_SRDListFixer( + A_SRDReaderTarget& output ) noexcept + : output_( output ) +{ } + +void T_SRDListFixer::start( + T_SRDErrors& errors ) +{ + listStarts_.clear( ); + output_.start( errors ); +} + +void T_SRDListFixer::push( T_SRDErrors& errors , + T_SRDToken&& token ) +{ + switch ( token.type( ) ) { + case E_SRDTokenType::START: + listStarts_.add( token.location( ) ); + break; + + case E_SRDTokenType::END: + if ( listStarts_.size( ) == 0 ) { + errors.add( "unexpected end of list" , token.location( ) ); + return; + } + listStarts_.remove( listStarts_.size( ) - 1 ); + break; + + default: break; + } + output_.push( errors , std::move( token ) ); +} + +void T_SRDListFixer::end( + T_SRDErrors& errors ) +{ + const auto nls( listStarts_.size( ) ); + const T_String eolLoc( T_String::Pooled( "*unterminated list*" ) ); + for ( auto i = 0u ; i < nls ; i ++ ) { + errors.add( "unterminated list" , listStarts_[ i ] ); + auto tok( T_SRDToken::ListEnd( ) ); + tok.location( eolLoc , nls - i - 1 ); + output_.push( errors , std::move( tok ) ); + } + output_.end( errors ); +} + + +/*= T_SRDMemoryTarget ========================================================*/ + +T_SRDMemoryTarget::T_SRDMemoryTarget( bool structured ) + : structured_( structured ) , + clearFlushToken_( false ) , + list_( T_SRDToken::List( T_SRDList( ) ) ) , + stack_( 128 ) +{ } + +void T_SRDMemoryTarget::start( T_SRDErrors& ) +{ + list_.list( ) = T_SRDList( ); + current_ = &list_; +} + +void T_SRDMemoryTarget::push( T_SRDErrors& errors , T_SRDToken&& token ) +{ + assert( current_ != nullptr ); + + const auto tt( token.type( ) ); + if ( tt == E_SRDTokenType::START ) { + if ( structured_ ) { + auto& c( current_->list( ) ); + c.add( T_SRDToken::List( T_SRDList( ) ) ); + + auto& t( c[ c.size( ) - 1 ] ); + t.copyLocationOf( token ); + + stack_.add( current_ ); + current_ = &t; + } else { + auto& l( current_->list( ) ); + l.add( std::move( token ) ); + stack_.add( &( l[ l.size( ) - 1 ] ) ); + } + + } else if ( tt == E_SRDTokenType::END ) { + if ( stack_.size( ) == 0 ) { + errors.add( "unexpected ')'" , token ); + } else { + const auto last = stack_.size( ) - 1; + if ( structured_ ) { + current_ = stack_[ last ]; + } else { + current_->list( ).add( std::move( token ) ); + } + stack_.remove( last ); + } + + } else if ( tt != E_SRDTokenType::FLUSH || !clearFlushToken_ ) { + current_->list( ).add( std::move( token ) ); + } +} + +void T_SRDMemoryTarget::end( T_SRDErrors& errors ) +{ + const auto stackSize( stack_.size( ) ); + if ( stackSize != 0 ) { + try { + const uint32_t start( structured_ ? 1 : 0 ); + for ( uint32_t i = start ; i < stackSize ; i ++ ) { + errors.add( "unterminated list" , + *stack_[ i ] ); + } + if ( structured_ ) { + errors.add( "unterminated list" , *current_ ); + } + } catch ( ... ) { + stack_.free( ); + throw; + } + } + stack_.free( ); +} + + +/*= T_SRDWriterTarget ========================================================*/ + +T_SRDWriterTarget::T_SRDWriterTarget( A_SRDWriter& writer ) + : writer_( writer ) +{ } + +void T_SRDWriterTarget::start( T_SRDErrors& errors ) +{ + try { + writer_.start( ); + } catch ( X_SRDWriterError const& e ) { + errors.add( e.what( ) , T_SRDLocation( ) ); + } +} + +void T_SRDWriterTarget::push( T_SRDErrors& errors , T_SRDToken&& token ) +{ + try { + writer_.putToken( token ); + } catch ( X_SRDWriterError const& e ) { + errors.add( e.what( ) , token ); + } +} + +void T_SRDWriterTarget::end( T_SRDErrors& errors ) +{ + try { + writer_.end( ); + } catch ( X_SRDWriterError const& e ) { + errors.add( e.what( ) , T_SRDLocation( ) ); + } +} diff --git a/src/SRDPPCommands.cc b/src/SRDPPCommands.cc new file mode 100644 index 0000000..827afd5 --- /dev/null +++ b/src/SRDPPCommands.cc @@ -0,0 +1,2518 @@ +/******************************************************************************/ +/* SRD - PREPROCESSOR COMMANDS ************************************************/ +/******************************************************************************/ + +#include +#include +#include +#include +#include +#include +using namespace lw; + + +/*= BUILTIN COMMANDS INITIALIZER =============================================*/ + +#define M_ADDCMD_( NAME , ARGS... ) \ + add< T_SRDPPCMD_ ## NAME >( ARGS ) + +void T_SRDPreprocessorConfig::addBuiltinCommands( ) noexcept +{ + M_ADDCMD_( Add ); + M_ADDCMD_( And ); + M_ADDCMD_( Bless ); + M_ADDCMD_( Break ); + M_ADDCMD_( BwAnd ); + M_ADDCMD_( BwNot ); + M_ADDCMD_( BwOr ); + M_ADDCMD_( BwXor ); + M_ADDCMD_( Call ); + M_ADDCMD_( CastString ); + M_ADDCMD_( CastWord ); + M_ADDCMD_( CastInt ); + M_ADDCMD_( CastLong ); + M_ADDCMD_( CastBestInt ); + M_ADDCMD_( CastReal ); + M_ADDCMD_( CastVar ); + M_ADDCMD_( CastList ); + M_ADDCMD_( ClearScope ); + M_ADDCMD_( Cmp ); + M_ADDCMD_( CmpEq ); + M_ADDCMD_( CmpNe ); + M_ADDCMD_( CmpLt ); + M_ADDCMD_( CmpGt ); + M_ADDCMD_( CmpLe ); + M_ADDCMD_( CmpGe ); + M_ADDCMD_( Concat ); + M_ADDCMD_( Div ); + M_ADDCMD_( EndsWith ); + M_ADDCMD_( Eval ); + M_ADDCMD_( Error ); + M_ADDCMD_( FromSource ); + M_ADDCMD_( FromSRB ); + M_ADDCMD_( Get ); + M_ADDCMD_( If ); + M_ADDCMD_( Ignore ); + M_ADDCMD_( IsBlessed ); + M_ADDCMD_( IsMacro ); + M_ADDCMD_( IsSet ); + M_ADDCMD_( Length ); + M_ADDCMD_( ListMacros ); + M_ADDCMD_( ListVariables ); + M_ADDCMD_( Mod ); + M_ADDCMD_( Mul ); + M_ADDCMD_( Neg ); + M_ADDCMD_( Not ); + M_ADDCMD_( Or ); + M_ADDCMD_( Output ); + M_ADDCMD_( Raw ); + M_ADDCMD_( Rethrow ); + M_ADDCMD_( Scope ); + M_ADDCMD_( Set ); + M_ADDCMD_( SetMacro ); + M_ADDCMD_( StartsWith ); + M_ADDCMD_( StrFind ); + M_ADDCMD_( StrSplit ); + M_ADDCMD_( Sub ); + M_ADDCMD_( Substr ); + M_ADDCMD_( ToSource ); + M_ADDCMD_( Try ); + M_ADDCMD_( TypeOf ); + M_ADDCMD_( Unset ); + M_ADDCMD_( UnsetMacro ); + M_ADDCMD_( Unwrap ); + M_ADDCMD_( Xor ); +} + +void T_SRDPreprocessorConfig::addVFSCommands( + T_VFS& vfs ) noexcept +{ + M_ADDCMD_( VFSList , vfs ); + M_ADDCMD_( VFSLoad , vfs ); + M_ADDCMD_( VFSType , vfs ); +} + + +/*= HELPER FOR MATH COMMANDS =================================================*/ + +typedef std::function< int64_t( int64_t , int64_t ) > F_LongOp_; +typedef std::function< double ( double , double ) > F_RealOp_; + +namespace { + +void DoMathOp_( T_SRDPreprocessorState& state , F_LongOp_ longOp , F_RealOp_ realOp , bool nonZero = false ) +{ + auto& input( state.output( ) ); + auto& output( state.data( ).top( ).output( ) ); + auto const& location( *state.initialLocation( ) ); + + const uint32_t nInputs( input.size( ) ); + if ( nInputs < 2 ) { + state.data( ).addError( location , "not enough arguments" ); + if ( nInputs == 0 ) { + T_SRDToken r( T_SRDToken::Integer( 0 ) ); + r.location( location ); + output.add( std::move( r ) ); + return; + } + } + + const E_SRDTokenType type( input[ 0 ].type( ) ); + if ( type != E_SRDTokenType::INT && type != E_SRDTokenType::LONG && type != E_SRDTokenType::FLOAT ) { + state.data( ).addError( input[ 0 ] , "numeric argument expected" ); + } + + bool hadReal( type == E_SRDTokenType::FLOAT ); + int64_t rl( input[ 0 ].longValue( ) ); + double rr( input[ 0 ].floatValue( ) ); + for ( uint32_t i = 1 ; i < nInputs ; i ++ ) { + auto& tok( input[ i ] ); + const auto tt( tok.type( ) ); + if ( tt != E_SRDTokenType::INT && tt != E_SRDTokenType::LONG && + tt != E_SRDTokenType::FLOAT ) { + state.data( ).addError( tok , "numeric argument expected" ); + continue; + } + if ( tok.floatValue( ) == 0 && nonZero ) { + state.data( ).addError( tok , "non-zero argument expected" ); + continue; + } + hadReal = hadReal || tt == E_SRDTokenType::FLOAT; + if ( !hadReal ) { + rl = longOp( rl , tok.longValue( ) ); + } + rr = realOp( rr , tok.floatValue( ) ); + } + + T_SRDToken result( hadReal ? T_SRDToken::Float( rr ) : T_SRDToken::AutoInteger( rl ) ); + result.location( location ); + output.add( std::move( result ) ); +} + +void DoLogicOp_( T_SRDPreprocessorState& state , F_LongOp_ op ) +{ + auto& input( state.output( ) ); + auto& output( state.data( ).top( ).output( ) ); + auto const& location( *state.initialLocation( ) ); + + const uint32_t nInputs( input.size( ) ); + if ( nInputs < 2 ) { + state.data( ).addError( location , "not enough arguments" ); + if ( nInputs == 0 ) { + T_SRDToken r( T_SRDToken::Integer( 0 ) ); + r.location( location ); + output.add( std::move( r ) ); + return; + } + } + if ( !input[ 0 ].isInteger( ) ) { + state.data( ).addError( input[ 0 ] , "integer argument expected" ); + } + + int64_t rl( input[ 0 ].longValue( ) ); + for ( uint32_t i = 1 ; i < nInputs ; i ++ ) { + auto& tok( input[ i ] ); + if ( !tok.isInteger( ) ) { + state.data( ).addError( tok , "integer argument expected" ); + continue; + } + rl = op( rl , tok.longValue( ) ); + } + + T_SRDToken result( T_SRDToken::AutoInteger( rl ) ); + result.location( location ); + output.add( std::move( result ) ); +} + +} + + +/*= HELPER FOR COMPARISONS ===================================================*/ + +namespace { + +bool CompareInput_( T_SRDPreprocessorState & state , int & result ) +{ + auto& data( state.data( ) ); + auto& input( state.output( ) ); + auto& loc( *state.initialLocation( ) ); + + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + return false; + } + if ( input.size( ) > 2 ) { + data.addError( loc , "too many arguments" ); + return false; + } + + result = input[ 0 ].compare( input[ 1 ] ); + return true; +} + +} + + +/*= COMMANDS =================================================================*/ + +M_SRDPP_COMMAND_EXEC( Add ) +{ + DoMathOp_( state , + []( auto a , auto b ) { + return a + b; + } , + []( auto a , auto b ) { + return a + b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( And ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return ( a && b ) ? 1 : 0; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Bless ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + const auto n( input.size( ) ); + if ( n == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& ntok( input[ i ] ); + if ( ntok.type( ) != E_SRDTokenType::WORD ) { + data.addError( ntok , "word expected" ); + continue; + } + + auto const& vn( ntok.stringValue( ) ); + auto& s( data.scopes( ) ); + if ( s.isBlessed( vn ) ) { + continue; + } + + RPC_SRDList var( s.get( false , vn ) ); + if ( var == nullptr ) { + data.addError( ntok , "unknown variable" ); + continue; + } + + if ( T_SRDPreprocessor::isValidFunction( *var ) ) { + s.bless( vn ); + } else { + data.addError( ntok , "variable does not contain a valid function" ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Break ) +{ + if ( state.output( ).size( ) ) { + state.data().addError( *state.initialLocation( ) , "too many arguments" ); + } + state.data( ).interrupt( T_SRDPreprocessorState::C_CALL ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( BwAnd ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return a & b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( BwNot ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + bool useLong( false ); + int64_t iv( 0 ); + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) != 1 ) { + data.addError( loc , "too many arguments" ); + } else if ( !input[ 0 ].isInteger( ) ) { + data.addError( input[ 0 ] , "integer argument expected" ); + } else { + iv = ~input[ 0 ].longValue( ); + useLong = ( input[ 0 ].type( ) == E_SRDTokenType::LONG ); + if ( !useLong ) { + iv = iv & 0xffffffff; + } + } + + T_SRDToken output( useLong ? T_SRDToken::Long( iv ) : T_SRDToken::Integer( iv ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( BwOr ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return a | b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( BwXor ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return a ^ b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Call ) +{ + auto& data( state.data( ) ); + auto& input( state.output( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) < 1 ) { + data.addError( loc , "not enough arguments" ); + return; + } + + auto& func( input[ 0 ] ); + if ( !T_SRDPreprocessor::isValidFunction( func ) ) { + data.addError( func , "invalid function" ); + return; + } + + auto const& argsTok( func.list( )[ 0 ] ); + auto const& args( argsTok.list( ) ); + const bool hasOptArgs( args.size( ) > 0 + && args[ args.size( ) - 1 ].type( ) == E_SRDTokenType::LIST ); + const uint32_t reqArgs( args.size( ) - ( hasOptArgs ? 1 : 0 ) ); + const auto nValues( input.size( ) - 1 ); + if ( nValues < reqArgs ) { + data.addError( loc , "not enough function arguments" ); + return; + } + if ( nValues > reqArgs && !hasOptArgs ) { + data.addError( loc , "too many function arguments" ); + return; + } + + data.scopes( ).setupCallScope( args , input , 1 ); + data.push( T_SRDPreprocessorState::C_CALL , + NewShared< T_SRDList >( func.list( ).moveRange( 1 ) ) , + data.top( ).outputPointer( ) , + []( T_SRDPreprocessorState& ns ) { + ns.data( ).scopes( ).exit( ); + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastString ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + T_String outString; + switch ( token.type( ) ) { + + case E_SRDTokenType::STRING: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::WORD: + outString = token.stringValue( ); + break; + + case E_SRDTokenType::INT: + case E_SRDTokenType::LONG: + { + T_StringBuilder sb; + sb << token.longValue( ); + outString = std::move( sb ); + break; + } + + case E_SRDTokenType::FLOAT: + { + T_StringBuilder sb; + sb << token.floatValue( ); + outString = std::move( sb ); + break; + } + + case E_SRDTokenType::VAR: + { + T_StringBuilder sb; + sb << '$' << token.stringValue( ); + outString = std::move( sb ); + break; + } + + case E_SRDTokenType::BINARY: + { + auto const& buffer( token.binary( ) ); + outString = T_String( (char const*) buffer.data( ) , + buffer.bytes( ) ); + if ( !outString.valid( ) ) { + outString = T_String( ); + data.addError( token , "invalid UTF-8 data" ); + } + break; + } + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::String( std::move( outString ) ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastWord ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + T_String outString; + switch ( token.type( ) ) { + + case E_SRDTokenType::STRING: + outString = token.stringValue( ); + if ( !T_SRDToken::IsWord( outString ) ) { + data.addError( token , "invalid word" ); + continue; + } + break; + + case E_SRDTokenType::WORD: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::VAR: + outString = token.stringValue( ); + break; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::Word( std::move( outString ) ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastInt ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + int64_t value; + switch ( token.type( ) ) { + + case E_SRDTokenType::INT: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::STRING: + { + bool ok; + value = token.stringValue( ).toInteger( &ok ); + if ( !ok ) { + data.addError( token , "invalid integer value" ); + continue; + } + break; + } + + case E_SRDTokenType::LONG: + value = token.longValue( ); + break; + + case E_SRDTokenType::FLOAT: + value = token.floatValue( ); + break; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + if ( value < INT32_MIN || value > INT32_MAX ) { + data.addError( token , "value out of range" ); + continue; + } + + T_SRDToken otok( T_SRDToken::Integer( value ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastLong ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + int64_t value; + switch ( token.type( ) ) { + + case E_SRDTokenType::LONG: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::STRING: + { + bool ok; + value = token.stringValue( ).toInteger( &ok ); + if ( !ok ) { + data.addError( token , "invalid integer value" ); + continue; + } + break; + } + + case E_SRDTokenType::INT: + value = token.longValue( ); + break; + + case E_SRDTokenType::FLOAT: + value = token.floatValue( ); + break; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::Long( value ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastBestInt ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + int64_t value; + switch ( token.type( ) ) { + + case E_SRDTokenType::INT: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::STRING: + { + bool ok; + value = token.stringValue( ).toInteger( &ok ); + if ( !ok ) { + data.addError( token , "invalid integer value" ); + continue; + } + break; + } + + case E_SRDTokenType::LONG: + value = token.longValue( ); + break; + + case E_SRDTokenType::FLOAT: + value = token.floatValue( ); + break; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::AutoInteger( value ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastReal ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + double value; + switch ( token.type( ) ) { + + case E_SRDTokenType::FLOAT: + output.add( std::move( token ) ); + continue; + + case E_SRDTokenType::STRING: + { + bool ok; + value = token.stringValue( ).toDouble( &ok ); + if ( !ok ) { + data.addError( token , "invalid real value" ); + continue; + } + break; + } + + case E_SRDTokenType::INT: + case E_SRDTokenType::LONG: + value = token.longValue( ); + break; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::Float( value ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastVar ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + + T_String outString; + switch ( token.type( ) ) { + + case E_SRDTokenType::STRING: + outString = token.stringValue( ); + if ( outString[ 0 ] == '$' ) { + outString = outString.substr( 1 ); + } + if ( !T_SRDToken::IsWord( outString ) || outString[ 0 ] == '-' ) { + data.addError( token , "invalid variable name" ); + continue; + } + break; + + case E_SRDTokenType::WORD: + outString = token.stringValue( ); + if ( outString[ 0 ] == '-' ) { + data.addError( token , "invalid variable name" ); + continue; + } + break; + + case E_SRDTokenType::VAR: + output.add( std::move( token ) ); + continue; + + default: + data.addError( token , "unsupported type conversion" ); + continue; + + } + + T_SRDToken otok( T_SRDToken::Variable( std::move( outString ) ) ); + otok.copyLocationOf( token ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( CastList ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + const auto size( input.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto& token( input[ i ] ); + switch ( token.type( ) ) { + + case E_SRDTokenType::STRING: + case E_SRDTokenType::WORD: + { + T_StringIterator it( token.stringValue( ) ); + T_StringBuilder sb; + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + sb << c; + T_SRDToken ctok( T_SRDToken::String( sb ) ); + ctok.copyLocationOf( token ); + output.add( std::move( ctok ) ); + sb.clear( ); + } + break; + } + + case E_SRDTokenType::BINARY: + { + auto const& buffer( token.binary( ) ); + const auto bs( buffer.size( ) ); + for ( auto i = 0u ; i < bs ; i ++ ) { + T_SRDToken vtok( T_SRDToken::Integer( buffer[ i ] ) ); + vtok.copyLocationOf( token ); + output.add( std::move( vtok ) ); + } + break; + } + + case E_SRDTokenType::LIST: + { + auto const& l( token.list( ) ); + const auto ls( l.size( ) ); + for ( auto i = 0u ; i < ls ; i ++ ) { + output.add( std::move( l[ i ] ) ); + } + break; + } + + default: + output.add( std::move( token ) ); + continue; + + } + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( ClearScope ) +{ + if ( state.output( ).size( ) != 0 ) { + auto& data( state.data( ) ); + data.scopes( ).enter( ); + data.scopes( ).clear( ); + data.push( T_SRDPreprocessorState::C_INNER , + state.outputPointer( ) , data.top( ).outputPointer( ) , + []( T_SRDPreprocessorState & ns ) { + ns.data( ).scopes( ).exit( ); + } ); + } else { + state.data( ).scopes( ).clear( ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Cmp ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( success ? result : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpEq ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result == 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpNe ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result != 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpLt ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result < 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpGt ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result > 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpLe ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result <= 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +M_SRDPP_COMMAND_EXEC( CmpGe ) +{ + int result; + bool success( CompareInput_( state , result ) ); + T_SRDToken token( T_SRDToken::Integer( ( success && result >= 0 ) ? 1 : 0 ) ); + token.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( token ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Concat ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + auto const& loc( *state.initialLocation( ) ); + + const auto size( input.size( ) ); + if ( size == 0 ) { + data.addError( loc , "not enough arguments" ); + T_SRDToken otok( T_SRDToken::String( T_String( "" ) ) ); + otok.location( loc ); + output.add( std::move( otok ) ); + return; + } + + // Find out whether this is a string concatenation or a binary + // concatenation. + uint32_t binLength = 0; + bool isBinary = true; + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto const& token( input[ i ] ); + if ( token.type( ) != E_SRDTokenType::BINARY ) { + isBinary = false; + break; + } + binLength += token.binary( ).size( ); + } + + // Binary concat + if ( isBinary ) { + auto ob( NewShared< T_Buffer< uint8_t > >( binLength ) ); + binLength = 0; + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto const& bin( input[ i ].binary( ) ); + memcpy( ob->data( ) + binLength , + bin.data( ) , bin.size( ) ); + binLength += bin.size( ); + } + T_SRDToken otok( T_SRDToken::Binary( ob ) ); + otok.location( loc ); + output.add( std::move( otok ) ); + return; + } + + // String concat + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < size ; i ++ ) { + auto const& token( input[ i ] ); + if ( !( token.isText( ) || token.isNumeric( ) ) ) { + data.addError( token , "expected text or numeric argument" ); + continue; + } + + switch ( token.type( ) ) { + + case E_SRDTokenType::STRING: + case E_SRDTokenType::WORD: + sb << token.stringValue( ); + continue; + + case E_SRDTokenType::INT: + case E_SRDTokenType::LONG: + sb << token.longValue( ); + break; + + case E_SRDTokenType::FLOAT: + sb << token.floatValue( ); + break; + + default: + assert( 0 && "we shouldn't be here" ); + continue; + + } + } + + T_SRDToken otok( T_SRDToken::String( std::move( sb ) ) ); + otok.location( loc ); + output.add( std::move( otok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Div ) +{ + DoMathOp_( state , + []( auto a , auto b ) { + return a / b; + } , + []( auto a , auto b ) { + return a / b; + } , true ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( EndsWith ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + uint32_t value = 0; + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) > 2 ) { + data.addError( loc , "too many arguments" ); + } else { + if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) { + value = input[ 1 ].stringValue( ).endsWith( + input[ 0 ].stringValue( ) ); + } else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY + && input[ 1 ].type( ) == E_SRDTokenType::BINARY ) { + auto const& needle( input[ 0 ].binary( ) ); + auto const& haystack( input[ 1 ].binary( ) ); + auto const nsz( needle.size( ) ); + auto const hsz( haystack.size( ) ); + value = ( nsz <= hsz + && !( nsz && memcmp( needle.data( ) , + haystack.data( ) + hsz - nsz , + nsz ) ) ); + } else { + data.addError( loc , "invalid arguments" ); + } + } + + auto tok( T_SRDToken::Integer( value ) ); + tok.location( loc ); + data.top( ).output( ).add( std::move( tok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Eval ) +{ + if ( state.output( ).size( ) == 0 ) { + return; + } + auto& data( state.data( ) ); +// data.push( T_SRDPreprocessorState::C_CALL , state.outputPointer( ) , +// data.top( ).outputPointer( ) ); + auto& input( state.output( ) ); + const auto is( input.size( ) ); + if ( is ) { + const SP_SRDLocation evalLocation( state.createChainedLocation( ) ); + SP_SRDList updated( NewShared< T_SRDList >( is ) ); + for ( size_t i = 0 ; i < is ; i ++ ) { + T_SRDToken token( std::move( input[ i ] ) ); + + auto& loc( token.location( ) ); + SP_SRDLocation el( ( i == is - 1 ) + ? evalLocation + : NewShared< T_SRDLocation >( *evalLocation ) ); + if ( loc.isChained( ) ) { + auto const& lc( loc.chaining( ) ); + el->chain( lc.circumstances , lc.location ); + } + loc.chain( E_SRDLocationChaining::EVALUATED , el ); + + updated->add( std::move( token ) ); + } + + data.push( T_SRDPreprocessorState::C_UNSPECIFIED , updated , + data.top( ).outputPointer( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Error ) +{ + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + auto& input( state.output( ) ); + const auto nTokens( input.size( ) ); + if ( nTokens == 0 ) { + data.addError( loc , "user error" ); + } else { + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < nTokens ; i ++ ) { + if ( i > 0 ) { + sb << ' '; + } + auto& tok( input[ i ] ); + switch( tok.type( ) ) { + + case E_SRDTokenType::VAR: + sb << '$'; + case E_SRDTokenType::STRING: + case E_SRDTokenType::WORD: + sb << tok.stringValue( ); + break; + + case E_SRDTokenType::INT: + case E_SRDTokenType::LONG: + sb << tok.longValue( ); + break; + + case E_SRDTokenType::FLOAT: + sb << tok.floatValue( ); + break; + + case E_SRDTokenType::LIST: + tok.generateFullText( ); + sb << tok.fullText( ); + break; + + default: + sb << "(INTERNAL ERROR)"; + break; + } + } + data.addError( loc , sb ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( FromSource ) +{ + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + auto& input( state.output( ) ); + const auto is( input.size( ) ); + if ( is == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( is > 2 ) { + data.addError( loc , "too many arguments" ); + } + + if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) { + data.addError( input[ 0 ] , "string expected" ); + return; + } + if ( is == 2 && !input[ 1 ].isText( ) ) { + data.addError( input[ 1 ] , "word or string expected" ); + } + + const T_String inputName( ( is == 2 && input[ 1 ].isText( ) ) + ? input[ 1 ].stringValue( ) + : T_String::Pooled( "(-from-source)" ) ); + T_SRDErrors& errors( data.errorContext( ) ); + + T_SRDMemoryTarget tgt( true ); + T_SRDLexer lexer( inputName , errors , tgt ); + tgt.start( errors ); + + T_StringIterator it( input[ 0 ].stringValue( ) ); + while ( !it.atEnd( ) ) { + lexer.processCharacter( it ); + it.next( ); + } + lexer.processEnd( ); + + auto const& generated( tgt.list( ) ); + auto& output( data.top( ).output( ) ); + const auto gs( generated.size( ) ); + const SP_SRDLocation gloc( state.createChainedLocation( ) ); + for ( size_t i = 0 ; i < gs ; i ++ ) { + T_SRDToken t( generated[ i ] ); + t.location( ).chain( E_SRDLocationChaining::GENERATED , gloc ); + output.add( std::move( t ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( FromSRB ) +{ + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + auto& input( state.output( ) ); + const auto is( input.size( ) ); + if ( is == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( is > 2 ) { + data.addError( loc , "too many arguments" ); + } + + if ( input[ 0 ].type( ) != E_SRDTokenType::BINARY ) { + data.addError( input[ 0 ] , "binary array expected" ); + return; + } + if ( is == 2 && !input[ 1 ].isText( ) ) { + data.addError( input[ 1 ] , "word or string expected" ); + } + + const T_String inputName( ( is == 2 && input[ 1 ].isText( ) ) + ? input[ 1 ].stringValue( ) + : T_String::Pooled( "(-from-srb)" ) ); + T_SRDErrors& errors( data.errorContext( ) ); + + T_SRDMemoryTarget tgt( true ); + T_SRDBinaryReader reader( tgt ); + T_MemoryInputStream istream( input[ 0 ].binary( ) ); + try { + reader.read( inputName , istream ); + } catch ( X_SRDErrors const& srbErrors ) { + errors.addAll( srbErrors.errors ); + } + data.top( ).output( ).addAll( tgt.list( ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Get ) +{ + auto& data( state.data( ) ); + auto& input( state.output( ) ); + const auto sz( input.size( ) ); + auto& output( state.data( ).top( ).output( ) ); + + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + auto const& vtok( input[ i ] ); + if ( vtok.type( ) != E_SRDTokenType::WORD ) { + data.addError( vtok , "word expected" ); + continue; + } + + auto var( data.scopes( ).get( false , vtok.stringValue( ) ) ); + if ( var == nullptr ) { + data.addError( vtok , "unknown variable" ); + } else { + output.addAll( *var ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( If ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + const auto sz( input.size( ) ); + + if ( sz < 2 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( sz > 3 ) { + data.addError( loc , "too many arguments" ); + return; + } + + auto const& cdTok( input[ 0 ] ); + if ( cdTok.type( ) != E_SRDTokenType::INT && cdTok.type( ) != E_SRDTokenType::LONG ) { + data.addError( cdTok , "integer expected" ); + return; + } + if ( input[ 1 ].type( ) != E_SRDTokenType::LIST ) { + data.addError( input[ 1 ] , "list expected" ); + return; + } + if ( sz == 3 && input[ 2 ].type( ) != E_SRDTokenType::LIST ) { + data.addError( input[ 2 ] , "list expected" ); + return; + } + + const bool cond( cdTok.longValue( ) != 0 ); + if ( sz == 2 && !cond ) { + return; + } + data.push( T_SRDPreprocessorState::C_INNER , + NewShared< T_SRDList >( input[ cond ? 1 : 2 ].list( ) ) , + data.top( ).outputPointer( ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Ignore ) +{ + (void) state; +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( IsBlessed ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + bool blessed; + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + blessed = false; + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + blessed = false; + } else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) { + data.addError( input[ 0 ] , "word expected" ); + blessed = false; + } else { + blessed = data.scopes( ).isBlessed( input[ 0 ].stringValue( ) ); + } + T_SRDToken output( T_SRDToken::Integer( blessed ? 1 : 0 ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( IsMacro ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + bool set; + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + set = false; + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + set = false; + } else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) { + data.addError( input[ 0 ] , "word expected" ); + set = false; + } else { + set = ( data.scopes( ).get( true , input[ 0 ].stringValue( ) ) != nullptr ); + } + T_SRDToken output( T_SRDToken::Integer( set ? 1 : 0 ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( IsSet ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + bool set; + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + set = false; + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + set = false; + } else if ( input[ 0 ].type( ) != E_SRDTokenType::WORD ) { + data.addError( input[ 0 ] , "word expected" ); + set = false; + } else { + set = ( data.scopes( ).get( false , input[ 0 ].stringValue( ) ) != nullptr ); + } + T_SRDToken output( T_SRDToken::Integer( set ? 1 : 0 ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Length ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + int64_t length; + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + length = -1; + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + length = -1; + } else if ( input[ 0 ].type( ) == E_SRDTokenType::LIST ) { + length = input[ 0 ].list( ).size( ); + } else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY ) { + length = input[ 0 ].binary( ).size( ); + } else if ( input[ 0 ].isText( ) ) { + length = input[ 0 ].stringValue( ).length( ); + } else { + data.addError( input[ 0 ] , "list, word, string or binary array expected" ); + length = -1; + } + + T_SRDToken output( T_SRDToken::AutoInteger( length ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( ListMacros ) +{ + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + if ( state.output( ).size( ) != 0 ) { + data.addError( loc , "too many arguments" ); + } + + auto & output( data.top( ).output( ) ); + T_Array< T_String > macroNames( data.scopes( ).list( true ) ); + const auto nMacros( macroNames.size( ) ); + for ( uint32_t i = 0 ; i < nMacros ; i ++ ) { + T_SRDToken tok( T_SRDToken::Word( macroNames[ i ] ) ); + tok.location( loc ); + output.add( std::move( tok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( ListVariables ) +{ + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + if ( state.output( ).size( ) != 0 ) { + data.addError( loc , "too many arguments" ); + } + + auto & output( data.top( ).output( ) ); + T_Array< T_String > varNames( data.scopes( ).list( false ) ); + const auto nVars( varNames.size( ) ); + for ( uint32_t i = 0 ; i < nVars ; i ++ ) { + T_SRDToken tok( T_SRDToken::Word( varNames[ i ] ) ); + tok.location( loc ); + output.add( std::move( tok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Mod ) +{ + DoMathOp_( state , + []( auto a , auto b ) { + return a % b; + } , + []( auto a , auto b ) { + return std::fmod( a , b ); + } , true ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Mul ) +{ + DoMathOp_( state , + []( auto a , auto b ) { + return a * b; + } , + []( auto a , auto b ) { + return a * b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Neg ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + bool of( false ); + int64_t iv( 0 ); + double fv( 0 ); + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) != 1 ) { + data.addError( loc , "too many arguments" ); + } else if ( !input[ 0 ].isNumeric( ) ) { + data.addError( input[ 0 ] , "numeric argument expected" ); + } else if ( input[ 0 ].isInteger( ) ) { + iv = -input[ 0 ].longValue( ); + } else { + fv = -input[ 0 ].floatValue( ); + of = true; + } + + T_SRDToken output( of ? T_SRDToken::Float( fv ) : T_SRDToken::AutoInteger( iv ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Not ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + int64_t iv( 0 ); + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) != 1 ) { + data.addError( loc , "too many arguments" ); + } else if ( !input[ 0 ].isInteger( ) ) { + data.addError( input[ 0 ] , "integer argument expected" ); + } else { + iv = input[ 0 ].longValue( ) ? 0 : 1; + } + + T_SRDToken output( T_SRDToken::Integer( iv ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Or ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return ( a || b ) ? 1 : 0; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Output ) +{ + auto& out( state.output( ) ); + const auto n( out.size( ) ); + + if ( n ) { + auto& data( state.data( ) ); + auto& pp( data.preprocessor( ) ); + auto& ec( data.errorContext( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + pp.sendOutput( ec , out[ i ] ); + } + pp.target( ).push( ec , T_SRDToken::Flush( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Raw ) +{ + state.data( ).top( ).output( ).addAll( state.output( ) ); +} + +/*----------------------------------------------------------------------------*/ + +namespace { + +void RethrowError_( T_SRDPreprocessorData& data , T_SRDList const& error ) +{ + auto const& esTok( error[ 0 ] ); + if ( esTok.type( ) != E_SRDTokenType::STRING ) { + data.addError( esTok , "string expected" ); + return; + } + + const auto elSize( error.size( ) ); + SP_SRDLocation location; + for ( uint32_t i = elSize - 1 ; i > 0 ; i -- ) { + auto const& locEntry( error[ i ] ); + if ( locEntry.type( ) != E_SRDTokenType::LIST ) { + data.addError( locEntry , "list expected" ); + return; + } + + auto const& locList( locEntry.list( ) ); + const bool hasChaining( i != elSize - 1 ); + const uint32_t expectedLength( hasChaining ? 4 : 3 ); + if ( locList.size( ) != expectedLength ) { + data.addError( locEntry , "invalid location entry" ); + return; + } + + auto const& locName( locList[ 0 ] ); + if ( locName.type( ) != E_SRDTokenType::STRING ) { + data.addError( locName , "string expected" ); + return; + } + auto const& locLine( locList[ 1 ] ); + if ( !locLine.isInteger( ) ) { + data.addError( locLine , "integer expected" ); + return; + } + const int64_t line( locLine.longValue( ) ); + if ( line < 0 || line > UINT32_MAX ) { + data.addError( locLine , "invalid line number" ); + return; + } + + auto const& locChar( locList[ 2 ] ); + if ( !locChar.isInteger( ) ) { + data.addError( locChar , "integer expected" ); + return; + } + const int64_t character( locChar.longValue( ) ); + if ( character < 0 || character > UINT32_MAX ) { + data.addError( locChar , "invalid character/byte number" ); + return; + } + + E_SRDLocationChaining chaining; + if ( hasChaining ) { + if ( locList[ 3 ].type( ) != E_SRDTokenType::WORD ) { + data.addError( locList[ 3 ] , "word expected" ); + return; + } + T_String const& chainWord( locList[ 3 ].stringValue( ) ); + if ( chainWord == "generated" ) { + chaining = E_SRDLocationChaining::GENERATED; + } else if ( chainWord == "included" ) { + chaining = E_SRDLocationChaining::INCLUDED; + } else if ( chainWord == "loaded" ) { + chaining = E_SRDLocationChaining::LOADED; + } else if ( chainWord == "called" ) { + chaining = E_SRDLocationChaining::CALLED; + } else if ( chainWord == "evaluated" ) { + chaining = E_SRDLocationChaining::EVALUATED; + } else if ( chainWord == "expanded" ) { + chaining = E_SRDLocationChaining::EXPANDED; + } else if ( chainWord == "substituted" ) { + chaining = E_SRDLocationChaining::SUBSTITUTED; + } else { + data.addError( locList[ 3 ] , "invalid chaining" ); + return; + } + } else { + // Compiler stfu + chaining = E_SRDLocationChaining( 0 ); + } + + T_String const& src( locName.stringValue( ) ); + SP_SRDLocation nLocation( line > 0 + ? NewShared< T_SRDLocation >( src , line , character ) + : NewShared< T_SRDLocation >( src , character ) ); + if ( hasChaining ) { + nLocation->chain( chaining , location ); + } + location = nLocation; + } + + data.addError( *location , esTok.stringValue( ) ); +} + +void DoRethrow_( T_SRDPreprocessorData& data , T_SRDList const& errors ) +{ + const auto nErrors( errors.size( ) ); + for ( uint32_t i = 0 ; i < nErrors ; i ++ ) { + auto const& errTok( errors[ i ] ); + if ( errTok.type( ) != E_SRDTokenType::LIST ) { + data.addError( errTok , "list expected" ); + continue; + } + if ( errTok.list( ).size( ) < 2 ) { + data.addError( errTok , "invalid error record" ); + continue; + } + RethrowError_( data , errTok.list( ) ); + } +} + +} + +M_SRDPP_COMMAND_EXEC( Rethrow ) +{ + auto const& input( state.output( ) ); + if ( input.size( ) == 0 ) { + return; + } + if ( input.size( ) == 1 && input[ 0 ].type( ) == E_SRDTokenType::LIST ) { + auto const& innerList( input[ 0 ].list( ) ); + if ( innerList.size( ) == 0 ) { + return; + } + if ( innerList[ 0 ].type( ) == E_SRDTokenType::LIST ) { + DoRethrow_( state.data( ) , innerList ); + return; + } + } + DoRethrow_( state.data( ) , input ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Scope ) +{ + if ( state.output( ).size( ) == 0 ) { + return; + } + auto& data( state.data( ) ); + data.scopes( ).enter( ); + data.push( T_SRDPreprocessorState::C_INNER , + state.outputPointer( ) , data.top( ).outputPointer( ) , + []( T_SRDPreprocessorState & ns ) { + ns.data( ).scopes( ).exit( ); + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Set ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + + auto const& names( input[ 0 ] ); + auto& s( data.scopes( ) ); + if ( names.type( ) == E_SRDTokenType::WORD ) { + auto const& vn( names.stringValue( ) ); + if ( !s.canSet( false , vn ) ) { + data.addError( names , "duplicate variable name" ); + return; + } + s.set( false , vn , input , 1 ); + + } else if ( names.type( ) == E_SRDTokenType::LIST ) { + auto const& list( names.list( ) ); + const auto n( list.size( ) ); + if ( n == 0 ) { + data.addError( names , "non-empty list expected" ); + return; + } + + bool ok( true ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& name( list[ i ] ); + if ( name.type( ) != E_SRDTokenType::WORD ) { + data.addError( name , "word expected" ); + ok = false; + continue; + } + + auto const& vn( name.stringValue( ) ); + for ( uint32_t j = 0 ; j < i ; j ++ ) { + if ( list[ j ].type( ) != E_SRDTokenType::WORD ) { + continue; + } + if ( list[ j ].stringValue( ) == vn ) { + data.addError( name , "duplicate variable name" ); + ok = false; + goto next; + } + } + if ( !s.canSet( false , vn ) ) { + data.addError( name , "duplicate variable name" ); + ok = false; + } + +next: ; + } + + if ( !ok ) { + return; + } + for ( uint32_t i = 0 ; i + 1 < n ; i ++ ) { + s.set( false , list[ i ].stringValue( ) , input , 1 + i , 1 + i ); + } + s.set( false , list[ n - 1 ].stringValue( ) , input , n ); + + } else { + data.addError( names , "word or list expected" ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( SetMacro ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( input.size( ) > 2 ) { + data.addError( loc , "too many arguments" ); + return; + } + + auto const& name( input[ 0 ] ); + if ( name.type( ) != E_SRDTokenType::WORD ) { + data.addError( name , "word expected" ); + return; + } + auto const& mn( name.stringValue( ) ); + auto& s( data.scopes( ) ); + if ( !s.canSet( true , mn ) ) { + data.addError( name , "duplicate macro name" ); + return; + } + + auto const& value( input[ 1 ] ); + if ( !T_SRDPreprocessor::isValidFunction( value ) ) { + data.addError( value , "invalid macro body" ); + return; + } + + s.set( true , mn , value.list( ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( StartsWith ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + uint32_t value = 0; + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) > 2 ) { + data.addError( loc , "too many arguments" ); + } else { + if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) { + value = input[ 1 ].stringValue( ).startsWith( + input[ 0 ].stringValue( ) ); + } else if ( input[ 0 ].type( ) == E_SRDTokenType::BINARY + && input[ 1 ].type( ) == E_SRDTokenType::BINARY ) { + auto const& needle( input[ 0 ].binary( ) ); + auto const& haystack( input[ 1 ].binary( ) ); + auto const nsz( needle.size( ) ); + auto const hsz( haystack.size( ) ); + value = ( nsz <= hsz + && !( nsz && memcmp( needle.data( ) , + haystack.data( ) , nsz ) ) ); + } else { + data.addError( loc , "invalid arguments" ); + } + } + + auto tok( T_SRDToken::Integer( value ) ); + tok.location( loc ); + data.top( ).output( ).add( std::move( tok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( StrFind ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + int32_t value = -1; + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) > 2 ) { + data.addError( loc , "too many arguments" ); + } else { + if ( !input[ 0 ].isText( ) ) { + data.addError( input[ 0 ] , "text expected" ); + } + if ( !input[ 1 ].isText( ) ) { + data.addError( input[ 1 ] , "text expected" ); + } + if ( input[ 0 ].isText( ) && input[ 1 ].isText( ) ) { + value = input[ 1 ].stringValue( ).find( + input[ 0 ].stringValue( ) ); + } + } + + auto tok( T_SRDToken::Integer( value ) ); + tok.location( loc ); + data.top( ).output( ).add( std::move( tok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( StrSplit ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + auto& output( data.top( ).output( ) ); + + if ( input.size( ) < 2 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( input.size( ) > 3 ) { + data.addError( loc , "too many arguments" ); + return; + } + + bool argsOk = true; + if ( !input[ 0 ].isText( ) ) { + data.addError( input[ 0 ] , "text expected" ); + argsOk = false; + } else if ( !input[ 0 ].stringValue( ) ) { + data.addError( input[ 0 ] , "invalid argument value" ); + argsOk = false; + } + if ( !input[ 1 ].isText( ) ) { + data.addError( input[ 1 ] , "text expected" ); + argsOk = false; + } + if ( input.size( ) == 3 + && input[ 2 ].type( ) != E_SRDTokenType::INT + && input[ 2 ].type( ) != E_SRDTokenType::LONG ) { + data.addError( input[ 2 ] , "integer expected" ); + argsOk = false; + } + if ( !argsOk ) { + return; + } + uint32_t maxOutput; + if ( input.size( ) == 3 ) { + int64_t n( input[ 2 ].longValue( ) ); + if ( n < 0 || n > UINT32_MAX ) { + data.addError( input[ 2 ] , "invalid argument value" ); + maxOutput = UINT32_MAX; + } else { + maxOutput = n; + } + } else { + maxOutput = UINT32_MAX; + } + + int32_t nextSearch( 0 ); + auto const& needle( input[ 0 ].stringValue( ) ); + auto const& haystack( input[ 1 ].stringValue( ) ); + while ( nextSearch != -1 && maxOutput != 0 ) { + const int32_t sepIndex( haystack.find( needle , nextSearch ) ); + + const T_String os( sepIndex == -1 + ? haystack.substr( nextSearch ) + : ( sepIndex == 0 + ? T_String( ) + : haystack.range( nextSearch , sepIndex - 1 ) ) ); + T_SRDToken otok( T_SRDToken::String( os ) ); + otok.location( loc ); + output.add( std::move( otok ) ); + + if ( sepIndex == -1 ) { + nextSearch = -1; + } else { + nextSearch = sepIndex + needle.length( ); + } + maxOutput --; + } + + if ( maxOutput == 0 && nextSearch != -1 ) { + T_SRDToken otok( T_SRDToken::String( haystack.substr( nextSearch ) ) ); + otok.location( loc ); + output.add( std::move( otok ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Sub ) +{ + DoMathOp_( state , + []( auto a , auto b ) { + return a - b; + } , + []( auto a , auto b ) { + return a - b; + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Substr ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + const auto is( input.size( ) ); + if ( is < 2 ) { + data.addError( loc , "not enough arguments" ); + } else if ( is > 4 ) { + data.addError( loc , "too many arguments" ); + } + if ( is == 0 ) { + T_SRDToken otok( T_SRDToken::String( T_String( ) ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); + return; + } + + const bool isBinary( input[ 0 ].type( ) == E_SRDTokenType::BINARY ); + if ( is < 2 || is > 4 ) { + T_SRDToken otok( isBinary + ? T_SRDToken::Binary( (uint8_t const*) nullptr , 0 ) + : T_SRDToken::String( T_String( ) ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); + return; + } + + const bool isRange = ( is == 4 ); + bool argsOk = true; + uint32_t start( 0 ); + uint32_t end( UINT32_MAX ); + + if ( !( input[ 0 ].isText( ) || isBinary ) ) { + data.addError( input[ 0 ] , "binary or text expected" ); + argsOk = false; + } + + if ( isRange && ( input[ 2 ].type( ) != E_SRDTokenType::WORD + || input[ 2 ].stringValue( ) != "to" ) ) { + data.addError( input[ 2 ] , "'to' expected" ); + argsOk = false; + } + + if ( !input[ 1 ].isNumeric( ) || input[ 1 ].type( ) == E_SRDTokenType::FLOAT ) { + data.addError( input[ 1 ] , "integer expected" ); + argsOk = false; + } else { + const int64_t v( input[ 1 ].longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + data.addError( input[ 1 ] , "invalid argument value" ); + argsOk = false; + } else { + start = v; + } + } + + if ( is > 2 ) { + auto const& next( input[ isRange ? 3 : 2 ] ); + if ( !next.isNumeric( ) || next.type( ) == E_SRDTokenType::FLOAT ) { + data.addError( next , "integer expected" ); + argsOk = false; + } else { + const int64_t v( next.longValue( ) ); + if ( v < 0 || v > UINT32_MAX ) { + data.addError( next , "invalid argument value" ); + argsOk = false; + } else { + end = v; + } + } + } + + if ( !argsOk ) { + T_SRDToken otok( isBinary + ? T_SRDToken::Binary( (uint8_t const*) nullptr , 0 ) + : T_SRDToken::String( T_String( ) ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); + return; + } + + if ( !isBinary ) { + auto const& str( input[ 0 ].stringValue( ) ); + T_SRDToken otok( T_SRDToken::String( isRange + ? str.range( start , end ) + : str.substr( start , end ) ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); + return; + } + + const uint32_t oriLen( input[ 0 ].binary( ).size( ) ); + const uint32_t realEnd( std::min( oriLen , end ) ); + const uint32_t lengthMax( isRange + ? ( realEnd < start ? 0 : realEnd - start + 1 ) + : realEnd ); + const uint32_t length( start >= oriLen + ? 0 : ( std::min( oriLen , start + lengthMax ) - start ) ); + auto buffer( NewShared< T_Buffer< uint8_t > >( length ) ); + if ( length ) { + memcpy( buffer->data( ) , input[ 0 ].binary( ).data( ) , length ); + } + T_SRDToken otok( T_SRDToken::Binary( buffer ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( ToSource ) +{ + auto& input( state.output( ) ); + const auto n( input.size( ) ); + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + if ( i > 0 ) { + sb << ' '; + } + + auto& token( input[ i ] ); + token.generateFullText( ); + sb << token.fullText( ); + } + + T_SRDToken otok( T_SRDToken::String( std::move( sb ) ) ); + otok.location( *state.initialLocation( ) ); + state.data( ).top( ).output( ).add( std::move( otok ) ); +} + +/*----------------------------------------------------------------------------*/ + +namespace { + +void ErrorsToList_( T_SRDLocation const& location , T_SRDErrors const& errors , T_SRDList & output ) +{ + const auto nErrors( errors.size( ) ); + for ( uint32_t i = 0 ; i < nErrors ; i ++ ) { + auto const& error( errors[ i ] ); + auto& errToken( output[ output.add( T_SRDToken::List( ) ) ] ); + auto& etl( errToken.list( ) ); + errToken.location( location ); + + { + T_SRDToken msg( T_SRDToken::String( error.error( ) ) ); + msg.location( location ); + etl.add( std::move( msg ) ); + } + + RPC_SRDLocation errLoc( &error.location( ) ); + RPC_SRDLocationChaining chaining( nullptr ); + while ( errLoc != nullptr ) { + auto& locToken( etl[ etl.add( T_SRDToken::List( ) ) ] ); + auto& ll( locToken.list( ) ); + locToken.location( location ); + + { + T_SRDToken src( T_SRDToken::String( errLoc->source( ) ) ); + src.location( location ); + ll.add( std::move( src ) ); + }{ + T_SRDToken ln( T_SRDToken::Integer( errLoc->line( ) ) ); + ln.location( location ); + ll.add( std::move( ln ) ); + }{ + T_SRDToken cn( T_SRDToken::Integer( errLoc->character( ) ) ); + cn.location( location ); + ll.add( std::move( cn ) ); + } + if ( chaining ) { + T_String chain( T_String::Pooled( ( []( RPC_SRDLocationChaining c ) -> char const* { + switch ( c->circumstances ) { + case E_SRDLocationChaining::INCLUDED: + return "included"; + case E_SRDLocationChaining::LOADED: + return "loaded"; + case E_SRDLocationChaining::CALLED: + return "called"; + case E_SRDLocationChaining::GENERATED: + return "generated"; + case E_SRDLocationChaining::SUBSTITUTED: + return "substituted"; + case E_SRDLocationChaining::EVALUATED: + return "evaluated"; + case E_SRDLocationChaining::EXPANDED: + return "expanded"; + } + return "unknown"; + } )( chaining ) ) ); + + T_SRDToken ctok( T_SRDToken::Word( std::move( chain ) ) ); + ctok.location( location ); + ll.add( std::move( ctok ) ); + } + + if ( errLoc->isChained( ) ) { + chaining = &errLoc->chaining( ); + errLoc = RPC_SRDLocation( chaining->location ); + } else { + errLoc = nullptr; + } + } + } +} + +} + +M_SRDPP_COMMAND_EXEC( Try ) +{ + auto& data( state.data( ) ); + auto& output( data.top( ).output( ) ); + + T_SRDToken otok( T_SRDToken::List( ) ); + otok.location( *state.initialLocation( ) ); + if ( state.output( ).size( ) == 0 ) { + // No input -> empty list + output.add( std::move( otok ) ); + return; + } + + const SP_SRDList fullOutput( NewShared< T_SRDList >( 16 ) ); + fullOutput->add( std::move( otok ) ); + + data.pushErrorContext( ); + data.push( T_SRDPreprocessorState::C_TRY , state.outputPointer( ) , fullOutput , + []( T_SRDPreprocessorState & ns ) { + const RP_SRDErrors errors( ns.data( ).popErrorContext( ) ); + auto& errTok( ns.output( )[ 0 ] ); + ErrorsToList_( errTok.location( ) , *errors , errTok.list( ) ); + delete errors; + ns.data( ).top( ).output( ).addAll( std::move( ns.output( ) ) ); + } ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( TypeOf ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + char const* type; + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + type = "none"; + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + type = "none"; + } else { + switch ( input[ 0 ].type( ) ) { + case E_SRDTokenType::LIST: + type = "list"; + break; + + case E_SRDTokenType::WORD: + type = "word"; + break; + + case E_SRDTokenType::STRING: + type = "string"; + break; + + case E_SRDTokenType::INT: + type = "int"; + break; + + case E_SRDTokenType::LONG: + type = "long"; + break; + + case E_SRDTokenType::FLOAT: + type = "real"; + break; + + case E_SRDTokenType::VAR: + type = "var"; + break; + + case E_SRDTokenType::BINARY: + type = "binary"; + break; + + case E_SRDTokenType::COMMENT: + type = "comment"; + break; + + default: + type = "none"; + data.addError( input[ 0 ] , "internal error" ); + break; + } + } + + T_SRDToken output( T_SRDToken::Word( T_String::Pooled( type ) ) ); + output.location( loc ); + state.data( ).top( ).output( ).add( std::move( output ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Unset ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + + const uint32_t n( input.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& name( input[ i ] ); + if ( name.type( ) != E_SRDTokenType::WORD ) { + data.addError( name , "word expected" ); + } else { + data.scopes( ).unset( false , name.stringValue( ) ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( UnsetMacro ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + + const uint32_t n( input.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& name( input[ i ] ); + if ( name.type( ) != E_SRDTokenType::WORD ) { + data.addError( name , "word expected" ); + } else { + data.scopes( ).unset( true , name.stringValue( ) ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Unwrap ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + } else if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + } else if ( input[ 0 ].type( ) != E_SRDTokenType::LIST ) { + data.addError( input[ 0 ] , "list expected" ); + } else { + data.top( ).output( ).addAll( std::move( input[ 0 ].list( ) ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( Xor ) +{ + DoLogicOp_( state , + []( auto a , auto b ) { + return ( a || b ) && !( a && b ); + } ); +} + + +/*= VFS COMMANDS =============================================================*/ + +M_SRDPP_COMMAND_EXEC( VFSList ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + return; + } + if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) { + data.addError( input[ 0 ] , "string expected" ); + return; + } + + const T_VFSPath path( input[ 0 ].stringValue( ) ); + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + data.addError( input[ 0 ] , "absolute VFS path expected" ); + return; + } + + T_Array< T_VFSPath > list( 32 ); + if ( !vfs_.list( path , list ) ) { + return; + } + + const uint32_t n( list.size( ) ); + T_SRDList output( std::max( 1u , n ) ); + output.ensureCapacity( n ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + output.add( T_SRDToken::String( T_String( list[ i ] ) ) ); + } + + auto otok( T_SRDToken::List( std::move( output ) ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( VFSLoad ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + auto const& loc( *state.initialLocation( ) ); + + if ( input.size( ) == 0 ) { + data.addError( loc , "not enough arguments" ); + return; + } + if ( input.size( ) > 1 ) { + data.addError( loc , "too many arguments" ); + return; + } + if ( input[ 0 ].type( ) != E_SRDTokenType::STRING ) { + data.addError( input[ 0 ] , "string expected" ); + return; + } + + const T_VFSPath path( input[ 0 ].stringValue( ) ); + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + data.addError( input[ 0 ] , "absolute VFS path expected" ); + return; + } + + const OP_InputStream istream( vfs_.read( path ) ); + if ( !istream ) { + T_StringBuilder sb( "error while loading '" ); + sb << T_String( path ) << "': file not found"; + data.addError( input[ 0 ] , std::move( sb ) ); + return; + } + + T_SharedPtr< T_Buffer< uint8_t > > buffer; + try { + if ( istream->isSizeKnown( ) ) { + const auto sz( istream->size( ) ); + buffer = NewShared< T_Buffer< uint8_t > >( sz ); + if ( sz ) { + istream->read( buffer->data( ) , sz ); + } + } else { + uint8_t temp[ 4096 ]; + buffer = NewShared< T_Buffer< uint8_t > >( ); + try { + while ( 1 ) { + const auto nRead( istream->read( temp , sizeof( temp ) ) ); + const auto prevSize( buffer->size( ) ); + assert( nRead > 0 ); + buffer->resize( prevSize + nRead ); + memcpy( buffer->data( ) + prevSize , temp , nRead ); + } + } catch ( X_StreamError const& error ) { + if ( error.code( ) != E_StreamError::END ) { + throw; + } + } + } + } catch ( X_StreamError const& error ) { + T_StringBuilder sb( "error while loading '" ); + sb << T_String( path ) << "': " << error.what( ); + data.addError( input[ 0 ] , std::move( sb ) ); + } + + auto otok( T_SRDToken::Binary( buffer ) ); + otok.location( loc ); + data.top( ).output( ).add( std::move( otok ) ); +} + +/*----------------------------------------------------------------------------*/ + +M_SRDPP_COMMAND_EXEC( VFSType ) +{ + auto& input( state.output( ) ); + auto& data( state.data( ) ); + + const auto is( input.size( ) ); + if ( is == 0 ) { + return; + } + + T_Array< T_VFSPath > paths( 4 ); + paths.ensureCapacity( is ); + for ( uint32_t i = 0 ; i < is ; i ++ ) { + if ( input[ i ].type( ) != E_SRDTokenType::STRING ) { + data.addError( input[ i ] , "string expected" ); + return; + } + + T_VFSPath const& path( paths.addNew( input[ i ].stringValue( ) ) ); + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + data.addError( input[ i ] , "absolute VFS path expected" ); + return; + } + } + + auto& out( data.top( ).output( ) ); + for ( uint32_t i = 0 ; i < is ; i ++ ) { + const E_VFSEntryType et( vfs_.typeOf( paths[ i ] ) ); + T_SRDToken token( ( []( E_VFSEntryType et ) { + switch ( et ) { + case E_VFSEntryType::NONE: + return T_SRDToken::Word( T_String::Pooled( "none" ) ); + + case E_VFSEntryType::FILE: + return T_SRDToken::Word( T_String::Pooled( "file" ) ); + + case E_VFSEntryType::DIRECTORY: + return T_SRDToken::Word( T_String::Pooled( "directory" ) ); + + case E_VFSEntryType::OTHER: + return T_SRDToken::Word( T_String::Pooled( "other" ) ); + } + return T_SRDToken::Word( T_String::Pooled( "unknown" ) ); + })( et ) ); + token.copyLocationOf( input[ i ] ); + out.add( std::move( token ) ); + } +} diff --git a/src/SRDParser.cc b/src/SRDParser.cc new file mode 100644 index 0000000..2858ba3 --- /dev/null +++ b/src/SRDParser.cc @@ -0,0 +1,686 @@ +/******************************************************************************/ +/* SRD PARSER AND PREPROCESSOR - PARSER ***************************************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +/*= T_SRDParserData ==========================================================*/ + +T_SRDParserData::T_SRDParserData( + T_SRDParserConfig const& config , + T_SRDErrors& errors ) + : config( config ) , errors( errors ) +{ } + + +/*= T_SRDParserPrivate_ ======================================================*/ + +namespace { +struct T_SRDParserPrivate_ +{ + // T_State_ - Internal state information + struct T_State_ + { + // Current context + uint32_t context; + // Are we parsing a rule ? + bool inRule; + // Rule start location + T_SRDLocation ruleStart; + // Current state + uint32_t state; + // Current depth (in-rule) + uint32_t depth; + // Last rule list output + SP_SRDList list; + // List/context ambiguity resolution - initial state ID + uint32_t lcarState = T_SRDParserConfig::INVALID_END; + // List/context ambiguity resolution - tokens to replay + SP_SRDList lcarReplay; + }; + + // E_ESAction_ - Stack actions for the execution + enum class E_ESAction_ + { + NONE , + PUSH , + POP + }; + + // T_Exec_ - Execution list entry + struct T_Exec_ + { + // Handler to call + F_SRDHandler handler; + // Name of the current context + T_String currentContext; + // Data that matched the rule + SP_SRDList input; + // Name of the target context + T_String targetContext; + // Execution data stack action + E_ESAction_ stackAction; + + T_Exec_( F_SRDHandler const& handler , T_String const& currentContext ); + T_Exec_( F_SRDHandler const& handler , T_String const& currentContext , SP_SRDList input ); + T_Exec_( F_SRDHandler const& handler , T_String const& currentContext , SP_SRDList input , + T_String const& targetContext , E_ESAction_ stackAction ); + }; + + // E_Recovery_ - Error recovery mode + enum class E_Recovery_ + { + NONE , + SOL , + EOL , + LIST + }; + + // Parser configuration data + T_SRDParserConfig const& cfg_; + // Current state + T_State_ current_; + // Memory target for accumulation of input + T_SRDMemoryTarget accum_; + // State stack, changed when entering / exiting contexts + T_Array< T_State_ > stack_; + // Current error recovery mode + E_Recovery_ recovery_; + + // Flush mode + E_SRDFlushMode flushMode_; + // Accept flush tokens? + bool handleFlushTokens_; + // Execution list + T_Array< T_Exec_ > exec_; + // Execution stack + T_Array< T_Variant > execStack_; + // Current execution data, if any + OP_SRDParserData execData_; + + // --------------------------------------------------------------------- + + explicit T_SRDParserPrivate_( T_SRDParserConfig const& cfg ); + + // Add a token's description to a string builder + static void addDescription( T_StringBuilder & sb , + T_SRDToken const& token ); + // Flush the list/context ambiguity resolution data to the accumulator + void flushLCARReplay( T_SRDErrors& errors ); + // End the current input list + void endList( T_SRDErrors & errors ); + // Add a match error caused by a specific token + void addMatchError( T_SRDErrors & errors , + T_SRDToken const& token ) const; + // Enter a context + void enterContext( T_SRDErrors& errors , uint32_t endId , + uint32_t ctxId , + T_SRDLocation const& startLocation ); + + // Handle error recovery if necessary + bool handleRecovery( T_SRDToken const& token ); + // Check for rules starting + void checkRuleStart( T_SRDErrors & errors , T_SRDToken const& token ); + // Check for rules ending + bool checkRuleEnd( T_SRDErrors & errors , T_SRDToken const& token , + uint32_t eid ); + + // Get the data at the top of the execution stack + T_Variant const& getExecStackTop( ) const; + + // Public methods + void start( T_SRDErrors & errors ); + void push( T_SRDErrors & errors , T_SRDToken && token ); + void end( T_SRDErrors & errors ); + void flush( ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_SRDParserPrivate_::T_Exec_::T_Exec_( + F_SRDHandler const& handler , + T_String const& currentContext ) + : handler( handler ) , currentContext( currentContext ) , + stackAction( E_ESAction_::NONE ) +{ } + +T_SRDParserPrivate_::T_Exec_::T_Exec_( + F_SRDHandler const& handler , + T_String const& currentContext , + SP_SRDList input ) + : handler( handler ) , currentContext( currentContext ) , + input( input ) , stackAction( E_ESAction_::NONE ) +{ } + +T_SRDParserPrivate_::T_Exec_::T_Exec_( + F_SRDHandler const& handler , + T_String const& currentContext , + SP_SRDList input , + T_String const& targetContext , + E_ESAction_ stackAction ) + : handler( handler ) , currentContext( currentContext ) , + input( input ) , targetContext( targetContext ) , + stackAction( stackAction ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline T_SRDParserPrivate_::T_SRDParserPrivate_( + T_SRDParserConfig const& cfg ) + : cfg_( cfg ) , stack_( 16 ) , flushMode_( E_SRDFlushMode::END ) , + handleFlushTokens_( false ) , execStack_( 16 ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDParserPrivate_::flushLCARReplay( T_SRDErrors& errors ) +{ + auto& list( *current_.lcarReplay ); + const auto size( list.size( ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + accum_.push( errors , std::move( list[ i ] ) ); + } +} + +void T_SRDParserPrivate_::endList( T_SRDErrors& errors ) +{ + if ( current_.lcarReplay ) { + flushLCARReplay( errors ); + current_.lcarReplay = SP_SRDList( ); + } + accum_.end( errors ); + current_.list = NewShared< T_SRDList >( accum_.list( ) ); +} + +void T_SRDParserPrivate_::addDescription( T_StringBuilder& sb , T_SRDToken const& token ) +{ + auto tt( token.type( ) ); + switch ( tt ) { + + case E_SRDTokenType::WORD: + sb << '\'' << token.stringValue( ) << '\''; + break; + + case E_SRDTokenType::START: + sb << "'('"; + break; + + case E_SRDTokenType::END: + sb << "')'"; + break; + + default: + sb << token.type( ) << " token"; + break; + + } +} + +void T_SRDParserPrivate_::addMatchError( T_SRDErrors& errors , T_SRDToken const& token ) const +{ + const auto nTrans( cfg_.transitions.sizeOf( current_.state ) ); + const auto eid( cfg_.endStates[ current_.state ] ); + + T_StringBuilder sb; + if ( nTrans == 0 ) { + assert( eid != T_SRDParserConfig::INVALID_END ); + const auto nctx( cfg_.ruleContexts[ eid ] ); + if ( nctx != T_SRDParserConfig::INVALID_END ) { + sb << "'(' or "; + } + sb << "')'"; + + } else { + if ( eid != T_SRDParserConfig::INVALID_END ) { + const auto nctx( cfg_.ruleContexts[ eid ] ); + if ( nctx != T_SRDParserConfig::INVALID_END ) { + sb << "'(', "; + } + sb << "')'"; + if ( nTrans > 1 ) { + sb << ", "; + } else { + sb << " or "; + } + } + + for ( uint32_t i = 0 ; i < nTrans ; i ++ ) { + auto const& t( cfg_.transitions.get( current_.state , i ) ); + if ( t.type == E_SRDTransitionType::WORD ) { + sb << '\'' << cfg_.words[ t.index ] << '\''; + } else if ( t.type == E_SRDTransitionType::ENUM ) { + sb << '\'' << cfg_.enums[ t.index ].name( ) + << "' enum value"; + } else { + sb << t.tokenType << " token"; + } + if ( i + 2 < nTrans ) { + sb << ", "; + } else if ( i + 1 < nTrans ) { + sb << " or "; + } + } + } + sb << " expected, "; + addDescription( sb , token ); + sb << " found instead."; + errors.add( sb , token ); +} + +void T_SRDParserPrivate_::enterContext( T_SRDErrors& errors , uint32_t endId , uint32_t ctxId , + T_SRDLocation const& startLocation ) +{ + endList( errors ); + + auto const& cctx( cfg_.contexts[ current_.context ] ); + auto const& rule( cctx.rules( )[ endId ] ); + auto const& nctx( cfg_.contexts[ ctxId ] ); + if ( rule.onEnter( ) ) { + const auto action( rule.onExit( ) + ? E_ESAction_::PUSH + : E_ESAction_::NONE ); + exec_.addNew( rule.onEnter( ) , cctx.name( ) , + current_.list , nctx.name( ) , + action ); + } + + stack_.add( std::move( current_ ) ); + + current_.context = ctxId; + current_.inRule = true; + current_.state = cfg_.startStates[ ctxId ]; + current_.depth = 0; + current_.ruleStart = startLocation; + + accum_.start( errors ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDParserPrivate_::handleRecovery( T_SRDToken const& token ) +{ + const auto tt( token.type( ) ); + + switch ( recovery_ ) { + + case E_Recovery_::NONE: + return true; + + case E_Recovery_::SOL: + // Wait for '(', process it + if ( tt == E_SRDTokenType::START ) { + recovery_ = E_Recovery_::NONE; + return true; + } + break; + + case E_Recovery_::EOL: + // Wait for the required ')'. Don't process it. + if ( tt == E_SRDTokenType::START ) { + current_.depth ++; + } else if ( tt == E_SRDTokenType::END ) { + if ( current_.depth == 0 ) { + current_.inRule = false; + recovery_ = E_Recovery_::NONE; + } else { + current_.depth --; + } + } + break; + + case E_Recovery_::LIST: + // Process both '(' and ')' + if ( tt == E_SRDTokenType::START || tt == E_SRDTokenType::END ) { + recovery_ = E_Recovery_::NONE; + return true; + } + break; + + default: + throw std::logic_error( "invalid error recovery state" ); + } + + return false; +} + +void T_SRDParserPrivate_::checkRuleStart( T_SRDErrors& errors , T_SRDToken const& token ) +{ + const auto tt( token.type( ) ); + const auto ss( stack_.size( ) ); + if ( tt == E_SRDTokenType::START ) { + // Entering a rule + current_.inRule = true; + current_.state = cfg_.startStates[ current_.context ]; + current_.depth = 0; + current_.ruleStart = token.location( ); + accum_.start( errors ); + + } else if ( ss != 0 && tt == E_SRDTokenType::END ) { + // Exiting a context + const auto oldCtx( current_.context ); + current_ = std::move( stack_[ ss - 1 ] ); + stack_.remove( ss - 1 ); + + const auto eid( cfg_.endStates[ current_.state ] ); + auto const& cctx( cfg_.contexts[ current_.context ] ); + auto const& rule( cctx.rules( )[ eid ] ); + auto const& octx( cfg_.contexts[ oldCtx ] ); + + if ( rule.onExit( ) ) { + const auto action( rule.onEnter( ) + ? E_ESAction_::POP + : E_ESAction_::NONE ); + exec_.addNew( rule.onExit( ) , octx.name( ) , + current_.list , cctx.name( ) , + action ); + } + if ( rule.handler( ) ) { + exec_.addNew( rule.handler( ) , cctx.name( ) , current_.list ); + } + + if ( flushMode_ == E_SRDFlushMode::SENTENCE + && stack_.size( ) == 0 ) { + flush( ); + } + + current_.inRule = false; + + } else { + T_StringBuilder sb( "'(' " ); + if ( ss == 0 ) { + recovery_ = E_Recovery_::SOL; + } else { + sb << "or ')' "; + recovery_ = E_Recovery_::LIST; + } + sb << "expected, "; + addDescription( sb , token ); + sb << " found instead."; + errors.add( sb , token ); + } +} + +bool T_SRDParserPrivate_::checkRuleEnd( T_SRDErrors& errors , T_SRDToken const& token , uint32_t eid ) +{ + const auto tt( token.type( ) ); + if ( tt == E_SRDTokenType::END ) { + endList( errors ); + + auto const& cctx( cfg_.contexts[ current_.context ] ); + auto const& rule( cctx.rules( )[ eid ] ); + if ( rule.handler( ) ) { + exec_.addNew( rule.handler( ) , cctx.name( ) , current_.list ); + } + + current_.inRule = false; + return true; + } + + if ( tt != E_SRDTokenType::START ) { + return false; + } + + const auto cid( cfg_.ruleContexts.get( current_.context , eid ) ); + if ( cid == T_SRDParserConfig::INVALID_END ) { + return false; + } + enterContext( errors , eid , cid , token.location( ) ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +inline T_Variant const& T_SRDParserPrivate_::getExecStackTop( ) const +{ + assert( execStack_.size( ) != 0 ); + const auto stackLast( execStack_.size( ) - 1 ); + return execStack_[ stackLast ]; +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDParserPrivate_::start( T_SRDErrors& errors ) +{ + execData_ = NewOwned< T_SRDParserData >( cfg_ , errors ); + execStack_.clear( ); + execStack_.addNew( ); + exec_.clear( ); + if ( cfg_.onStart ) { + auto const& dc( cfg_.contexts[ 0 ] ); + exec_.addNew( cfg_.onStart , dc.name( ) ); + if ( flushMode_ == E_SRDFlushMode::SENTENCE ) { + flush( ); + } + } + + stack_.clear( ); + current_.context = 0; + current_.inRule = false; + recovery_ = E_Recovery_::NONE; +} + +inline void T_SRDParserPrivate_::push( T_SRDErrors& errors , T_SRDToken&& token ) +{ + const auto tt( token.type( ) ); + if ( tt == E_SRDTokenType::FLUSH ) { + if ( handleFlushTokens_ ) { + flush( ); + } + return; + } + + if ( tt == E_SRDTokenType::COMMENT || !handleRecovery( token ) ) { + return; + } + + if ( !current_.inRule ) { + checkRuleStart( errors , token ); + return; + } + + const auto prevState( current_.state ); + const auto eid( cfg_.endStates[ prevState ] ); + if ( cfg_.transition( current_.state , token ) ) { + // Handling of list/context ambiguities + if ( tt == E_SRDTokenType::START && eid != T_SRDParserConfig::INVALID_END ) { + assert( current_.depth == 0 ); + const auto cid( cfg_.ruleContexts.get( current_.context , eid ) ); + if ( cid != T_SRDParserConfig::INVALID_END ) { + current_.lcarState = prevState; + if ( current_.lcarReplay ) { + flushLCARReplay( errors ); + current_.lcarReplay->clear( ); + } else { + current_.lcarReplay = NewShared< T_SRDList >( 16 ); + } + } + } + + // Handle depth + if ( tt == E_SRDTokenType::START ) { + current_.depth ++; + } else if ( tt == E_SRDTokenType::END ) { + assert( current_.depth != 0 ); + current_.depth --; + } + + // Accumulate token + if ( current_.lcarState == T_SRDParserConfig::INVALID_END ) { + accum_.push( errors , std::move( token ) ); + } else { + current_.lcarReplay->add( std::move( token ) ); + } + return; + } + + // Check for "normal" end of rule + if ( current_.depth == 0 && eid != T_SRDParserConfig::INVALID_END + && checkRuleEnd( errors , token , eid ) ) { + return; + } + + // If we had found a list/context ambiguity, enter the context then + // replay what we had. + if ( current_.lcarState != T_SRDParserConfig::INVALID_END ) { + auto lcarState( current_.lcarState ); + auto lcarEnd( cfg_.endStates[ lcarState ] ); + auto lcarContext( cfg_.ruleContexts.get( current_.context , lcarEnd ) ); + SP_SRDList replay( std::move( current_.lcarReplay ) ); + current_.lcarState = T_SRDParserConfig::INVALID_END; + current_.state = lcarState; + + const auto nTokens( replay->size( ) ); + enterContext( errors , lcarEnd , lcarContext , (*replay)[ 0 ].location( ) ); + for ( uint32_t i = 1 ; i < nTokens ; i ++ ) { + push( errors , std::move( (*replay)[ i ] ) ); + } + push( errors , std::move( token ) ); + return; + } + + addMatchError( errors , token ); + if ( token.type( ) == E_SRDTokenType::END ) { + if ( current_.depth == 0 ) { + // Don't go into recovery mode + current_.inRule = false; + return; + } + current_.depth --; + } + recovery_ = E_Recovery_::EOL; +} + +inline void T_SRDParserPrivate_::end( T_SRDErrors& errors ) +{ + const auto ss( stack_.size( ) ); + for ( uint32_t i = 0 ; i < ss ; i ++ ) { + auto const& si( stack_[ i ] ); + errors.add( "Unterminated list." , si.ruleStart ); + } + if ( current_.inRule ) { + errors.add( "Unterminated list." , current_.ruleStart ); + } + + if ( cfg_.onFinish ) { + auto const& dc( cfg_.contexts[ 0 ] ); + exec_.addNew( cfg_.onFinish , dc.name( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDParserPrivate_::flush( ) +{ + const auto nExec( exec_.size( ) ); + if ( !nExec ) { + return; + } + + auto& data( *execData_ ); + if ( data.errors.size( ) != 0 ) { + exec_.clear( ); + return; + } + + bool enabled = true; + for ( uint32_t i = 0 ; i < nExec ; i ++ ) { + auto const& step( exec_[ i ] ); + + if ( step.stackAction == E_ESAction_::PUSH && enabled ) { + execStack_.addNew( ); + } + + const auto stackLast( execStack_.size( ) - 1 ); + if ( enabled ) { + data.input = RPC_SRDList( step.input ); + data.currentContext = step.currentContext; + data.targetContext = step.targetContext; + + if ( step.stackAction == E_ESAction_::PUSH ) { + data.currentData = &execStack_[ stackLast - 1 ]; + data.targetData = &execStack_[ stackLast ]; + } else { + data.currentData = &execStack_[ stackLast ]; + if ( step.stackAction == E_ESAction_::POP ) { + data.targetData = &execStack_[ stackLast - 1 ]; + } + } + + enabled = step.handler( data ); + enabled = enabled && data.errors.size( ) == 0; + } + + if ( step.stackAction == E_ESAction_::POP + && ( enabled || stackLast > 0 ) ) { + execStack_.remove( stackLast ); + } + } + exec_.clear( ); +} + + +/*= T_SRDParser ==============================================================*/ + +T_SRDParser::T_SRDParser( T_SRDParserConfig const& cfg ) + : A_PrivateImplementation( new T_SRDParserPrivate_( cfg ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDParser::start( T_SRDErrors& errors ) +{ + p< T_SRDParserPrivate_ >( ).start( errors ); +} + +void T_SRDParser::push( T_SRDErrors& errors , T_SRDToken&& token ) +{ + p< T_SRDParserPrivate_ >( ).push( errors , std::move( token ) ); +} + +void T_SRDParser::end( T_SRDErrors& errors ) +{ + auto& pi( p< T_SRDParserPrivate_ >( ) ); + pi.end( errors ); + if ( pi.flushMode_ != E_SRDFlushMode::MANUAL ) { + pi.flush( ); + } +} + +void T_SRDParser::flush( ) +{ + p< T_SRDParserPrivate_ >( ).flush( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDParser::flushMode( + const E_SRDFlushMode mode ) noexcept +{ + p< T_SRDParserPrivate_ >( ).flushMode_ = mode; +} + +E_SRDFlushMode T_SRDParser::flushMode( ) const noexcept +{ + return p< T_SRDParserPrivate_ >( ).flushMode_; +} + +void T_SRDParser::handleFlushToken( bool handleIt ) noexcept +{ + p< T_SRDParserPrivate_ >( ).handleFlushTokens_ = handleIt; +} + +bool T_SRDParser::handleFlushToken( ) const noexcept +{ + return p< T_SRDParserPrivate_ >( ).handleFlushTokens_; +} + +/*----------------------------------------------------------------------------*/ + +T_Variant const& T_SRDParser::getExecStackTop( ) const +{ + return p< T_SRDParserPrivate_ >( ).getExecStackTop( ); +} diff --git a/src/SRDParserConfig.cc b/src/SRDParserConfig.cc new file mode 100644 index 0000000..bb5b0c4 --- /dev/null +++ b/src/SRDParserConfig.cc @@ -0,0 +1,982 @@ +/******************************************************************************/ +/* SRD - PARSER CONFIGURATION *************************************************/ +/******************************************************************************/ + +#include +using namespace lw; + +namespace { + + +/*= TEMPORARY CONVERSION DATA ================================================*/ + +// E_NodeType_ - Node types for the intermediary RE representation +enum class E_NodeType_ +{ CAT , ALT , STAR , SYM , EPS }; + + +// E_SymNodeType_ - Symbol types for the intermediary RE representation +// This includes END symbols, which are not present in the actual +// configuration. +enum class E_SymNodeType_ +{ WORD , ENUM , TOKEN , END }; + + +// T_RuleNode_ - A node in the intermediary RE tree +struct T_RuleNode_ +{ + uint32_t index; + E_NodeType_ type; + uint32_t left; + uint32_t right; + uint32_t label = -1; +}; + + +// T_Symbol_ - Symbol data for the intermediary representation +struct T_Symbol_ +{ + E_SymNodeType_ type; + uint32_t idx; + + explicit T_Symbol_( T_RuleNode_ const& node ) + : type( E_SymNodeType_( node.left ) ) , + idx( node.right ) + { } +}; + + +// T_RuleSource_ - Initial context and rule indices +struct T_RuleSource_ +{ + uint32_t context; + uint32_t rule; + + T_RuleSource_( uint32_t context , uint32_t rule ) + : context( context ) , rule( rule ) + { } +}; + + +// T_ContextConverter_ - Definitions to configuration converter +struct T_ContextConverter_ +{ + typedef T_Array< T_SRDInputItem > T_InSeq_; + typedef T_Array< T_RuleNode_ > T_Nodes_; + + T_SRDParserConfig& cfg; + T_SRDParserDefs const& defs; + + // Data used to transform definition contexts into actual contexts + T_Array< uint32_t > usedContexts; + T_MultiArray< T_RuleSource_ > ruleSources; + + // Current context & rule + uint32_t cid; + uint32_t curRule; + + // RE representation of a context's rules + T_Nodes_ nodes; + T_Array< uint32_t > symNodes; + uint32_t maxLabel; + + // Automaton construction internals + T_Array< bool > cancellable; + T_MultiArray< uint32_t > firstPos; + T_MultiArray< uint32_t > lastPos; + T_MultiArray< uint32_t > nextPos; + T_Array< uint32_t > nameAccum; + T_MultiArray< uint32_t > stateNames; + + // "Alphabet" for a set of rules + T_Array< T_Symbol_ > symbols; + T_MultiArray< uint32_t > symLabels; + + + T_ContextConverter_( T_SRDParserConfig& cfg , T_SRDParserDefs const& defs ); + + // Go through all rules to check for unbalanced lists + void checkUnbalancedRules( ) const; + // Unbalanced list check - sequence + int checkUBSequence( T_String const& ctx , uint32_t cRule , T_InSeq_ const& sequence ) const; + // Unbalanced list check - alternatives + int checkUBAlternatives( T_String const& ctx , uint32_t cRule , T_InSeq_ const& alternatives ) const; + // Unbalanced list check - single item + int checkUBItem( T_String const& ctx , uint32_t cRule , T_SRDInputItem const& item ) const; + // Unbalanced list check - throw the error + void errorUnbalanced( T_String const& ctx , uint32_t cRule ) const; + + // Find all contexts that are used by another; also check inheritance + void findUsedContexts( ); + // Build the context for the parser's configuration, including parents + void buildContext( uint32_t context ); + // Add a context to the list of contexts to copy + void addUsedContext( T_String const& current , uint32_t cRule , T_String const& next ); + // Find all context IDs required to build a context + int findContextsFor( uint32_t sourceContext , uint32_t* output ); + + // Add a node, set its index and type, then return it + T_RuleNode_& addNode( E_NodeType_ type ); + // Add a symbol node + T_RuleNode_& addSymbolNode( E_SymNodeType_ type , uint32_t idx ); + // Add a word node. If the word is not in the words index, add it there. + T_RuleNode_& addWord( T_String const& word ); + // Add an enumeration node. + T_RuleNode_& addEnum( T_String const& eName ); + // Add a token node. + T_RuleNode_& addToken( E_SRDTokenType token ); + // Add a branch that represents alternatives + T_RuleNode_& addAlternatives( T_InSeq_ const& alts ); + // Add a branch that represents a sequence of items, using CAT nodes + T_RuleNode_& addSequence( T_InSeq_ const& sequence ); + // Generate a branch that represents repetitions of a sequence. + T_RuleNode_& convertRepetitions( T_SRDInputItem const& item ); + // Convert a rule item into a tree branch + T_RuleNode_& convertItem( T_SRDInputItem const& item ); + + // Convert all rules in the current context into a RE tree + void convertRules( ); + + // Generate the "cancellable" value for all nodes + void computeCancellable( ); + // Implementation for compute{First|Last}Pos + void computePos( T_MultiArray< uint32_t >& output , bool first ); + // Generate first positions for all nodes + void computeFirstPos( ); + // Generate last positions for all nodes + void computeLastPos( ); + // Generate next positions for all labels + void computeNextPos( ); + // Gather all symbols and their associated labels + void gatherSymbols( ); + + // Set the rule index for a state + void setEndState( uint32_t state , uint32_t end ); + // Add values from a label to the state name accumulator + void addNameParts( uint32_t label ); + // Check symbol labels in a state name + void checkStateName( uint32_t state , uint32_t first , uint32_t end , uint32_t si ); + // Try to find an existing state matching the accumulated name. + // Returns T_HashIndex::INVALID_INDEX if not found. + uint32_t findState( uint32_t firstState ); + // Add a transition + void addTransition( T_Symbol_ const& sym , uint32_t state ); + // Build the automaton for the current context + void buildAutomaton( ); +}; + +} // namespace + + +/*= T_ContextConverter_ ======================================================*/ + +namespace { + +T_ContextConverter_::T_ContextConverter_( T_SRDParserConfig& cfg , T_SRDParserDefs const& defs ) + : cfg( cfg ) , defs( defs ) , usedContexts( defs.contexts( ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::checkUnbalancedRules( ) const +{ + const auto nContexts( defs.contexts( ) ); + for ( uint32_t i = 0 ; i < nContexts ; i ++ ) { + auto const& ctx( defs[ i ] ); + auto const& cName( ctx.name( ) ); + + auto const& rules( ctx.rules( ) ); + const auto nRules( rules.size( ) ); + for ( uint32_t j = 0 ; j < nRules ; j ++ ) { + auto const& rule( rules[ j ] ); + if ( checkUBSequence( cName , j + 1 , rule.rule( ) ) != 0 ) { + errorUnbalanced( cName , j + 1 ); + } + } + } +} + +int T_ContextConverter_::checkUBSequence( T_String const& ctx , uint32_t cRule , + T_InSeq_ const& sequence ) const +{ + const auto length( sequence.size( ) ); + int sum( 0 ); + for ( uint32_t i = 0 ; i < length ; i ++ ) { + sum += checkUBItem( ctx , cRule , sequence[ i ] ); + } + return sum; +} + +int T_ContextConverter_::checkUBAlternatives( T_String const& ctx , uint32_t cRule , + T_InSeq_ const& alternatives ) const +{ + const auto length( alternatives.size( ) ); + int current( 0 ); + for ( uint32_t i = 0 ; i < length ; i ++ ) { + const int value( checkUBItem( ctx , cRule , alternatives[ i ] ) ); + if ( i == 0 ) { + current = value; + } else if ( value != current ) { + errorUnbalanced( ctx , cRule ); + } + } + return current; +} + +int T_ContextConverter_::checkUBItem( T_String const& ctx , uint32_t cRule , + T_SRDInputItem const& item ) const +{ + const auto iType( item.type( ) ); + switch ( iType ) { + + case E_SRDInputItem::TOKEN: + if ( item.token( ) == E_SRDTokenType::START ) { + return 1; + } else if ( item.token( ) == E_SRDTokenType::END ) { + return -1; + } + break; + + case E_SRDInputItem::ALTERNATIVE: + return checkUBAlternatives( ctx , cRule , item.items( ) ); + + case E_SRDInputItem::REPETITION: + { + const int sv( checkUBSequence( ctx , cRule , item.items( ) ) ); + if ( sv != 0 && item.min( ) != item.max( ) ) { + errorUnbalanced( ctx , cRule ); + } else { + return sv * item.min( ); + } + } + break; + + case E_SRDInputItem::WORD: + case E_SRDInputItem::ENUM: + break; + + } + return 0; +} + +void T_ContextConverter_::errorUnbalanced( T_String const& ctx , uint32_t cRule ) const +{ + T_StringBuilder sb; + sb << "In context `" << ctx << "`: rule #" << cRule << " is unbalanced."; + throw X_SRDParserConfig( sb ); +} + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::findUsedContexts( ) +{ + uint32_t pos = 0; + usedContexts.add( defs.contextId( defs.defaultContext( ) ) ); + + while ( pos < usedContexts.size( ) ) { + buildContext( usedContexts[ pos ] ); + pos ++; + } +} + +void T_ContextConverter_::buildContext( uint32_t context ) +{ + uint32_t list[ defs.contexts( ) ]; + + const auto nContexts( findContextsFor( context , list ) ); + auto const& name( defs[ context ].name( ) ); + cfg.contexts.addNew( name ); + + auto& output( cfg.contexts[ cfg.contexts.size( ) - 1 ] ); + ruleSources.next( ); + cfg.ruleContexts.next( ); + for ( auto c = nContexts - 1 ; c >= 0 ; c -- ) { + auto const& sCtx( defs[ list[ c ] ] ); + auto const& src( sCtx.rules( ) ); + for ( uint32_t i = 0 ; i < src.size( ) ; i ++ ) { + ruleSources.addNew( list[ c ] , i + 1 ); + auto const& r( src[ i ] ); + output << r; + addUsedContext( sCtx.name( ) , i + 1 , r.context( ) ); + } + } +} + +void T_ContextConverter_::addUsedContext( T_String const& current , uint32_t cRule , T_String const& next ) +{ + if ( !next ) { + cfg.ruleContexts.add( T_SRDParserConfig::INVALID_END ); + return; + } + + const auto id( defs.contextId( next ) ); + if ( id == T_HashIndex::INVALID_INDEX ) { + T_StringBuilder sb; + sb << "In context `" << current << "`: rule #" << cRule + << " refers to missing context `" << next << "`."; + throw X_SRDParserConfig( sb ); + } + + for ( uint32_t i = 0 ; i < usedContexts.size( ) ; i ++ ) { + if ( usedContexts[ i ] == id ) { + cfg.ruleContexts.add( i ); + return; + } + } + cfg.ruleContexts.add( usedContexts.size( ) ); + usedContexts.add( id ); +} + +int T_ContextConverter_::findContextsFor( uint32_t sourceContext , uint32_t* output ) +{ + auto spos = 1; + output[ 0 ] = sourceContext; + + do { + auto const& c( defs[ output[ spos - 1 ] ] ); + if ( !c.parent( ) ) { + break; + } + + const auto pid( defs.contextId( c.parent( ) ) ); + if ( pid == T_HashIndex::INVALID_INDEX ) { + T_StringBuilder sb; + sb << "Context `" << c.name( ) << "`: unknown parent `" + << c.parent( ) << "`"; + throw X_SRDParserConfig( sb ); + } + + for ( auto i = 0 ; i < spos ; i ++ ) { + if ( output[ i ] == pid ) { + T_StringBuilder sb; + sb << "Context `" << c.parent( ) + << "`: recursive inheritance"; + throw X_SRDParserConfig( sb ); + } + } + output[ spos ++ ] = pid; + } while ( 1 ); + + return spos; +} + +/*----------------------------------------------------------------------------*/ + +T_RuleNode_& T_ContextConverter_::addNode( E_NodeType_ type ) +{ + const uint32_t index( nodes.size( ) ); + auto& node( nodes.addNew( ) ); + node.index = index; + node.type = type; + return node; +} + +T_RuleNode_& T_ContextConverter_::addSymbolNode( E_SymNodeType_ type , uint32_t idx ) +{ + auto& node( addNode( E_NodeType_::SYM ) ); + node.left = uint32_t( type ); + node.right = uint32_t( idx ); + node.label = ++ maxLabel; + symNodes.add( node.index ); + return node; +} + +T_RuleNode_& T_ContextConverter_::addWord( T_String const& word ) +{ + const auto hash = ComputeHash( word ); + uint32_t idx = cfg.wordsIndex.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( cfg.words[ idx ] == word ) { + break; + } + idx = cfg.wordsIndex.next( idx ); + } + if ( idx == T_HashIndex::INVALID_INDEX ) { + idx = cfg.words.size( ); + cfg.wordsIndex.add( hash ); + cfg.words.add( word ); + } + + return addSymbolNode( E_SymNodeType_::WORD , idx ); +} + +T_RuleNode_& T_ContextConverter_::addEnum( T_String const& eName ) +{ + if ( !cfg.enums.contains( eName ) ) { + if ( !defs.hasEnum( eName ) ) { + T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) ); + T_SRDContext const& sCtx( defs[ src.context ] ); + + T_StringBuilder sb; + sb << "In context `" << sCtx.name( ) << "`: rule #" + << src.rule << " refers to unknown enumeration `" << eName << "`."; + throw X_SRDParserConfig( sb ); + } + cfg.enums.add( defs.enumeration( eName ) ); + } + return addSymbolNode( E_SymNodeType_::ENUM , + cfg.enums.indexOf( eName ) ); +} + +inline T_RuleNode_& T_ContextConverter_::addToken( E_SRDTokenType token ) +{ + return addSymbolNode( E_SymNodeType_::TOKEN , uint32_t( token ) ); +} + +T_RuleNode_& T_ContextConverter_::addAlternatives( T_InSeq_ const& alts ) +{ + auto n( alts.size( ) ); + if ( n == 0 ) { + T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) ); + T_SRDContext const& sCtx( defs[ src.context ] ); + + T_StringBuilder sb; + sb << "In context `" << sCtx.name( ) << "`: rule #" << src.rule + << " features an empty alternation."; + throw X_SRDParserConfig( sb ); + } + + uint32_t indices[ n ]; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto& node( convertItem( alts[ i ] ) ); + indices[ i ] = node.index; + } + + uint32_t nextLeft = indices[ 0 ]; + for ( uint32_t i = 1 ; i < n ; i ++ ) { + auto& node( addNode( E_NodeType_::ALT ) ); + node.left = nextLeft; + node.right = indices[ i ]; + nextLeft = node.index; + } + + return nodes[ nextLeft ]; +} + +T_RuleNode_& T_ContextConverter_::addSequence( T_InSeq_ const& sequence ) +{ + auto n( sequence.size( ) ); + if ( n == 0 ) { + T_RuleSource_ const& src( ruleSources.get( cid , curRule - 1 ) ); + T_SRDContext const& sCtx( defs[ src.context ] ); + + T_StringBuilder sb; + sb << "In context `" << sCtx.name( ) << "`: rule #" << src.rule + << " features an empty sequence."; + throw X_SRDParserConfig( sb ); + } + + uint32_t indices[ n ]; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto& node( convertItem( sequence[ i ] ) ); + indices[ i ] = node.index; + } + + uint32_t nextLeft = indices[ 0 ]; + for ( uint32_t i = 1 ; i < n ; i ++ ) { + auto& node( addNode( E_NodeType_::CAT ) ); + node.left = nextLeft; + node.right = indices[ i ]; + nextLeft = node.index; + } + + return nodes[ nextLeft ]; +} + +T_RuleNode_& T_ContextConverter_::convertRepetitions( T_SRDInputItem const& item ) +{ + const auto min( item.min( ) ); + const auto max( item.max( ) ); + auto const& seq( item.items( ) ); + + // Minimum amount of repetitions + uint32_t nextLeft = UINT32_MAX; + for ( uint32_t i = 0 ; i < min ; i ++ ) { + auto const& node( addSequence( seq ) ); + if ( i > 0 ) { + auto& cat( addNode( E_NodeType_::CAT ) ); + cat.left = nextLeft; + cat.right = node.index; + nextLeft = cat.index; + } else { + nextLeft = node.index; + } + } + + uint32_t nextRight = UINT32_MAX; + if ( max == UINT32_MAX ) { + // Unlimited repetitions + auto const& node( addSequence( seq ) ); + auto& star( addNode( E_NodeType_::STAR ) ); + star.left = node.index; + nextRight = star.index; + + } else if ( max > min ) { + // First item: (alt $seq epsilon) + auto const& seq0( addSequence( seq ) ); + auto const& epsilon0( addNode( E_NodeType_::EPS ) ); + + auto& alt0( addNode( E_NodeType_::ALT ) ); + alt0.left = seq0.index; + alt0.right = epsilon0.index; + nextRight = alt0.index; + + // Other items: (alt (cat $seq $previous) epsilon) + const uint32_t count( max - min ); + for ( uint32_t i = 1 ; i < count ; i ++ ) { + auto const& nseq( addSequence( seq ) ); + + auto& cat( addNode( E_NodeType_::CAT ) ); + cat.left = nseq.index; + cat.right = nextRight; + + auto const& eps( addNode( E_NodeType_::EPS ) ); + + auto& alt( addNode( E_NodeType_::ALT ) ); + alt.left = cat.index; + alt.right = eps.index; + nextRight = alt.index; + } + } + + assert( nextRight != UINT32_MAX || nextLeft != UINT32_MAX ); + + // Combine fixed part and variable part if both exist + if ( nextRight != UINT32_MAX && nextLeft != UINT32_MAX ) { + auto& cat( addNode( E_NodeType_::CAT ) ); + cat.left = nextLeft; + cat.right = nextRight; + return cat; + } + + if ( nextRight == UINT32_MAX ) { + return nodes[ nextLeft ]; + } else { + return nodes[ nextRight ]; + } +} + +T_RuleNode_& T_ContextConverter_::convertItem( T_SRDInputItem const& item ) +{ + switch ( item.type( ) ) { + + case E_SRDInputItem::WORD: + return addWord( item.word( ) ); + + case E_SRDInputItem::ENUM: + return addEnum( item.word( ) ); + + case E_SRDInputItem::TOKEN: + return addToken( item.token( ) ); + + case E_SRDInputItem::ALTERNATIVE: + return addAlternatives( item.items( ) ); + + case E_SRDInputItem::REPETITION: + return convertRepetitions( item ); + + } + throw std::domain_error( "unknown item type" ); +} + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::convertRules( ) +{ + auto const& ctx( cfg.contexts[ cid ] ); + nodes.clear( ); + symNodes.clear( ); + maxLabel = 0; + + // Access all rules + auto const& rules( ctx.rules( ) ); + const uint32_t nRules( rules.size( ) ); + if ( nRules == 0 ) { + T_StringBuilder sb; + sb << "Context `" << ctx.name( ) + << "` is empty."; + throw X_SRDParserConfig( sb ); + } + + // Convert each rule + uint32_t ruleRoots[ nRules ]; + for ( curRule = 1 ; curRule <= nRules ; curRule ++ ) { + // Convert the rule + auto& rNode( addSequence( rules[ curRule - 1 ].rule( ) ) ); + auto const& ruleEnd( addSymbolNode( E_SymNodeType_::END , curRule - 1 ) ); + auto& ruleRoot( addNode( E_NodeType_::CAT ) ); + ruleRoot.left = rNode.index; + ruleRoot.right = ruleEnd.index; + + ruleRoots[ curRule - 1 ] = ruleRoot.index; + } + + // Add alternations between the rules + uint32_t nextLeft = ruleRoots[ 0 ]; + for ( uint32_t i = 1 ; i < nRules ; i ++ ) { + auto& alt( addNode( E_NodeType_::ALT ) ); + alt.left = nextLeft; + alt.right = ruleRoots[ i ]; + nextLeft = alt.index; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::computeCancellable( ) +{ + const uint32_t nNodes( nodes.size( ) ); + cancellable.clear( ); + for ( uint32_t i = 0 ; i < nNodes ; i ++ ) { + auto const& node( nodes[ i ] ); + switch ( node.type ) { + + case E_NodeType_::ALT: + { + bool l( cancellable[ node.left ] ); + bool r( cancellable[ node.right ] ); + cancellable.add( l || r ); + break; + } + + case E_NodeType_::CAT: + { + bool l( cancellable[ node.left ] ); + bool r( cancellable[ node.right ] ); + cancellable.add( l && r ); + break; + } + + case E_NodeType_::SYM: + cancellable.add( false ); + break; + + case E_NodeType_::STAR: + case E_NodeType_::EPS: + cancellable.add( true ); + break; + } + } +} + +void T_ContextConverter_::computePos( T_MultiArray< uint32_t >& output , const bool first ) +{ + const auto n( nodes.size( ) ); + output.clear( ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& node( nodes[ i ] ); + output.next( ); + switch ( node.type ) { + + case E_NodeType_::SYM: + output.add( node.label ); + break; + + case E_NodeType_::STAR: + output.copyFrom( node.left ); + break; + + case E_NodeType_::ALT: + case E_NodeType_::CAT: + { + const auto n1( first ? node.left : node.right ); + const auto n2( first ? node.right : node.left ); + output.copyFrom( n1 ); + if ( node.type == E_NodeType_::ALT + || cancellable[ n1 ] ) { + output.copyUnique( n2 ); + } + break; + } + + case E_NodeType_::EPS: + // Do nothing. + break; + } + } +} + +inline void T_ContextConverter_::computeFirstPos( ) +{ + computePos( firstPos , true ); +} + +inline void T_ContextConverter_::computeLastPos( ) +{ + computePos( lastPos , false ); +} + +void T_ContextConverter_::computeNextPos( ) +{ + const auto lp( maxLabel + 1 ); + const auto nn( nodes.size( ) ); + nextPos.clear( ); + for ( uint32_t pos = 1 ; pos < lp ; pos ++ ) { + nextPos.next( ); + for ( uint32_t i = 0 ; i < nn ; i ++ ) { + auto const& node( nodes[ i ] ); + if ( node.type == E_NodeType_::CAT ) { + if ( lastPos.contains( node.left , pos ) ) { + nextPos.copyUnique( firstPos , node.right ); + } + } else if ( node.type == E_NodeType_::STAR ) { + if ( lastPos.contains( i , pos ) ) { + nextPos.copyUnique( firstPos , i ); + } + } + } + } +} + +void T_ContextConverter_::gatherSymbols( ) +{ + symbols.clear( ); + symLabels.clear( ); + while ( symNodes.size( ) ) { + auto const& first( nodes[ symNodes[ 0 ] ] ); + symNodes.removeSwap( 0 ); + symbols.addNew( first ); + symLabels.next( ); + symLabels.add( first.label ); + + uint32_t i = 0; + while ( i < symNodes.size( ) ) { + auto const& node( nodes[ symNodes[ i ] ] ); + if ( first.left == node.left && first.right == node.right ) { + symLabels.add( node.label ); + symNodes.removeSwap( i ); + } else { + i ++; + } + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::setEndState( uint32_t state , uint32_t end ) +{ + const auto curEnd( cfg.endStates[ state ] ); + if ( curEnd != T_SRDParserConfig::INVALID_END && curEnd != end ) { + T_StringBuilder sb; + sb << "In context `" << defs[ cid ].name( ) << "`: rule #" + << ( curEnd + 1 ) << " and rule #" << ( end + 1 ) << " are ambiguous."; + throw X_SRDParserConfig( sb ); + } + cfg.endStates[ state ] = end; +} + +void T_ContextConverter_::addNameParts( uint32_t label ) +{ + const auto npf( nextPos.firstOf( label - 1 ) ); + const auto npl( nextPos.sizeOf( label - 1 ) + npf ); + for ( uint32_t i = npf ; i < npl ; i ++ ) { + const auto p( nextPos[ i ] ); + const auto nas( nameAccum.size( ) ); + + bool found = false; + for ( uint32_t j = 0 ; j < nas && !found ; j ++ ) { + found = nameAccum[ j ] == p; + } + + if ( !found ) { + nameAccum.add( p ); + } + } +} + +void T_ContextConverter_::checkStateName( uint32_t state , uint32_t first , uint32_t end , uint32_t si ) +{ + auto const& sym( symbols[ si ] ); + nameAccum.clear( ); + for ( uint32_t i = first ; i < end ; i ++ ) { + const auto snVal( stateNames[ i ] ); + if ( !symLabels.contains( si , snVal ) ) { + continue; + } + if ( sym.type == E_SymNodeType_::END ) { + setEndState( state , sym.idx ); + } else { + addNameParts( snVal ); + } + } +} + +uint32_t T_ContextConverter_::findState( uint32_t firstState ) +{ + const uint32_t nsize( nameAccum.size( ) ); + const uint32_t snsize( stateNames.size( ) ); + + for ( uint32_t i = 0 ; i < snsize ; i ++ ) { + if ( stateNames.sizeOf( i ) != nsize ) { + continue; + } + if ( !memcmp( &stateNames[ stateNames.firstOf( i ) ] , &nameAccum[ 0 ] , + nsize * sizeof( uint32_t ) ) ) { + return i + firstState; + } + } + return T_HashIndex::INVALID_INDEX; +} + +void T_ContextConverter_::addTransition( T_Symbol_ const& sym , uint32_t state ) +{ + if ( sym.type == E_SymNodeType_::TOKEN ) { + cfg.transitions.addNew( E_SRDTokenType( sym.idx ) , state ); + } else { + const auto type( sym.type == E_SymNodeType_::WORD + ? E_SRDTransitionType::WORD + : E_SRDTransitionType::ENUM ); + cfg.transitions.addNew( type , sym.idx , state ); + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ContextConverter_::buildAutomaton( ) +{ + static const F_Comparator< T_SRDTransition > transSorter = + []( T_SRDTransition const& a , T_SRDTransition const& b ) -> int { + return T_Comparator< decltype( a.type ) >::compare( a.type , b.type ); + }; + + const auto firstState( cfg.endStates.size( ) ); + cfg.endStates.add( T_SRDParserConfig::INVALID_END ); + cfg.startStates.add( firstState ); + stateNames.clear( ); + stateNames.copyFrom( firstPos , firstPos.size( ) - 1 ); + stateNames.sort( 0 ); + + const auto nsyms( symbols.size( ) ); + auto state = firstState; + while ( state < cfg.endStates.size( ) ) { + cfg.transitions.next( ); + + const auto snFirst( stateNames.firstOf( state - firstState ) ); + const auto snEnd( snFirst + stateNames.sizeOf( state - firstState ) ); + for ( uint32_t si = 0 ; si < nsyms ; si ++ ) { + // Find the next state's name + checkStateName( state , snFirst , snEnd , si ); + if ( nameAccum.size( ) == 0 ) { + continue; + } + nameAccum.sort( ); + + // Check if the state exists + uint32_t found( findState( firstState ) ); + + // Add new state if needed + if ( found == T_HashIndex::INVALID_INDEX ) { + found = cfg.endStates.size( ); + cfg.endStates.add( T_SRDParserConfig::INVALID_END ); + stateNames.next( ); + stateNames.copyFrom( nameAccum ); + } + + // Add the transition + addTransition( symbols[ si ] , found ); + } + + cfg.transitions.sort( state , transSorter ); + state ++; + } +} + + +} // namespace + + +/*= X_SRDParserConfig ========================================================*/ + +X_SRDParserConfig::X_SRDParserConfig( T_StringBuilder const& error ) + : errorString_( error.size( ) + 1 ) +{ + memcpy( &errorString_[ 0 ] , error.data( ) , error.size( ) ); + errorString_[ error.size( ) ] = 0; +} + +X_SRDParserConfig::X_SRDParserConfig( T_String const& error ) + : errorString_( error.size( ) + 1 ) +{ + memcpy( &errorString_[ 0 ] , error.data( ) , error.size( ) ); + errorString_[ error.size( ) ] = 0; +} + +char const* X_SRDParserConfig::what( ) const noexcept +{ + return &errorString_[ 0 ]; +} + + +/*= T_SRDParserConfig ========================================================*/ + +T_SRDParserConfig::T_SRDParserConfig( T_SRDParserDefs const& defs ) + : onStart( defs.onStart( ) ) , onFinish( defs.onFinish( ) ) , contexts( defs.contexts( ) ) , + enums( []( T_SRDEnum const& e ) -> T_String const& { + return e.name( ); + } , 64 , 64 , 64 ) , wordsIndex( 256 , 256 , 256 ) , words( 256 ) , startStates( defs.contexts( ) ) +{ + T_ContextConverter_ converter( *this , defs ); + converter.checkUnbalancedRules( ); + converter.findUsedContexts( ); + + const auto nc( contexts.size( ) ); + for ( uint32_t i = 0 ; i < nc ; i ++ ) { + converter.cid = i; + converter.convertRules( ); + converter.computeCancellable( ); + converter.computeFirstPos( ); + converter.computeLastPos( ); + converter.computeNextPos( ); + converter.gatherSymbols( ); + converter.buildAutomaton( ); + } +} + +bool T_SRDParserConfig::transition( uint32_t& state , T_SRDToken const& token ) const +{ + const auto first( transitions.firstOf( state ) ); + const auto end( first + transitions.sizeOf( state ) ); + + for ( uint32_t i = first ; i < end ; i ++ ) { + auto const& t( transitions[ i ] ); + + if ( t.type == E_SRDTransitionType::TOKEN ) { + if ( token.type( ) == t.tokenType ) { + state = t.next; + return true; + } + continue; + } + + if ( token.type( ) != E_SRDTokenType::WORD ) { + continue; + } + + if ( t.type == E_SRDTransitionType::WORD ) { + if ( token.stringValue( ) == words[ t.index ] ) { + state = t.next; + return true; + } + continue; + } + + assert( t.type == E_SRDTransitionType::ENUM ); + if ( enums[ t.index ].contains( token.stringValue( ) ) ) { + state = t.next; + return true; + } + } + + return false; +} + +T_Optional< uint32_t > T_SRDParserConfig::enumValue( + T_String const& name , T_String const& member ) const noexcept +{ + T_SRDEnum const* const enumData( enums.get( name ) ); + if ( enumData && enumData->contains( member ) ) { + return (*enumData)[ member ]; + } + return T_Optional< uint32_t >( ); +} diff --git a/src/SRDPreproc.cc b/src/SRDPreproc.cc new file mode 100644 index 0000000..1909be8 --- /dev/null +++ b/src/SRDPreproc.cc @@ -0,0 +1,1416 @@ +/******************************************************************************/ +/* SRD - PREPROCESSOR *********************************************************/ +/******************************************************************************/ + +#include +#include +#include +using namespace lw; + + +/*= A_SRDPreprocessorCommand =================================================*/ + +A_SRDPreprocessorCommand::~A_SRDPreprocessorCommand( ) +{ } + + +/*= T_SRDPreprocessorConfig ==================================================*/ + +T_SRDPreprocessorConfig::T_SRDPreprocessorConfig( ) + : commands_( []( OP_SRDPreprocessorCommand const& cmd ) -> T_String const& { + return cmd->command( ); + } ) +{ } + +T_SRDPreprocessorConfig& T_SRDPreprocessorConfig::add( + OP_SRDPreprocessorCommand&& command ) +{ + commands_.add( std::move( command ) ); + return *this; +} + +RPC_SRDPreprocessorCommand T_SRDPreprocessorConfig::command( + T_String const& name ) const +{ + const auto cmd( commands_.get( name ) ); + if ( cmd == nullptr ) { + return nullptr; + } + return cmd->get( ); +} + + +/*= T_SRDPPStateImpl_ ========================================================*/ + +namespace { +struct T_SRDPPStateImpl_ +{ + using E_Type = T_SRDPreprocessorState::E_Type; + + // --------------------------------------------------------------------- + + T_SRDPreprocessorData& data_; + + SP_SRDList input_; + uint32_t pos_; + + SP_SRDList output_; + SP_SRDLocation initialLocation_; + + F_SRDPreprocessorHandler postExec_; + + uint32_t processLimit_; + bool rawMode_; + T_Array< T_SRDToken > rawStack_; + + T_SRDPreprocessorState::E_Type type_; + uint8_t exiting_; + + // --------------------------------------------------------------------- + + explicit T_SRDPPStateImpl_( + T_SRDPreprocessorData& data ) noexcept; + + T_SRDPPStateImpl_( + T_SRDPreprocessorData& data , + E_Type type , + SP_SRDList&& input ) noexcept; + + T_SRDPPStateImpl_( + T_SRDPreprocessorData& data , + E_Type type , + SP_SRDList&& input , + SP_SRDList&& output , + F_SRDPreprocessorHandler postExec ) noexcept; + + // --------------------------------------------------------------------- +}; +} + +/*----------------------------------------------------------------------------*/ + +inline T_SRDPPStateImpl_::T_SRDPPStateImpl_( + T_SRDPreprocessorData& data ) noexcept + : T_SRDPPStateImpl_( data , T_SRDPreprocessorState::C_UNSPECIFIED , {} ) +{ } + +inline T_SRDPPStateImpl_::T_SRDPPStateImpl_( + T_SRDPreprocessorData& data , + const E_Type type , + SP_SRDList&& input ) noexcept + : T_SRDPPStateImpl_( data , type , std::move( input ) , + NewShared< T_SRDList >( 32 ) , {} ) +{ } + +inline T_SRDPPStateImpl_::T_SRDPPStateImpl_( + T_SRDPreprocessorData& data , + const E_Type type , + SP_SRDList&& input , + SP_SRDList&& output , + F_SRDPreprocessorHandler postExec ) noexcept + : data_( data ) , + // + input_( std::move( input ) ) , + pos_( 0 ) , + // + output_( std::move( output ) ) , + initialLocation_( ) , + // + postExec_( postExec ) , + // + processLimit_( UINT32_MAX ) , + rawMode_( false ) , + rawStack_( 16 ) , + // + type_( type ) , + exiting_( 0 ) +{ } + + + +/*= T_SRDPreprocessorState ===================================================*/ + +T_SRDPreprocessorState::T_SRDPreprocessorState( + T_SRDPreprocessorData& data ) + : A_PrivateImplementation( new T_SRDPPStateImpl_( data ) ) +{ } + +T_SRDPreprocessorState::T_SRDPreprocessorState( + T_SRDPreprocessorData& data , + const E_Type type , + SP_SRDList input ) + : A_PrivateImplementation( new T_SRDPPStateImpl_( + data , type , std::move( input ) ) ) +{ } + +T_SRDPreprocessorState::T_SRDPreprocessorState( + T_SRDPreprocessorData& data , + const E_Type type , + SP_SRDList input , + SP_SRDList output , + F_SRDPreprocessorHandler postExec ) + : A_PrivateImplementation( new T_SRDPPStateImpl_( + data , type , std::move( input ) , + std::move( output ) , postExec ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +T_SRDPreprocessorData& T_SRDPreprocessorState::data( ) const +{ + return p< T_SRDPPStateImpl_ >( ).data_; +} + +bool T_SRDPreprocessorState::hasInput( ) const +{ + return bool( p< T_SRDPPStateImpl_ >( ).input_ ); +} + +bool T_SRDPreprocessorState::isFinished( ) const +{ + auto const& pi( p< T_SRDPPStateImpl_ >( ) ); + return !pi.input_ || pi.pos_ >= pi.input_->size( ); +} + +T_SRDList& T_SRDPreprocessorState::output( ) const +{ + return *( p< T_SRDPPStateImpl_ >( ).output_ ); +} + +SP_SRDList const& T_SRDPreprocessorState::outputPointer( ) const +{ + return p< T_SRDPPStateImpl_ >( ).output_; +} + +RPC_SRDLocation T_SRDPreprocessorState::initialLocation( ) const +{ + return RPC_SRDLocation( p< T_SRDPPStateImpl_ >( ).initialLocation_ ); +} + +T_SRDPreprocessorState::E_Type T_SRDPreprocessorState::type( ) const +{ + return p< T_SRDPPStateImpl_ >( ).type_; +} + +void T_SRDPreprocessorState::setExiting( E_Type type ) +{ + auto& e( p< T_SRDPPStateImpl_ >( ).exiting_ ); + e = e | uint8_t( type ); +} + +uint8_t T_SRDPreprocessorState::isExiting( ) const +{ + return p< T_SRDPPStateImpl_ >( ).exiting_; +} + +/*----------------------------------------------------------------------------*/ + +SP_SRDLocation T_SRDPreprocessorState::createChainedLocation( ) const +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + SP_SRDLocation cloc( pi.data_.callLocation( ) ); + if ( pi.initialLocation_ && cloc ) { + SP_SRDLocation location( NewShared< T_SRDLocation >( *pi.initialLocation_ ) ); + if ( location->isChained( ) ) { + auto& lc( location->chaining( ) ); + SP_SRDLocation ptrInitial( cloc ); + SP_SRDLocation ptrNew( NewShared< T_SRDLocation >( *cloc ) ); + cloc = ptrNew; + while ( ptrInitial ) { + if ( ptrInitial->isChained( ) ) { + auto& chain( ptrInitial->chaining( ) ); + SP_SRDLocation ptrNext( NewShared< T_SRDLocation >( + *( chain.location ) ) ); + if ( chain.depth > 1 ) { + ptrNew->chain( chain.circumstances , chain.depth , ptrNext ); + } else { + ptrNew->chain( chain.circumstances , ptrNext ); + } + ptrNew = ptrNext; + ptrInitial = chain.location; + } else { + if ( lc.depth > 1 ) { + ptrNew->chain( lc.circumstances , lc.depth , lc.location ); + } else { + ptrNew->chain( lc.circumstances , lc.location ); + } + ptrInitial = SP_SRDLocation( ); + } + } + } + location->chain( E_SRDLocationChaining::CALLED , pi.data_.callRecursionDepth( ) , cloc ); + return location; + } else if ( pi.initialLocation_ ) { + return pi.initialLocation_; + } else { + return cloc; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorState::processToken( + T_SRDToken& token ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + if ( token.type( ) != E_SRDTokenType::COMMENT + && ( ( pi.rawMode_ && handleRaw( token ) ) + || ( !pi.rawMode_ && handleProcessed( token ) ) ) ) { + return; + } + pi.pos_ ++; + if ( pi.pos_ >= pi.processLimit_ ) { + pi.rawMode_ = true; + } +} + +void T_SRDPreprocessorState::processNext( ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + assert( pi.input_ ); + assert( pi.pos_ < pi.input_->size( ) ); + processToken( (*pi.input_)[ pi.pos_ ] ); +} + +void T_SRDPreprocessorState::checkUnterminated( + T_SRDErrors& errors ) const +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + if ( !pi.rawMode_ ) { + return; + } + + auto const rss( pi.rawStack_.size( ) ); + for ( uint32_t i = 0 ; i < rss ; i ++ ) { + auto const& item( pi.rawStack_[ i ] ); + errors.add( "unterminated list" , item.location( ) ); + } +} + +void T_SRDPreprocessorState::terminate( ) +{ + // We need to store this so the current instance lives on until we exit + // the method. + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + auto se( pi.data_.pop( ) ); + assert( RP_SRDPreprocessorState( se ) == this ); + if ( pi.postExec_ ) { + try { + pi.postExec_( *this ); + } catch ( X_SRDErrors const& ) { + pi.data_.interrupt( C_TRY ); + } + } +} + +bool T_SRDPreprocessorState::handleRaw( + T_SRDToken& token ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + const auto tt( token.type( ) ); + + if ( tt == E_SRDTokenType::START ) { + T_SRDToken& top( pi.rawStack_[ + pi.rawStack_.add( T_SRDToken::List( ) ) ] ); + top.copyLocationOf( token ); + return false; + } + + const uint32_t ss( pi.rawStack_.size( ) ); + if ( tt == E_SRDTokenType::END ) { + if ( ss == 0 ) { + terminate( ); + return true; + } + + const uint32_t last( ss - 1 ); + T_SRDToken top( std::move( pi.rawStack_[ last ] ) ); + pi.rawStack_.remove( last ); + if ( last != 0 ) { + pi.rawStack_[ last - 1 ].list( ).add( std::move( top ) ); + } else { + pi.output_->add( std::move( top ) ); + } + return false; + } + + if ( ss != 0 ) { + pi.rawStack_[ ss - 1 ].list( ).add( std::move( token ) ); + } else { + pi.output_->add( std::move( token ) ); + } + return false; +} + +bool T_SRDPreprocessorState::handleProcessed( + T_SRDToken& token ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + const auto tt( token.type( ) ); + + // Lists that have already been processed + if ( tt == E_SRDTokenType::LIST ) { + T_SRDPreprocessorState& nState( + pi.data_.push( C_UNSPECIFIED , + NewShared< T_SRDList >( std::move( token.list( ) ) ) , + NewShared< T_SRDList >( ) , + &T_SRDPreprocessorState::handleEndOfList ) ); + nState.p< T_SRDPPStateImpl_ >( ).initialLocation_ = NewShared< T_SRDLocation >( token.location( ) ); + return false; + } + + // Start of unprocessed list + if ( tt == E_SRDTokenType::START ) { + T_SRDPreprocessorState& nState( pi.data_.push( ) ); + auto& npi( nState.p< T_SRDPPStateImpl_ >( ) ); + npi.postExec_ = &T_SRDPreprocessorState::handleEndOfList; + npi.initialLocation_ = NewShared< T_SRDLocation >( token.location( ) ); + return false; + } + + // End of unprocessed list + if ( tt == E_SRDTokenType::END ) { + terminate( ); + return true; + } + + // Variables and calls + if ( tt == E_SRDTokenType::VAR ) { + handleVariable( token ); + return false; + } + + // Macros, commands and words at the start of a list + if ( tt == E_SRDTokenType::WORD && pi.pos_ == 0 + && !pi.data_.topLevel( ) && pi.type_ == C_UNSPECIFIED + && checkCommandsAndMacros( token ) ) { + return false; + } + + // Otherwise simply copy the token + pi.output_->addNew( std::move( token ) ); + return false; +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorState::handleVariable( + T_SRDToken& token ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + auto const& s( pi.data_.scopes( ) ); + auto const& vn( token.stringValue( ) ); + RPC_SRDList var( s.get( false , vn ) ); + if ( var == nullptr ) { + try { + pi.data_.addError( token , "unknown variable" ); + } catch ( X_SRDErrors const& ) { + pi.data_.interrupt( C_TRY ); + } + } else if ( pi.type_ == C_UNSPECIFIED && pi.pos_ == 0 + && s.isBlessed( vn ) ) { + initFunctionCall( ( *var )[ 0 ].list( ) ); + } else { + pi.output_->addAll( *var ); + } +} + +bool T_SRDPreprocessorState::checkCommandsAndMacros( + T_SRDToken& token ) +{ + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + auto const& cfg( pi.data_.preprocessor( ).config( ) ); + RPC_SRDPreprocessorCommand cmd( cfg.command( token.stringValue( ) ) ); + if ( cmd != nullptr ) { + const auto pl( cmd->processArgs( ) ); + if ( pl != UINT32_MAX ) { + pi.processLimit_ = 1 + pl; + } + pi.postExec_ = [cmd]( T_SRDPreprocessorState& state ) { + cmd->execute( state ); + }; + return true; + } + + RPC_SRDList macro( pi.data_.scopes( ).get( true , token.stringValue( ) ) ); + if ( macro != nullptr ) { + initMacro( *macro ); + return true; + } + + if ( token.stringValue( )[ 0 ] == '-' ) { + try { + pi.data_.addError( token , "unknown command" ); + } catch ( X_SRDErrors const& ) { + pi.data_.interrupt( C_TRY ); + } + return true; + } + + return false; +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorState::initFunctionCall( + T_SRDList const& function ) +{ + assert( function.size( ) > 0 ); + assert( function[ 0 ].type( ) == E_SRDTokenType::LIST ); + + p< T_SRDPPStateImpl_ >( ).postExec_ = [&]( T_SRDPreprocessorState& state ) { + const uint32_t fSize( function.size( ) ); + T_SRDToken arguments( function[ 0 ] ); + SP_SRDList body( NewShared< T_SRDList >( fSize > 1 ? ( fSize - 1 ) : 1 ) ); + for ( uint32_t i = 1 ; i < fSize ; i ++ ) { + body->add( function[ i ] ); + } + + auto& sp( state.p< T_SRDPPStateImpl_ >( ) ); + auto& data( sp.data_ ); + auto const& args( arguments.list( ) ); + if ( !state.checkCallArguments( args ) ) { + return; + } + + data.pushCallLocation( sp.initialLocation_ ); + data.scopes( ).setupCallScope( args , *sp.output_ ); + data.push( C_CALL , body , NewShared< T_SRDList >( ) , + []( T_SRDPreprocessorState& ns ) { + auto& nsp( ns.p< T_SRDPPStateImpl_ >( ) ); + auto& data( nsp.data_ ); + data.scopes( ).exit( ); + + const auto callDepth( data.callRecursionDepth( ) ); + SP_SRDLocation initialLocation( data.popCallLocation( ) ); + auto& output( data.top( ).output( ) ); + auto& input( ns.output( ) ); + const auto is( input.size( ) ); + for ( size_t i = 0 ; i < is ; i ++ ) { + T_SRDToken token( std::move( input[ i ] ) ); + auto& loc( token.location( ) ); + if ( !loc.isChained( ) ) { + SP_SRDLocation gLoc( NewShared< T_SRDLocation >( loc ) ); + gLoc->chain( E_SRDLocationChaining::CALLED , callDepth , + initialLocation ); + loc.chain( E_SRDLocationChaining::GENERATED , gLoc ); + } + output.add( std::move( token ) ); + } + } ); + }; +} + +void T_SRDPreprocessorState::initMacro( + T_SRDList const& macro ) +{ + assert( macro.size( ) > 0 ); + assert( macro[ 0 ].type( ) == E_SRDTokenType::LIST ); + + auto& pi( p< T_SRDPPStateImpl_ >( ) ); + pi.rawMode_ = true; + pi.postExec_ = [&]( T_SRDPreprocessorState& state ) { + const uint32_t mSize( macro.size( ) ); + T_SRDToken arguments( macro[ 0 ] ); + SP_SRDList body( NewShared< T_SRDList >( + mSize > 1 ? ( mSize - 1 ) : 1 ) ); + for ( uint32_t i = 1 ; i < mSize ; i ++ ) { + body->add( macro[ i ] ); + } + + auto& sp( state.p< T_SRDPPStateImpl_ >( ) ); + auto& data( sp.data_ ); + auto const& args( arguments.list( ) ); + if ( !state.checkCallArguments( args ) ) { + return; + } + + SP_SRDLocation evalLocation( sp.initialLocation_ ); + data.pushCallLocation( sp.initialLocation_ ); + data.scopes( ).setupCallScope( args , *sp.output_ ); + data.push( C_CALL , body , NewShared< T_SRDList >( ) , + [evalLocation]( T_SRDPreprocessorState& ns ) { + // Process the macro's output + auto& nsp( ns.p< T_SRDPPStateImpl_ >( ) ); + auto& data( nsp.data_ ); + data.scopes( ).exit( ); + + const uint32_t depth( data.callRecursionDepth( ) ); + SP_SRDLocation callLocation( data.popCallLocation( ) ); + SP_SRDList updated( NewShared< T_SRDList >( nsp.output_->size( ) + ? nsp.output_->size( ) : 1 ) ); + auto& input( ns.output( ) ); + const auto is( input.size( ) ); + for ( size_t i = 0 ; i < is ; i ++ ) { + T_SRDToken token( std::move( input[ i ] ) ); + auto& loc( token.location( ) ); + SP_SRDLocation el( NewShared< T_SRDLocation >( *evalLocation ) ); + if ( loc.isChained( ) ) { + auto const& lc( loc.chaining( ) ); + el->chain( lc.circumstances , lc.location ); + } else { + SP_SRDLocation gen( NewShared< T_SRDLocation >( loc ) ); + gen->chain( E_SRDLocationChaining::CALLED , + depth , callLocation ); + el->chain( E_SRDLocationChaining::GENERATED , gen ); + } + loc.chain( E_SRDLocationChaining::EVALUATED , el ); + updated->add( std::move( token ) ); + } + + data.push( T_SRDPreprocessorState::C_UNSPECIFIED , + updated , data.top( ).outputPointer( ) ); + } ); + }; +} + +bool T_SRDPreprocessorState::checkCallArguments( + T_SRDList const& spec ) const +{ + auto const& pi( p< T_SRDPPStateImpl_ >( ) ); + const bool hasOptArgs( spec.size( ) > 0 + && spec[ spec.size( ) - 1 ].type( ) == E_SRDTokenType::LIST ); + const uint32_t reqArgs( spec.size( ) - ( hasOptArgs ? 1 : 0 ) ); + auto const& argVals( *pi.output_ ); + if ( argVals.size( ) < reqArgs ) { + p< T_SRDPPStateImpl_ >( ).data_.addError( + *pi.initialLocation_ , "not enough arguments" ); + return false; + } + if ( argVals.size( ) > reqArgs && !hasOptArgs ) { + p< T_SRDPPStateImpl_ >( ).data_.addError( + *pi.initialLocation_ , "too many arguments" ); + return false; + } + return true; +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorState::handleEndOfList( + T_SRDPreprocessorState& state ) +{ + auto& spi( state.p< T_SRDPPStateImpl_ >( ) ); + auto& output( *( spi.data_.top( ).p< T_SRDPPStateImpl_ >( ).output_ ) ); + uint32_t const pos( output.add( T_SRDToken::List( std::move( *spi.output_ ) ) ) ); + output[ pos ].location( std::move( *spi.initialLocation_ ) ); +} + + +/*= T_SRDPreprocessorScopes ==================================================*/ + +constexpr char T_SRDPreprocessorScopes::C_SET_; +constexpr char T_SRDPreprocessorScopes::C_BLESSED_; + +T_SRDPreprocessorScopes::T_SRDPreprocessorScopes( ) + : depth_( 0 ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorScopes::exit( ) +{ + assert( depth_ > 0 ); + + // Find all items for the current depth + const uint32_t size( itemDepth_.size( ) ); + const uint32_t nItems( ( [&]() { + uint32_t nItems( 0 ); + while ( size - nItems > 0 && itemDepth_[ size - nItems - 1 ] == depth_ ) { + nItems ++; + } + return nItems; + } )( ) ); + + // Remove items + for ( uint32_t i = 0 ; i < nItems ; i ++ ) { + const uint32_t ii( size - i - 1 ); + const uint32_t mainIndex( reverse_[ ii ] ); + const uint32_t chain( chain_[ ii ] ); + + // We're always removing from the end of the array, so removeSwap + // isn't needed. + if ( chain == T_HashIndex::INVALID_INDEX ) { + index_.remove( mainIndex ); + keys_.remove( mainIndex ); + indices_.remove( mainIndex ); + } else { + reverse_[ chain ] = mainIndex; + indices_[ mainIndex ] = chain; + } + + itemDepth_.remove( ii ); + chain_.remove( ii ); + reverse_.remove( ii ); + flags_.remove( ii ); + contents_.remove( ii ); + } + + depth_ --; +} + +void T_SRDPreprocessorScopes::clear( ) +{ + if ( depth_ == 0 ) { + index_.clear( ); + keys_.clear( ); + indices_.clear( ); + itemDepth_.clear( ); + chain_.clear( ); + reverse_.clear( ); + flags_.clear( ); + contents_.clear( ); + return; + } + + const uint32_t nEntries( keys_.size( ) ); + for ( uint32_t i = 0 ; i < nEntries ; i ++ ) { + const uint32_t idx( indices_[ i ] ); + if ( ( flags_[ idx ] & C_SET_ ) == 0 ) { + continue; + } + + if ( itemDepth_[ idx ] == depth_ ) { + flags_[ idx ] = 0; + contents_[ idx ] = NewShared< T_SRDList >( ); + } else { + addChained( i , idx , 0 , NewShared< T_SRDList >( ) ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorScopes::setupCallScope( + T_SRDList const& spec , + T_SRDList const& values , + uint32_t start ) +{ + const uint32_t nEntries( indices_.size( ) ); + enter( ); + + // Add parameters + const auto nSpec( spec.size( ) ); + for ( uint32_t i = 0 ; i < nSpec ; i ++ ) { + auto const& argSpec( spec[ i ] ); + + if ( argSpec.type( ) == E_SRDTokenType::WORD ) { + assert( i < values.size( ) ); + bool ok( set( false , argSpec.stringValue( ) , values , + i + start , i + start ) ); + assert( ok ); + continue; + } + + assert( argSpec.type( ) == E_SRDTokenType::LIST + && i == nSpec - 1 ); + + T_SRDList const& optArgs( argSpec.list( ) ); + const auto nOpt( optArgs.size( ) ); + for ( uint32_t j = 0 ; j < nOpt ; j ++ ) { + auto const& optArg( optArgs[ j ] ); + assert( optArg.type( ) == E_SRDTokenType::WORD ); + + bool ok( set( false , optArg.stringValue( ) , + values , i + j + start , + j == nOpt - 1 ? UINT32_MAX : i + j + start ) ); + assert( ok ); + } + } + + // Hide all variables from previous scopes + for ( uint32_t i = 0 ; i < nEntries ; i ++ ) { + if ( keys_[ i ].first ) { + continue; + } + + const uint32_t index( indices_[ i ] ); + if ( itemDepth_[ index ] == depth_ ) { + continue; + } + + const char flags( flags_[ index ] ); + if ( ( flags & C_SET_ ) == 0 || ( flags & C_BLESSED_ ) != 0 ) { + continue; + } + addChained( i , index , 0 , NewShared< T_SRDList >( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +RPC_SRDList T_SRDPreprocessorScopes::get( + const bool macro , + T_String const& name ) const +{ + const uint32_t idx( find( macro , name ) ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } + const uint32_t vIndex( indices_[ idx ] ); + if ( ( flags_[ vIndex ] & C_SET_ ) == 0 ) { + return nullptr; + } + return RPC_SRDList( contents_[ vIndex ] ); +} + +bool T_SRDPreprocessorScopes::canSet( + const bool macro , + T_String const& name ) const +{ + const uint32_t idx( find( macro , name ) ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return true; + } + const uint32_t vIndex( indices_[ idx ] ); + return ( itemDepth_[ vIndex ] != depth_ + || ( flags_[ vIndex ] & C_SET_ ) == 0 ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDPreprocessorScopes::set( + const bool macro , + T_String const& name , + T_SRDList const& value , + const uint32_t from , + const uint32_t to ) +{ + const uint32_t hash( ComputeHash( name ) ^ ( macro ? 0x55555555 : 0 ) ); + const uint32_t idx( find( hash , macro , name ) ); + + // If no entry exists, create a new one + if ( idx == T_HashIndex::INVALID_INDEX ) { + index_.add( hash ); + + const uint32_t mainIndex( keys_.size( ) ); + keys_.add( std::pair< bool , T_String >( macro , name ) ); + indices_.add( itemDepth_.size( ) ); + + itemDepth_.add( depth_ ); + chain_.add( T_HashIndex::INVALID_INDEX ); + reverse_.add( mainIndex ); + flags_.add( C_SET_ ); + contents_.add( NewShared< T_SRDList >( + value.copyRange( from , to ) ) ); + return true; + } + + const uint32_t previous( indices_[ idx ] ); + + // If there's an entry at the current depth, try to update it. + if ( itemDepth_[ previous ] == depth_ ) { + if ( ( flags_[ previous ] & C_SET_ ) != 0 ) { + return false; + } + flags_[ previous ] = C_SET_; + contents_[ previous ] = NewShared< T_SRDList >( + value.copyRange( from , to ) ); + return true; + } + + // Otherwise we need to chain it + addChained( idx , previous , C_SET_ , NewShared< T_SRDList >( + value.copyRange( from , to ) ) ); + return true; +} + +bool T_SRDPreprocessorScopes::unset( + const bool macro , + T_String const& name ) +{ + const uint32_t idx( find( macro , name ) ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + const uint32_t vIndex( indices_[ idx ] ); + if ( ( flags_[ vIndex ] & C_SET_ ) == 0 ) { + return false; + } + if ( itemDepth_[ vIndex ] == depth_ ) { + if ( depth_ == 0 ) { + removeItem( idx ); + } else { + contents_[ vIndex ] = NewShared< T_SRDList >( ); + flags_[ vIndex ] = 0; + } + return true; + } + addChained( idx , vIndex , 0 , NewShared< T_SRDList >( ) ); + return true; +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDPreprocessorScopes::isBlessed( + T_String const& name ) const +{ + const uint32_t idx( find( false , name ) ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + const uint32_t vIndex( indices_[ idx ] ); + return ( ( flags_[ vIndex ] & C_BLESSED_ ) != 0 ); +} + +bool T_SRDPreprocessorScopes::bless( + T_String const& name ) +{ + const uint32_t idx( find( false , name ) ); + if ( idx == T_HashIndex::INVALID_INDEX ) { + return false; + } + + const uint32_t vIndex( indices_[ idx ] ); + const char cFlags( flags_[ vIndex ] ); + if ( ( cFlags & C_SET_ ) == 0 || ( cFlags & C_BLESSED_ ) != 0 ) { + return false; + } + + if ( itemDepth_[ vIndex ] == depth_ ) { + flags_[ vIndex ] = cFlags | C_BLESSED_; + } else { + addChained( idx , vIndex , cFlags | C_BLESSED_ , NewShared< T_SRDList >( contents_[ vIndex ] ) ); + } + return true; +} + +/*----------------------------------------------------------------------------*/ + +T_Array< T_String > T_SRDPreprocessorScopes::list( + const bool macros ) const +{ + T_Array< T_String > result; + const uint32_t nEntries( keys_.size( ) ); + for ( uint32_t i = 0 ; i < nEntries ; i ++ ) { + auto const& k( keys_[ i ] ); + if ( k.first != macros ) { + continue; + } + if ( ( flags_[ indices_[ i ] ] & C_SET_ ) == 0 ) { + continue; + } + result.add( k.second ); + } + return result; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t T_SRDPreprocessorScopes::find( + const bool macro , + T_String const& name ) const +{ + const uint32_t hash( ComputeHash( name ) ^ ( macro ? 0x55555555 : 0 ) ); + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + auto const& key( keys_[ idx ] ); + if ( key.first == macro && key.second == name ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + +uint32_t T_SRDPreprocessorScopes::find( + const uint32_t hash , + const bool macro , + T_String const& name ) const +{ + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + auto const& key( keys_[ idx ] ); + if ( key.first == macro && key.second == name ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorScopes::addChained( + const uint32_t index , + const uint32_t previous , + const char flags , + SP_SRDList&& contents ) +{ + const uint32_t newIndex( itemDepth_.size( ) ); + itemDepth_.add( depth_ ); + chain_.add( previous ); + reverse_.add( index ); + flags_.add( flags ); + contents_.add( std::move( contents ) ); + indices_[ index ] = newIndex; + reverse_[ previous ] = newIndex; +} + +void T_SRDPreprocessorScopes::removeItem( + const uint32_t index ) +{ + // We only ever do that at depth 0, so we don't have to worry about + // chaining (we still need to update the index for the element that + // gets swapped, though). + + assert( depth_ == 0 ); + assert( keys_.size( ) == itemDepth_.size( ) ); + + itemDepth_.removeSwap( index ); + flags_.removeSwap( index ); + chain_.removeSwap( index ); + reverse_.removeSwap( index ); + contents_.removeSwap( index ); + + keys_.removeSwap( index ); + indices_.removeSwap( index ); + index_.remove( index ); + + if ( index != indices_.size( ) ) { + reverse_[ index ] = index; + indices_[ index ] = index; + } +} + + +/*= T_SRDPPDataImpl_ =========================================================*/ + +namespace { +struct T_SRDPPDataImpl_ { + T_SRDPreprocessor& preprocessor; + + SP_SRDPreprocessorScopes scopes; + T_Array< RP_SRDErrors > errors; + + T_Array< SP_SRDPreprocessorState > stack; + T_Array< SP_SRDLocation > callStack; + T_Array< uint32_t > recursionDepths; + + T_SRDPPDataImpl_( + T_SRDPreprocessor& preprocessor , + T_SRDErrors& errors , + SP_SRDPreprocessorScopes scopes ) noexcept; + ~T_SRDPPDataImpl_( ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_SRDPPDataImpl_::T_SRDPPDataImpl_( + T_SRDPreprocessor& preprocessor , + T_SRDErrors& errors , + SP_SRDPreprocessorScopes scopes ) noexcept + : preprocessor( preprocessor ) , + scopes( std::move( scopes ) ) , + errors( 32 ) +{ + this->errors.add( &errors ); +} + +T_SRDPPDataImpl_::~T_SRDPPDataImpl_( ) +{ + const uint32_t nErrors( errors.size( ) ); + for ( uint32_t i = 1 ; i < nErrors ; i ++ ) { + delete errors[ i ]; + } + + try { + T_SRDErrors& elst( *errors[ 0 ] ); + const uint32_t ss( stack.size( ) ); + for ( uint32_t i = 1 ; i < ss ; i ++ ) { + T_SRDPreprocessorState & state( *stack[ i ] ); + if ( state.initialLocation( ) ) { + elst.add( "unterminated list" , *state.initialLocation( ) ); + } + state.checkUnterminated( elst ); + } + } catch ( X_SRDErrors const& ) { + // Just stop adding errors + } +} + + +/*= T_SRDPreprocessorData ====================================================*/ + +T_SRDPreprocessorData::T_SRDPreprocessorData( + T_SRDPreprocessor& preprocessor , + T_SRDErrors& errors , + SP_SRDPreprocessorScopes scopes ) + : A_PrivateImplementation( new T_SRDPPDataImpl_( + preprocessor , errors , std::move( scopes ) ) ) +{ + push( ); +} + +/*----------------------------------------------------------------------------*/ + +T_SRDPreprocessor& T_SRDPreprocessorData::preprocessor( ) const +{ + return p< T_SRDPPDataImpl_ >( ).preprocessor; +} + +T_SRDList& T_SRDPreprocessorData::output( ) const +{ + return p< T_SRDPPDataImpl_ >( ).stack[ 0 ]->output( ); +} + +T_SRDPreprocessorScopes& T_SRDPreprocessorData::scopes( ) +{ + return *( p< T_SRDPPDataImpl_ >( ).scopes ); +} + +T_SRDPreprocessorScopes const& T_SRDPreprocessorData::scopes( ) const +{ + return *( p< T_SRDPPDataImpl_ >( ).scopes ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorData::push( + SP_SRDPreprocessorState&& state ) +{ + p< T_SRDPPDataImpl_ >( ).stack.add( std::move( state ) ); +} + +T_SRDPreprocessorState& T_SRDPreprocessorData::top( ) const +{ + auto const& stack( p< T_SRDPPDataImpl_ >( ).stack ); + return *( stack[ stack.size( ) - 1 ] ); +} + +bool T_SRDPreprocessorData::topLevel( ) const +{ + return p< T_SRDPPDataImpl_ >( ).stack.size( ) == 1; +} + +inline SP_SRDLocation T_SRDPreprocessorData::callLocation( ) const noexcept +{ + auto const& cs( p< T_SRDPPDataImpl_ >( ).callStack ); + if ( cs.size( ) ) { + return cs[ cs.size( ) - 1 ]; + } + return SP_SRDLocation( ); +} + +SP_SRDPreprocessorState T_SRDPreprocessorData::pop( ) +{ + auto& stack( p< T_SRDPPDataImpl_ >( ).stack ); + const auto last( stack.size( ) - 1 ); + SP_SRDPreprocessorState state( stack[ last ] ); + stack.remove( last ); + return state; +} + +void T_SRDPreprocessorData::interrupt( + const T_SRDPreprocessorState::E_Type type ) +{ + assert( type != T_SRDPreprocessorState::C_UNSPECIFIED + && type != T_SRDPreprocessorState::C_INNER ); + + auto& stack( p< T_SRDPPDataImpl_ >( ).stack ); + const uint32_t ss( stack.size( ) ); + uint32_t i( 0 ); + while ( i < ss ) { + auto& toRemove( *( stack[ ss - i - 1 ] ) ); + i ++; + toRemove.setExiting( type ); + if ( toRemove.type( ) == type ) { + break; + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorData::pushCallLocation( + SP_SRDLocation const& location ) noexcept +{ + auto& pi( p< T_SRDPPDataImpl_ >( ) ); + const auto css( pi.callStack.size( ) ); + if ( css ) { + auto& next( pi.callStack[ css - 1 ] ); + auto depth( pi.recursionDepths[ css - 1 ] ); + SP_SRDLocation append( NewShared< T_SRDLocation >( *location ) ); + if ( next->line( ) == location->line( ) + && next->character( ) == location->character( ) + && next->source( ) == location->source( ) ) { + pi.recursionDepths.add( depth + 1 ); + if ( next->isChained( ) ) { + auto& nc( next->chaining( ) ); + append->chain( E_SRDLocationChaining::CALLED , nc.depth , + nc.location ); + } + } else { + append->chain( E_SRDLocationChaining::CALLED , depth , next ); + pi.recursionDepths.add( 1 ); + } + pi.callStack.add( std::move( append ) ); + } else { + pi.callStack.add( location ); + pi.recursionDepths.add( 1 ); + } +} + +SP_SRDLocation T_SRDPreprocessorData::popCallLocation( ) noexcept +{ + auto& pi( p< T_SRDPPDataImpl_ >( ) ); + const auto css( pi.callStack.size( ) ); + SP_SRDLocation location( pi.callStack[ css - 1 ] ); + pi.callStack.remove( css - 1 ); + pi.recursionDepths.remove( css - 1 ); + return location; +} + +uint32_t T_SRDPreprocessorData::callRecursionDepth( ) const noexcept +{ + auto& pi( p< T_SRDPPDataImpl_ >( ) ); + const auto css( pi.callStack.size( ) ); + assert( css > 0 ); + return pi.recursionDepths[ css - 1 ]; +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDPreprocessorData::pushErrorContext( ) +{ + p< T_SRDPPDataImpl_ >( ).errors.add( new T_SRDErrors( ) ); +} + +RP_SRDErrors T_SRDPreprocessorData::popErrorContext( ) +{ + auto& errs( p< T_SRDPPDataImpl_ >( ).errors ); + const auto sz( errs.size( ) ); + assert( sz > 1 ); + + const RP_SRDErrors record( errs[ sz - 1 ] ); + errs.remove( sz - 1 ); + return record; +} + +T_SRDErrors& T_SRDPreprocessorData::errorContext( ) const +{ + auto const& errs( p< T_SRDPPDataImpl_ >( ).errors ); + return *( errs[ errs.size( ) - 1 ] ); +} + +void T_SRDPreprocessorData::addError( + T_SRDLocation const& location , + T_String const& text ) +{ + auto& pi( p< T_SRDPPDataImpl_ >( ) ); + auto& errs( pi.errors ); + const auto last( errs.size( ) - 1 ); + RP_SRDErrors errData( errs[ last ] ); + + const auto css( pi.callStack.size( ) ); + if ( css == 0 || location.isChained( ) ) { + errData->add( text , location ); + } else { + T_SRDLocation l( location ); + l.chain( E_SRDLocationChaining::CALLED , callRecursionDepth( ) , + pi.callStack[ css - 1 ] ); + errData->add( text , l ); + } +} + + +/*= T_SRDPreprocessor ========================================================*/ + +T_SRDPreprocessor::T_SRDPreprocessor( + T_SRDPreprocessorConfig const& config , + A_SRDReaderTarget& target , + SP_SRDPreprocessorScopes scopes ) + : config_( config ) , target_( target ) , scopes_( scopes ) +{ } + +void T_SRDPreprocessor::start( T_SRDErrors& errors ) +{ + if ( scopes_ ) { + data_ = NewOwned< T_SRDPreprocessorData >( *this , errors , scopes_ ); + } else { + data_ = NewOwned< T_SRDPreprocessorData >( *this , errors ); + } + outPos_ = 0; + outDepth_ = 0; + target_.start( errors ); +} + +void T_SRDPreprocessor::push( + T_SRDErrors& errors , + T_SRDToken&& token ) +{ + while ( !data_->topLevel( ) && data_->top( ).isExiting( ) ) { + data_->top( ).terminate( ); + } + + // Process the new token + if ( !data_->top( ).isExiting( ) ) { + data_->top( ).processToken( token ); + } + + // Try to process as much as possible from the stack + while ( 1 ) { + auto& top( data_->top( ) ); + if ( top.isExiting( ) ) { + if ( data_->topLevel( ) ) { + break; + } + top.terminate( ); + continue; + } + + if ( !top.hasInput( ) ) { + break; + } + if ( top.isFinished( ) ) { + top.terminate( ); + } else { + top.processNext( ); + } + } + + // Send output to the target + auto& outBuffer( data_->output( ) ); + const uint32_t bufSize( outBuffer.size( ) ); + assert( outPos_ <= bufSize ); + uint32_t remove = 0; + for ( uint32_t i = outPos_ ; i < bufSize ; i ++ ) { + auto& token( outBuffer[ i ] ); + const auto tt( token.type( ) ); + if ( tt == E_SRDTokenType::START ) { + outDepth_ ++; + + } else if ( tt == E_SRDTokenType::END ) { + assert( outDepth_ > 0 ); + outDepth_ --; + if ( outDepth_ == 0 ) { + for ( uint32_t j = 0 ; j <= i ; j ++ ) { + sendOutput( errors , outBuffer[ j ] ); + } + remove = i + 1; + outPos_ = i; + } + + } else if ( outDepth_ == 0 ) { + sendOutput( errors , token ); + remove ++; + } + } + outPos_ = bufSize; + if ( remove != 0 ) { + outBuffer = outBuffer.moveRange( remove , bufSize ); + outPos_ -= remove; + + target_.push( errors , T_SRDToken::Flush( ) ); + } +} + +void T_SRDPreprocessor::end( + T_SRDErrors& errors ) +{ + data_ = T_OwnPtr< T_SRDPreprocessorData >( ); + target_.end( errors ); +} + +void T_SRDPreprocessor::sendOutput( + T_SRDErrors& errors , + T_SRDToken& token ) +{ + if ( token.type( ) == E_SRDTokenType::LIST ) { + { + T_SRDToken lStart( T_SRDToken::ListStart( ) ); + lStart.copyLocationOf( token ); + target_.push( errors , std::move( lStart ) ); + } + { + auto& list( token.list( ) ); + const auto n( list.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + sendOutput( errors , list[ i ] ); + } + } + { + T_SRDToken lEnd( T_SRDToken::ListEnd( ) ); + lEnd.copyLocationOf( token ); + target_.push( errors , std::move( lEnd ) ); + } + + } else { + target_.push( errors , std::move( token ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_SRDPreprocessor::isValidFunction( + T_SRDToken const& token ) +{ + if ( token.type( ) != E_SRDTokenType::LIST ) { + return false; + } + + auto const& list( token.list( ) ); + if ( list.size( ) == 0 ) { + return false; + } + + auto const& ft( list[ 0 ] ); + if ( ft.type( ) != E_SRDTokenType::LIST ) { + return false; + } + + auto const& pl( ft.list( ) ); + const auto n( pl.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& p( pl[ i ] ); + const auto tt( p.type( ) ); + + if ( tt == E_SRDTokenType::WORD ) { + // Check for duplicate names + auto const& pn( p.stringValue( ) ); + for ( uint32_t j = 0 ; j < i ; j ++ ) { + if ( pl[ j ].stringValue( ) == pn ) { + return false; + } + } + continue; + + } else if ( tt != E_SRDTokenType::LIST ) { + return false; + } + + // Must be the last entry + if ( i != n - 1 ) { + return false; + } + + auto const& opl( p.list( ) ); + const auto no( opl.size( ) ); + if ( no == 0 ) { + return false; + } + for ( uint32_t j = 0 ; j < no ; j ++ ) { + auto const& op( opl[ j ] ); + if ( op.type( ) != E_SRDTokenType::WORD ) { + return false; + } + + auto const& opn( op.stringValue( ) ); + // Check for duplicates in main list + for ( uint32_t k = 0 ; k < i ; k ++ ) { + if ( pl[ k ].stringValue( ) == opn ) { + return false; + } + } + // Check for duplicates in optional args + for ( uint32_t k = 0 ; k < j ; k ++ ) { + if ( opl[ k ].stringValue( ) == opn ) { + return false; + } + } + } + } + + return true; +} diff --git a/src/SRDText.cc b/src/SRDText.cc new file mode 100644 index 0000000..d703ea4 --- /dev/null +++ b/src/SRDText.cc @@ -0,0 +1,1329 @@ +/******************************************************************************/ +/* SRD - TEXT STORAGE *********************************************************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +/*= T_SRDLexerPrivate_ =======================================================*/ + +namespace { +struct T_SRDLexerPrivate_ +{ + // E_State_ - Lexer states + enum class E_State_ { + BASE , // Base state + COMMENT_SL_START , // Start of single-line comment + COMMENT_SL , // In single-line comment + COMMENT_ML_START , // Start of multi-line comment + COMMENT_ML , // In multi-line comment + STRING , // In a quoted string + STRING_ESCAPE , // In string, after backslash + STRING_UTF8 , // In string, after \u/\U + INITIAL_DASH , // After an initial '-' + NB_INT_PART , // Number, integral part + NB_FRAC_PART_M , // Number, fractional part (mandatory) + NB_FRAC_PART , // Number, fractional part + NB_EXP_START , // Number, exponent start + NB_EXP_DASH , // Number, exponent, after '-' + NB_EXP , // Number, exponent + WORD , // Inside a word + WORD_AFTER_DASH , // Word, right after a '-' + VAR , // Inside a variable name + VAR_AFTER_DASH , // Variable name, right after a '-' + ER_WORD , // Word/variable error recovery + BINARY , // In a binary data array + BINARY_DIGIT , // In a binary data array, after a digit + BINARY_SEPARATOR , // In a binary data array, after a second digit + ER_BINARY , // Binary data error recovery + }; + + const T_String name_; + T_SRDErrors& errors_; + A_SRDReaderTarget& target_; + + E_State_ state_; + T_StringBuilder buffer_; + T_StringBuilder stringBuffer_; + T_Array< uint8_t > binBuffer_; + uint32_t depth_; + uint32_t line_; + size_t char_; + uint32_t tokLine_; + size_t tokChar_; + + uint32_t utf8Left_; + uint32_t utf8Char_; + + //---------------------------------------------------------------------- + + T_SRDLexerPrivate_( T_String const& name , + T_SRDErrors& errors , + A_SRDReaderTarget& target ); + + //---------------------------------------------------------------------- + + // Various helpers + void tokStart( T_Character c ); + void push( T_SRDToken&& token ); + void error( char const* string ); + + //---------------------------------------------------------------------- + + // Output + void pushString( ); + void pushComment( ); + void pushInteger( ); + void pushFloat( ); + void pushWord( ); + void pushVar( ); + void pushBinary( ); + + //---------------------------------------------------------------------- + + // State implementations + void pcBase( T_Character c ); + void pcCommentSLStart( T_Character c ); + void pcCommentSL( T_Character c ); + void pcCommentMLStart( T_Character c ); + void pcCommentML( T_Character c ); + void pcString( T_Character c ); + void pcStringEscape( T_Character c ); + void pcStringUTF8( T_Character c ); + void pcInitialDash( T_Character c ); + void pcNbIntPart( T_Character c ); + void pcNbFracPart( T_Character c , bool mandatory ); + void pcNbExp( T_Character c ); + void pcWord( T_Character c , bool afterDash , bool isVar ); + void pcBinary( T_Character c ); + void pcBinaryDigit( T_Character c ); + void pcBinarySeparator( T_Character c ); + + //---------------------------------------------------------------------- + + // Main processing + void processCharacter( T_Character c ); + void processEnd( ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_SRDLexerPrivate_::T_SRDLexerPrivate_( + T_String const& name , + T_SRDErrors& errors , + A_SRDReaderTarget& target ) + : name_( name ) , errors_( errors ) , target_( target ) , + state_( E_State_::BASE ) , line_( 1 ) , char_( 1 ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDLexerPrivate_::tokStart( T_Character c ) +{ + tokLine_ = line_; + tokChar_ = char_; + buffer_.clear( ); + stringBuffer_.clear( ); + depth_ = 0; + buffer_ << c; +} + +void T_SRDLexerPrivate_::push( T_SRDToken&& token ) +{ + token.location( name_ , tokLine_ , tokChar_ ); + if ( !token.hasFullText( ) && buffer_.size( ) != 0 ) { + token.setFullText( std::move( buffer_ ) ); + } + target_.push( errors_ , std::move( token ) ); +} + +inline void T_SRDLexerPrivate_::error( char const* string ) +{ + errors_.add( string , name_ , line_ , char_ ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDLexerPrivate_::pushString( ) +{ + push( T_SRDToken::String( std::move( stringBuffer_ ) ) ); +} + +inline void T_SRDLexerPrivate_::pushComment( ) +{ + push( T_SRDToken::Comment( std::move( stringBuffer_ ) ) ); +} + +void T_SRDLexerPrivate_::pushInteger( ) +{ + bool ok; + auto v( buffer_.toInteger( &ok ) ); + if ( !ok ) { + error( "invalid integer value" ); + push( T_SRDToken::Integer( 0 ) ); + } else { + push( T_SRDToken::AutoInteger( v ) ); + } +} + +void T_SRDLexerPrivate_::pushFloat( ) +{ + stringBuffer_ << buffer_ << '\0'; + + errno = 0; + auto v = strtod( stringBuffer_.data( ) , nullptr ); + if ( errno != 0 ) { + error( "invalid floating point value" ); + push( T_SRDToken::Float( 0 ) ); + } else { + push( T_SRDToken::Float( v ) ); + } +} + +void T_SRDLexerPrivate_::pushWord( ) +{ + T_String w( std::move( stringBuffer_ ) ); + w.usePool( ); + push( T_SRDToken::Word( std::move( w ) ) ); +} + +void T_SRDLexerPrivate_::pushVar( ) +{ + T_String w( std::move( stringBuffer_ ) ); + w.usePool( ); + push( T_SRDToken::Variable( std::move( w ) ) ); +} + +void T_SRDLexerPrivate_::pushBinary( ) +{ + if ( binBuffer_.size( ) ) { + push( T_SRDToken::Binary( &binBuffer_[ 0 ] , binBuffer_.size( ) ) ); + } else { + push( T_SRDToken::Binary( (uint8_t*) nullptr , 0 ) ); + } + binBuffer_.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDLexerPrivate_::pcBase( T_Character c ) +{ + switch ( c.codepoint ) { + + case '(': + tokStart( c ); + push( T_SRDToken::ListStart( ) ); + break; + + case ')': + tokStart( c ); + push( T_SRDToken::ListEnd( ) ); + break; + + case '"': state_ = E_State_::STRING; + break; + + case '$': state_ = E_State_::VAR_AFTER_DASH; + break; + + case '-': state_ = E_State_::INITIAL_DASH; + break; + + case '.': state_ = E_State_::NB_FRAC_PART_M; + break; + + case '#': state_ = E_State_::COMMENT_SL_START; + break; + + case '{': + state_ = E_State_::COMMENT_ML_START; + break; + + case '[': + state_ = E_State_::BINARY; + break; + + default: + if ( c.isAlpha( ) ) { + state_ = E_State_::WORD; + + } else if ( c.isNumeric( ) ) { + state_ = E_State_::NB_INT_PART; + + } else if ( !c.isWhitespace( ) ) { + error( "unexpected character" ); + } + break; + } + + if ( state_ != E_State_::BASE ) { + tokStart( c ); + if ( state_ == E_State_::WORD ) { + stringBuffer_ << c; + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDLexerPrivate_::pcCommentSLStart( T_Character c ) +{ + if ( c == '\n' ) { + buffer_.clear( ); + state_ = E_State_::BASE; + return; + } + + buffer_ << c; + if ( !c.isWhitespace( ) ) { + state_ = E_State_::COMMENT_SL; + stringBuffer_ << c; + } +} + +void T_SRDLexerPrivate_::pcCommentSL( T_Character c ) +{ + if ( c == '\n' ) { + pushComment( ); + state_ = E_State_::BASE; + } else { + buffer_ << c; + stringBuffer_ << c; + } +} + +void T_SRDLexerPrivate_::pcCommentMLStart( T_Character c ) +{ + if ( c.isWhitespace( ) ) { + buffer_ << c; + } else if ( c == '}' ) { + state_ = E_State_::BASE; + } else { + state_ = E_State_::COMMENT_ML; + pcCommentML( c ); + } +} + +void T_SRDLexerPrivate_::pcCommentML( T_Character c ) +{ + buffer_ << c; + if ( c == '}' && depth_ == 0 ) { + pushComment( ); + state_ = E_State_::BASE; + } else { + if ( c == '{' ) { + depth_ ++; + } else if ( c == '}' ) { + depth_ --; + } + stringBuffer_ << c; + } +} + +void T_SRDLexerPrivate_::pcString( T_Character c ) +{ + if ( c == '\n' ) { + pushString( ); + error( "unterminated string" ); + state_ = E_State_::BASE; + } else if ( c.isControl( ) && c != '\t' ) { + error( "control character in string" ); + } else { + buffer_ << c; + if ( c == '"' ) { + pushString( ); + state_ = E_State_::BASE; + } else if ( c == '\\' ) { + state_ = E_State_::STRING_ESCAPE; + } else { + stringBuffer_ << c; + } + } +} + +void T_SRDLexerPrivate_::pcStringEscape( T_Character c ) +{ + if ( c == '\n' ) { + pushString( ); + error( "unterminated string" ); + state_ = E_State_::BASE; + } else { + if ( c.isControl( ) && c != '\t' ) { + error( "control character in string" ); + } else { + buffer_ << c; + if ( c == 'n' ) { + stringBuffer_ << '\n'; + } else if ( c == 'b' ) { + stringBuffer_ << char( 8 ); + } else if ( c == 'f' ) { + stringBuffer_ << char( 12 ); + } else if ( c == 'r' ) { + stringBuffer_ << '\r'; + } else if ( c == 't' ) { + stringBuffer_ << '\t'; + } else if ( c.toLower( ) == 'u' ) { + state_ = E_State_::STRING_UTF8; + utf8Char_ = 0; + utf8Left_ = ( c == 'u' ) ? 4 : 8; + return; + } else { + stringBuffer_ << c; + } + } + state_ = E_State_::STRING; + } +} + +void T_SRDLexerPrivate_::pcStringUTF8( T_Character c ) +{ + if ( c == '\n' ) { + pushString( ); + error( "unterminated string" ); + state_ = E_State_::BASE; + } else if ( c.isControl( ) ) { + error( "control character in UTF-8 sequence" ); + state_ = E_State_::STRING; + } else if ( c == '"' ) { + pushString( ); + error( "incomplete UTF-8 sequence" ); + state_ = E_State_::BASE; + } else { + buffer_ << c; + uint32_t value; + if ( c.isNumeric( ) ) { + value = c - '0'; + } else if ( c.isAlpha( ) ) { + value = c.toUpper( ) - 55; + if ( value > 15 ) { + error( "invalid UTF-8 sequence" ); + state_ = E_State_::STRING; + return; + } + } else { + error( "invalid UTF-8 sequence" ); + state_ = E_State_::STRING; + return; + } + utf8Char_ = ( utf8Char_ << 4 ) | value; + utf8Left_ --; + if ( utf8Left_ == 0 ) { + T_Character c( utf8Char_ ); + if ( c.isValid( ) ) { + stringBuffer_ << c; + } else { + error( "invalid UTF-8 codepoint" ); + } + state_ = E_State_::STRING; + } + } +} + +void T_SRDLexerPrivate_::pcInitialDash( T_Character c ) +{ + if ( c.isNumeric( ) ) { + state_ = E_State_::NB_INT_PART; + pcNbIntPart( c ); + } else if ( c.isAlpha( ) ) { + state_ = E_State_::WORD_AFTER_DASH; + stringBuffer_ << '-'; + pcWord( c , true , false ); + } else if ( c == '.' ) { + state_ = E_State_::NB_FRAC_PART_M; + buffer_ << c; + } else { + error( "unexpected character" ); + state_ = E_State_::BASE; + pcBase( c ); + } +} + +void T_SRDLexerPrivate_::pcNbIntPart( T_Character c ) +{ + if ( c.isNumeric( ) ) { + buffer_ << c; + } else if ( c == '.' ) { + buffer_ << c; + state_ = E_State_::NB_FRAC_PART; + } else if ( c == 'e' || c == 'E' ) { + buffer_ << c; + state_ = E_State_::NB_EXP_START; + } else { + const bool ok = c.isWhitespace( ) || c == '(' || c == ')' + || c == '{' || c == '#'; + if ( !ok ) { + error( "unexpected character" ); + } + + pushInteger( ); + + state_ = E_State_::BASE; + pcBase( c ); + } +} + +void T_SRDLexerPrivate_::pcNbFracPart( T_Character c , bool mandatory ) +{ + if ( c.isNumeric( ) ) { + buffer_ << c; + state_ = E_State_::NB_FRAC_PART; + } else { + if ( mandatory ) { + error( "fractional part expected" ); + } + + if ( c == 'e' || c == 'E' ) { + buffer_ << c; + state_ = E_State_::NB_EXP_START; + } else { + const bool ok = c.isWhitespace( ) || c == '(' + || c == ')' || c == '{' || c == '#'; + if ( !ok ) { + error( "unexpected character" ); + } + + pushFloat( ); + + state_ = E_State_::BASE; + pcBase( c ); + } + } +} + +void T_SRDLexerPrivate_::pcNbExp( T_Character c ) +{ + if ( state_ == E_State_::NB_EXP_START && ( c == '-' || c == '+' ) ) { + buffer_ << c; + state_ = E_State_::NB_EXP_DASH; + return; + } + + if ( c.isNumeric( ) ) { + buffer_ << c; + state_ = E_State_::NB_EXP; + return; + } + + if ( state_ != E_State_::NB_EXP ) { + error( "exponent expected" ); + } + + const bool ok = c.isWhitespace( ) || c == '(' + || c == ')' || c == '{' || c == '#'; + if ( !ok ) { + error( "unexpected character" ); + } + + stringBuffer_ << buffer_ << '\0'; + auto v = strtod( stringBuffer_.data( ) , nullptr ); + if ( errno == ERANGE ) { + error( "invalid floating point value" ); + push( T_SRDToken::Float( 0 ) ); + } else { + push( T_SRDToken::Float( v ) ); + } + + state_ = E_State_::BASE; + pcBase( c ); +} + +void T_SRDLexerPrivate_::pcWord( T_Character c , bool afterDash , bool isVar ) +{ + if ( c.isAlpha( ) || c.isNumeric( ) || c == '-' ) { + if ( afterDash && !c.isAlpha( ) ) { + error( "letter expected" ); + state_ = E_State_::ER_WORD; + stringBuffer_.clear( ); + stringBuffer_ << "invalid"; + if ( isVar ) { + pushVar( ); + } else { + pushWord( ); + } + return; + } + buffer_ << c; + stringBuffer_ << c; + state_ = isVar + ? ( c == '-' + ? E_State_::VAR_AFTER_DASH + : E_State_::VAR ) + : ( c == '-' + ? E_State_::WORD_AFTER_DASH + : E_State_::WORD ); + return; + } + + const bool ok = c.isWhitespace( ) || c == '(' || c == ')' || c == '{' + || c == '#'; + if ( !ok ) { + error( "unexpected character" ); + } else if ( afterDash ) { + error( isVar ? "incomplete variable name" : "incomplete word" ); + } + + if ( !ok || afterDash ) { + stringBuffer_.clear( ); + stringBuffer_ << "invalid"; + } + + if ( isVar ) { + pushVar( ); + } else { + pushWord( ); + } + state_ = E_State_::BASE; + pcBase( c ); +} + +void T_SRDLexerPrivate_::pcBinary( + T_Character c ) +{ + buffer_ << c; + if ( c == ']' ) { + pushBinary( ); + state_ = E_State_::BASE; + } else if ( c.isAlpha( ) ) { + T_Character l( c.toUpper( ) ); + if ( l > 'F' ) { + error( "invalid binary digit" ); + state_ = E_State_::ER_BINARY; + } else { + binBuffer_.add( ( uint32_t( l ) - 55 ) * 16 ); + state_ = E_State_::BINARY_DIGIT; + } + } else if ( c.isNumeric( ) ) { + binBuffer_.add( ( uint32_t( c ) - 48 ) * 16 ); + state_ = E_State_::BINARY_DIGIT; + } else if ( !c.isWhitespace( ) ) { + error( "invalid binary digit" ); + state_ = E_State_::ER_BINARY; + } +} + +void T_SRDLexerPrivate_::pcBinaryDigit( + T_Character c ) +{ + buffer_ << c; + if ( c.isAlpha( ) ) { + T_Character l( c.toUpper( ) ); + if ( l > 'F' ) { + error( "invalid binary digit" ); + state_ = E_State_::ER_BINARY; + } else { + const auto i( binBuffer_.size( ) - 1 ); + binBuffer_[ i ] += uint32_t( l ) - 55; + state_ = E_State_::BINARY; + } + } else if ( c.isNumeric( ) ) { + const auto i( binBuffer_.size( ) - 1 ); + binBuffer_[ i ] += uint32_t( c ) - 48; + state_ = E_State_::BINARY; + } else { + error( "invalid binary digit" ); + if ( c.isWhitespace( ) ) { + state_ = E_State_::BINARY; + } else if ( c == ']' ) { + pushBinary( ); + state_ = E_State_::BASE; + } else { + state_ = E_State_::ER_BINARY; + } + } +} + +void T_SRDLexerPrivate_::pcBinarySeparator( + T_Character c ) +{ + buffer_ << c; + if ( c.isWhitespace( ) ) { + state_ = E_State_::BINARY; + } else if ( c == ']' ) { + pushBinary( ); + state_ = E_State_::BASE; + } else { + error( "invalid binary digit" ); + state_ = E_State_::ER_BINARY; + } +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDLexerPrivate_::processCharacter( T_Character c ) +{ + switch ( state_ ) { + case E_State_::BASE: + pcBase( c ); + break; + + case E_State_::COMMENT_SL_START: + pcCommentSLStart( c ); + break; + + case E_State_::COMMENT_SL: + pcCommentSL( c ); + break; + + case E_State_::COMMENT_ML_START: + pcCommentMLStart( c ); + break; + + case E_State_::COMMENT_ML: + pcCommentML( c ); + break; + + case E_State_::STRING: + pcString( c ); + break; + + case E_State_::STRING_ESCAPE: + pcStringEscape( c ); + break; + + case E_State_::STRING_UTF8: + pcStringUTF8( c ); + break; + + case E_State_::INITIAL_DASH: + pcInitialDash( c ); + break; + + case E_State_::NB_INT_PART: + pcNbIntPart( c ); + break; + + case E_State_::NB_FRAC_PART_M: + pcNbFracPart( c , true ); + break; + + case E_State_::NB_FRAC_PART: + pcNbFracPart( c , false ); + break; + + case E_State_::NB_EXP_START: + case E_State_::NB_EXP_DASH: + case E_State_::NB_EXP: + pcNbExp( c ); + break; + + case E_State_::WORD: + pcWord( c , false , false ); + break; + + case E_State_::WORD_AFTER_DASH: + pcWord( c , true , false ); + break; + + case E_State_::VAR: + pcWord( c , false , true ); + break; + + case E_State_::VAR_AFTER_DASH: + pcWord( c , true , true ); + break; + + case E_State_::ER_WORD: + if ( !( c.isAlphanumeric( ) || c == '-' ) ) { + state_ = E_State_::BASE; + pcBase( c ); + } + break; + + case E_State_::BINARY: + pcBinary( c ); + break; + + case E_State_::BINARY_DIGIT: + pcBinaryDigit( c ); + break; + + case E_State_::BINARY_SEPARATOR: + pcBinarySeparator( c ); + break; + + case E_State_::ER_BINARY: + buffer_ << c; + if ( c == ']' ) { + pushBinary( ); + state_ = E_State_::BASE; + } else if ( c.isWhitespace( ) ) { + state_ = E_State_::BINARY; + } + break; + } + + if ( c == '\n' ) { + line_ ++; + char_ = 1; + } else { + char_ ++; + } +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDLexerPrivate_::processEnd( ) +{ + switch ( state_ ) { + + case E_State_::BASE: + case E_State_::ER_WORD: + case E_State_::COMMENT_SL_START: + break; + + case E_State_::STRING: + case E_State_::STRING_ESCAPE: + case E_State_::STRING_UTF8: + error( "unterminated string" ); + pushString( ); + break; + + case E_State_::COMMENT_SL: + pushComment( ); + break; + + case E_State_::COMMENT_ML: + pushComment( ); + + case E_State_::COMMENT_ML_START: + error( "unterminated multi-line comment" ); + break; + + case E_State_::NB_INT_PART: + pushInteger( ); + break; + + case E_State_::NB_FRAC_PART_M: + error( "fractional part expected" ); + + case E_State_::NB_FRAC_PART: + case E_State_::NB_EXP: + pushFloat( ); + break; + + case E_State_::NB_EXP_START: + case E_State_::NB_EXP_DASH: + error( "exponent expected" ); + pushFloat( ); + break; + + case E_State_::WORD_AFTER_DASH: + error( "incomplete word" ); + stringBuffer_.clear( ); + stringBuffer_ << "invalid"; + + case E_State_::WORD: + pushWord( ); + break; + + case E_State_::VAR_AFTER_DASH: + error( "incomplete variable name" ); + stringBuffer_.clear( ); + stringBuffer_ << "invalid"; + pushVar( ); + break; + + case E_State_::VAR: + if ( stringBuffer_.size( ) == 0 ) { + error( "incomplete variable name" ); + stringBuffer_ << "invalid"; + } + pushVar( ); + break; + + case E_State_::INITIAL_DASH: + error( "unexpected end of input" ); + break; + + case E_State_::BINARY: + case E_State_::BINARY_DIGIT: + case E_State_::BINARY_SEPARATOR: + case E_State_::ER_BINARY: + error( "incomplete binary data" ); + pushBinary( ); + break; + } + target_.end( errors_ ); + binBuffer_.free( ); +} + + +/*= T_SRDLexer ===============================================================*/ + +T_SRDLexer::T_SRDLexer( + T_String const& name , + T_SRDErrors& errors , + A_SRDReaderTarget& target ) + : A_PrivateImplementation( new T_SRDLexerPrivate_( + name , errors , target ) ) +{ } + +void T_SRDLexer::processCharacter( T_Character c ) +{ + p< T_SRDLexerPrivate_ >( ).processCharacter( c ); +} + +void T_SRDLexer::processEnd( ) +{ + p< T_SRDLexerPrivate_ >( ).processEnd( ); +} + +/*= T_SRDTextReader ==========================================================*/ + +void T_SRDTextReader::read( T_String const& name , A_InputStream& input ) +{ + T_SRDErrors errors; + + T_SRDLexer lexer( name , errors , target_ ); + T_BinaryReader reader( input ); + target_.start( errors ); + try { + do { + lexer.processCharacter( reader.read< T_Character >( ) ); + } while ( 1 ); + } catch ( X_StreamError const& e ) { + if ( e.code( ) != E_StreamError::END ) { + throw; + } + } + lexer.processEnd( ); + + if ( errors.size( ) != 0 ) { + throw X_SRDErrors( errors ); + } +} + + +/*= TEXT LEXING HELPERS ======================================================*/ + +T_SRDList lw::SRDFromText( T_String const& name , T_String const& string , bool structured ) +{ + T_SRDMemoryTarget mt( structured ); + T_SRDErrors errors; + mt.start( errors ); + + T_SRDLexer lexer( name , errors , mt ); + T_StringIterator it( string ); + while ( !it.atEnd( ) ) { + lexer.processCharacter( T_Character( it ) ); + it.next( ); + } + lexer.processEnd( ); + + if ( errors.size( ) != 0 ) { + throw X_SRDErrors( errors ); + } + return mt.list( ); +} + +T_SRDList lw::SRDFromText( T_String const& name , char const* string , bool structured ) +{ + T_SRDMemoryTarget mt( structured ); + T_SRDErrors errors; + mt.start( errors ); + + T_SRDLexer lexer( name , errors , mt ); + char const* ptr = string; + while ( *ptr ) { + lexer.processCharacter( T_Character( *ptr ++ ) ); + } + lexer.processEnd( ); + + if ( errors.size( ) != 0 ) { + throw X_SRDErrors( errors ); + } + return mt.list( ); +} + + +/*= T_SRDTextWriterPrivate_ ==================================================*/ + +namespace { +struct T_SRDTextWriterPrivate_ +{ + A_OutputStream& output_; + T_StringBuilder sb_; + bool justHadList_; + bool isFirst_; + uint32_t depth_; + + //---------------------------------------------------------------------- + + explicit T_SRDTextWriterPrivate_( A_OutputStream& output ); + + void flush( bool force ); + void addTabs( ); + void beforeToken( ); + + void start( ); + void end( ); + + void startList( ); + void endList( ); + + void putWord( T_String const& word ); + void putString( T_String const& string ); + void putComment( T_String const& comment ); + void putVariable( T_String const& variable ); + void putBinary( T_Buffer< uint8_t > const& buffer ); + void putInteger( int64_t value ); + void putInt32( int32_t value ); + void putInt64( int64_t value ); + void putFloat( double value ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriterPrivate_::T_SRDTextWriterPrivate_( A_OutputStream& output ) + : output_( output ) +{ } + +/*----------------------------------------------------------------------------*/ + +void T_SRDTextWriterPrivate_::addTabs( ) +{ + sb_ << "\r\n"; + for ( uint32_t i = 0 ; i < depth_ ; i ++ ) { + sb_ << " "; + } +} + +void T_SRDTextWriterPrivate_::beforeToken( ) +{ + if ( justHadList_ ) { + addTabs( ); + justHadList_ = false; + } else if ( !isFirst_ ) { + sb_ << ' '; + } + isFirst_ = false; +} + +void T_SRDTextWriterPrivate_::flush( bool force ) +{ + if ( !force && sb_.size( ) < 1024 ) { + return; + } + output_.write( ( uint8_t const* ) sb_.data( ) , sb_.size( ) ); + sb_.clear( ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDTextWriterPrivate_::start( ) +{ + justHadList_ = false; + isFirst_ = true; + depth_ = 0; +} + +inline void T_SRDTextWriterPrivate_::end( ) +{ + if ( depth_ != 0 ) { + throw X_SRDWriterError( "unterminated lists" ); + } + if ( sb_.size( ) && sb_.data( )[ sb_.size( ) - 1 ] != '\n' ) { + sb_ << "\r\n"; + } + flush( true ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDTextWriterPrivate_::startList( ) +{ + if ( !isFirst_ ) { + addTabs( ); + } + sb_ << '('; + depth_ ++; + isFirst_ = true; + justHadList_ = false; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::endList( ) +{ + if ( depth_ == 0 ) { + throw X_SRDWriterError( "at top level" ); + } + sb_ << ')'; + depth_ --; + justHadList_ = true; + isFirst_ = false; + flush( false ); +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDTextWriterPrivate_::putWord( T_String const& word ) +{ + beforeToken( ); + sb_ << word; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putString( T_String const& string ) +{ + beforeToken( ); + sb_ << '"'; + + T_StringIterator it( string ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + + if ( c == '\n' ) { + sb_ << "\\n"; + } else if ( c == '\r' ) { + sb_ << "\\r"; + } else if ( c == '\t' ) { + sb_ << "\\t"; + } else if ( c == 8 ) { + sb_ << "\\b"; + } else if ( c == 12 ) { + sb_ << "\\f"; + } else if ( c == '\\' ) { + sb_ << "\\\\"; + } else if ( c == '"' ) { + sb_ << "\\\""; + } else if ( c > 0xffff ) { + sb_ << "\\U"; + uint32_t x( 0x10000000 ); + while ( c < x ) { + sb_ << '0'; + x >>= 4; + } + sb_.appendNumeric( uint64_t( c ) , 16 ); + } else if ( c.isControl( ) || !c.isAscii( ) ) { + sb_ << "\\u"; + uint32_t x( 0x1000 ); + while ( c < x ) { + sb_ << '0'; + x >>= 4; + } + sb_.appendNumeric( uint64_t( c ) , 16 ); + } else { + sb_ << c; + } + } + + sb_ << '"'; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putComment( T_String const& comment ) +{ + bool newLine = true; + if ( !isFirst_ ) { + sb_ << ' '; + } + + T_StringIterator it( comment ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + if ( newLine ) { + sb_ << '#'; + newLine = false; + } + if ( c == '\n' ) { + addTabs( ); + newLine = true; + } else if ( c != '\r' ) { + sb_ << c; + } + } + + if ( !newLine && comment ) { + addTabs( ); + } + isFirst_ = true; + justHadList_ = false; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putVariable( T_String const& variable ) +{ + beforeToken( ); + sb_ << '$' << variable; + flush( false ); +} + +/*----------------------------------------------------------------------------*/ + +void T_SRDTextWriterPrivate_::putBinary( T_Buffer< uint8_t > const& buffer ) +{ + const auto bs( buffer.size( ) ); + beforeToken( ); + sb_ << '['; + if ( bs <= 8 ) { + for ( uint32_t i = 0 ; i < bs ; i ++ ) { + sb_ << ' '; + if ( buffer[ i ] < 16 ) { + sb_ << '0'; + } + sb_.appendNumeric( uint64_t( buffer[ i ] ) , 16 ); + } + if ( bs ) { + sb_ << ' '; + } + } else { + depth_ ++; + + for ( uint32_t i = 0 ; i < bs ; i ++ ) { + if ( i % 16 == 0 ) { + addTabs( ); + } else { + sb_ << ' '; + } + if ( buffer[ i ] < 16 ) { + sb_ << '0'; + } + sb_.appendNumeric( uint64_t( buffer[ i ] ) , 16 ); + } + + depth_ --; + addTabs( ); + } + sb_ << ']'; +} + +/*----------------------------------------------------------------------------*/ + +inline void T_SRDTextWriterPrivate_::putInteger( int64_t value ) +{ + beforeToken( ); + sb_ << value; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putInt32( int32_t value ) +{ + beforeToken( ); + sb_ << value; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putInt64( int64_t value ) +{ + beforeToken( ); + sb_ << value; + flush( false ); +} + +inline void T_SRDTextWriterPrivate_::putFloat( double value ) +{ + beforeToken( ); + sb_.appendDouble( value , 25 ); + flush( false ); +} + + +/*= T_SRDTextWriter ==========================================================*/ + +T_SRDTextWriter::T_SRDTextWriter( A_OutputStream& output ) + : A_PrivateImplementation( new T_SRDTextWriterPrivate_( output ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriter& T_SRDTextWriter::start( ) +{ + p< T_SRDTextWriterPrivate_ >( ).start( ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::end( ) +{ + p< T_SRDTextWriterPrivate_ >( ).end( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriter& T_SRDTextWriter::startList( ) +{ + p< T_SRDTextWriterPrivate_ >( ).startList( ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::endList( ) +{ + p< T_SRDTextWriterPrivate_ >( ).endList( ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriter& T_SRDTextWriter::putText( T_String const& text ) +{ + if ( T_SRDToken::IsWord( text ) ) { + return putWord( text ); + } else { + return putString( text ); + } +} + +T_SRDTextWriter& T_SRDTextWriter::putWord( T_String const& word ) +{ + p< T_SRDTextWriterPrivate_ >( ).putWord( word ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putString( T_String const& string ) +{ + p< T_SRDTextWriterPrivate_ >( ).putString( string ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putComment( T_String const& comment ) +{ + p< T_SRDTextWriterPrivate_ >( ).putComment( comment ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putVariable( T_String const& variable ) +{ + p< T_SRDTextWriterPrivate_ >( ).putVariable( variable ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriter& T_SRDTextWriter::putBinary( + T_Buffer< uint8_t > const& buffer ) +{ + p< T_SRDTextWriterPrivate_ >( ).putBinary( buffer ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_SRDTextWriter& T_SRDTextWriter::putInteger( int64_t value ) +{ + p< T_SRDTextWriterPrivate_ >( ).putInteger( value ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putInt32( int32_t value ) +{ + p< T_SRDTextWriterPrivate_ >( ).putInt32( value ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putInt64( int64_t value ) +{ + p< T_SRDTextWriterPrivate_ >( ).putInt64( value ); + return *this; +} + +T_SRDTextWriter& T_SRDTextWriter::putFloat( double value ) +{ + p< T_SRDTextWriterPrivate_ >( ).putFloat( value ); + return *this; +} diff --git a/src/Streams.cc b/src/Streams.cc new file mode 100644 index 0000000..1b46d24 --- /dev/null +++ b/src/Streams.cc @@ -0,0 +1,42 @@ +/******************************************************************************/ +/* STREAMS ********************************************************************/ +/******************************************************************************/ + +#include + +using namespace lw; + + +/*= X_StreamError ============================================================*/ + +char const* X_StreamError::what( ) const noexcept +{ + switch ( error_ ) { + case E_StreamError::NOT_SUPPORTED: + return "operation not supported"; + + case E_StreamError::INVALID_POSITION: + return "invalid position"; + + case E_StreamError::END: + return "stream ended"; + + case E_StreamError::BAD_DATA: + return "incomplete read/write"; + + case E_StreamError::SYSTEM_ERROR: + return "system error"; + + case E_StreamError::UNAVAILABLE: + return "stream no longer available"; + + default: + return "unknown error"; + } +} + + +/*= A_OutputStream ===========================================================*/ + +void A_OutputStream::flush( ) +{ } diff --git a/src/Strings.cc b/src/Strings.cc new file mode 100644 index 0000000..1e1ab69 --- /dev/null +++ b/src/Strings.cc @@ -0,0 +1,2015 @@ +/******************************************************************************/ +/* STRINGS AND RELATED UTILITIES **********************************************/ +/******************************************************************************/ + + +#include +#include +#include +#include +#include +#include + + +using namespace lw; + + +/*= STRING STORAGE AND POOLING CLASSES =======================================*/ + +namespace { + +// T_StringDataInternal - Actual interface w/ reference counting methods +class A_StringDataInternal : virtual public A_StringData +{ + public: + const bool poolable; + + explicit A_StringDataInternal( bool poolable ); + virtual ~A_StringDataInternal( ); + + // Add/remove user, for e.g. reference counting + virtual void addUser( ); + virtual void removeUser( ); +}; + + +M_ABSTRACT_POINTERS( StringDataInternal ); + +/*----------------------------------------------------------------------------*/ + +// T_EmptyString - Fake storage for the empty string +class T_EmptyString final : public A_StringDataInternal +{ + public: + static T_EmptyString EmptyString; + + T_EmptyString( ); + + T_EmptyString( T_EmptyString const& ) = delete; + T_EmptyString( T_EmptyString&& other ) = delete; +}; + + +// T_StaticString - Storage for read-only strings +class T_StaticString final : public A_StringDataInternal +{ + public: + T_StaticString( ) = delete; + T_StaticString( T_StaticString const& ) = delete; + T_StaticString( T_StaticString&& other ) noexcept = delete; + + T_StaticString( char const* string , uint32_t size ); + ~T_StaticString( ) override; +}; + + +// A_RefCountedString - A reference-counted string. Used for dynamic strings +// and substrings. +class A_RefCountedString : public A_StringDataInternal +{ + private: + std::atomic< uint32_t > users_; + + public: + A_RefCountedString( ); + A_RefCountedString( A_RefCountedString const& ) = delete; + A_RefCountedString( A_RefCountedString&& ) = delete; + + void addUser( ) override; + void removeUser( ) override; +}; + + +// T_DynamicString - Storage for dynamically-created strings +class T_DynamicString final : public A_RefCountedString +{ + public: + T_DynamicString( ) = delete; + T_DynamicString( T_DynamicString const& ) = delete; + T_DynamicString( T_DynamicString&& other ) noexcept = delete; + + // Pool allocation + void* operator new( size_t size ) noexcept; + void operator delete( void* object ) noexcept; + + T_DynamicString( char const* data , uint32_t size , bool nodup ); + ~T_DynamicString( ) override; +}; + + +// T_Substring - Storage for a string that is in fact a part of another +class T_Substring final : public A_RefCountedString +{ + private: + RP_StringDataInternal source_; + + public: + T_Substring( ) = delete; + T_Substring( T_Substring const& ) = delete; + T_Substring( T_Substring&& ) = delete; + + // Pool allocation + void* operator new( size_t size ) noexcept; + void operator delete( void* object ) noexcept; + + T_Substring( RP_StringDataInternal source , uint32_t offset , uint32_t size ); + ~T_Substring( ) override; +}; + + +/*----------------------------------------------------------------------------*/ + +// T_StringPool - Pool of string storage classes +class T_StringPool final +{ + private: + T_HashIndex index_; + T_Array< T_OwnPtr< T_StaticString > > strings_; + + public: + static T_StringPool Pool; + static T_ReadWriteMutex Mutex; + + T_StringPool( ); + + RP_StringDataInternal add( char const* data , uint32_t size ); + RP_StringDataInternal get( char const* data , uint32_t size ) const; + + private: + uint32_t find( char const* data , uint32_t length , uint32_t hash ) const; +}; + + +} // namespace + +namespace lw { M_DECLARE_HASH( A_StringDataInternal ); } + + +/*= UTF-8 UTILITY FUNCTIONS ==================================================*/ + +bool lw::UTF8IsValid( char const* string ) +{ + assert( string != nullptr ); + + char const* ptr = string; + char c; + while ( ( c = *ptr ) != '\0' ) { + // 4 bytes + if ( ( c & 0xf8 ) == 0xf0 ) { + // 3 following bytes should be part of this codepoint + if ( ( ptr[ 1 ] & 0xc0 ) != 0x80 + || ( ptr[ 2 ] & 0xc0 ) != 0x80 + || ( ptr[ 3 ] & 0xc0 ) != 0x80 ) { + return false; + } + + // Check for overlongs + if ( ( c & 0x07 ) == 0 && ( ptr[ 1 ] & 0x30 ) == 0 ) { + return false; + } + + ptr += 4; + + // 3 bytes + } else if ( ( c & 0xf0 ) == 0xe0 ) { + // 2 following bytes should be part of this codepoint + if ( ( ptr[ 1 ] & 0xc0 ) != 0x80 + || ( ptr[ 2 ] & 0xc0 ) != 0x80 ) { + return false; + } + + // Check for overlongs + if ( ( c & 0x0f ) == 0 && ( ptr[ 1 ] & 0x20 ) == 0 ) { + return false; + } + + ptr += 3; + + } else if ( ( c & 0xe0 ) == 0xc0 ) { + // Next byte should be part of this codepoint + if ( ( ptr[ 1 ] & 0xc0 ) != 0x80 ) { + return false; + } + + // Check for overlongs + if ( ( c & 0x1e ) == 0 ) { + return false; + } + + ptr += 2; + + } else if ( ( c & 0x80 ) != 0 ) { + return false; + + } else { + ptr ++; + } + } + + return true; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8Length( char const* string ) +{ + assert( string != nullptr ); + + uint32_t len = 0; + char const* ptr = string; + char c; + while ( ( c = *ptr ) != '\0' ) { + if ( ( c & 0xf8 ) == 0xf0 ) { + ptr += 4; + } else if ( ( c & 0xf0 ) == 0xe0 ) { + ptr += 3; + } else if ( ( c & 0xe0 ) == 0xc0 ) { + ptr += 2; + } else { + ptr ++; + } + len ++; + } + + return len; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8Size( char const* string ) +{ + assert( string != nullptr ); + + char const* ptr = string; + char c; + while ( ( c = *ptr ) != '\0' ) { + if ( ( c & 0xf8 ) == 0xf0 ) { + ptr += 4; + } else if ( ( c & 0xf0 ) == 0xe0 ) { + ptr += 3; + } else if ( ( c & 0xe0 ) == 0xc0 ) { + ptr += 2; + } else { + ptr ++; + } + } + + return uint32_t( ptr - string ); +} + +/*----------------------------------------------------------------------------*/ + +bool lw::UTF8Info( char const* string , uint32_t& size , uint32_t& length ) +{ + assert( string != nullptr ); + + char const* ptr = string; + uint32_t len = 0; + bool valid = true; + char c; + while ( ( c = *ptr ) != '\0' ) { + // 4 bytes + if ( ( c & 0xf8 ) == 0xf0 ) { + valid = valid + // 3 following bytes should be part of this + // codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + && ( ptr[ 2 ] & 0xc0 ) == 0x80 + && ( ptr[ 3 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( ( c & 0x07 ) != 0 || ( ptr[ 1 ] & 0x30 ) != 0 ); + ptr += 4; + + // 3 bytes + } else if ( ( c & 0xf0 ) == 0xe0 ) { + valid = valid + // 2 following bytes should be part of this + // codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + && ( ptr[ 2 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( ( c & 0x0f ) != 0 || ( ptr[ 1 ] & 0x20 ) != 0 ); + ptr += 3; + + } else if ( ( c & 0xe0 ) == 0xc0 ) { + valid = valid + // Next byte should be part of this codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( c & 0x1e ) == 0; + ptr += 2; + + } else { + valid = valid && ( c & 0x80 ) == 0; + ptr ++; + } + + len ++; + } + + length = len; + size = ptr - string; + return valid; +} + +/*----------------------------------------------------------------------------*/ + +bool lw::UTF8BufferInfo( char const* data , uint32_t size , uint32_t& length ) +{ + assert( data != nullptr ); + + char const* ptr = data; + uint32_t len = 0; + bool valid = true; + while ( ptr < data + size ) { + char c = *ptr; + bool ptrOk; + + // 4 bytes + if ( ( c & 0xf8 ) == 0xf0 ) { + ptrOk = ptr + 3 < data + size; + valid = valid && ptrOk + // 3 following bytes should be part of this + // codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + && ( ptr[ 2 ] & 0xc0 ) == 0x80 + && ( ptr[ 3 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( ( c & 0x07 ) != 0 + || ( ptr[ 1 ] & 0x30 ) != 0 ); + ptr += 4; + + // 3 bytes + } else if ( ( c & 0xf0 ) == 0xe0 ) { + ptrOk = ptr + 2 < data + size; + valid = valid && ptrOk + // 2 following bytes should be part of this + // codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + && ( ptr[ 2 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( ( c & 0x0f ) != 0 + || ( ptr[ 1 ] & 0x20 ) != 0 ); + ptr += 3; + + } else if ( ( c & 0xe0 ) == 0xc0 ) { + ptrOk = ptr + 1 < data + size; + valid = valid && ptrOk + // Next byte should be part of this codepoint + && ( ptr[ 1 ] & 0xc0 ) == 0x80 + // Check for overlongs + && ( c & 0x1e ) != 0; + ptr += 2; + + } else { + valid = valid && ( c & 0x80 ) == 0; + ptrOk = true; + ptr ++; + } + + if ( ptrOk ) { + len ++; + } + } + + length = len; + return valid; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8GetCodepoint( char const* data , uint32_t& bytes ) +{ + assert( data != nullptr ); + if ( ( data[ 0 ] & 0xf8 ) == 0xf0 ) { + bytes = 4; + return ( ( data[ 0 ] & 0x07 ) << 18 ) + | ( ( data[ 1 ] & 0x3f ) << 12 ) + | ( ( data[ 2 ] & 0x3f ) << 6 ) + | ( data[ 3 ] & 0x3f ); + + } else if ( ( data[ 0 ] & 0xf0 ) == 0xe0 ) { + bytes = 3; + return ( ( data[ 0 ] & 0x0f ) << 12 ) + | ( ( data[ 1 ] & 0x3f ) << 6 ) + | ( data[ 2 ] & 0x3f ); + + } else if ( ( data[ 0 ] & 0xe0 ) == 0xc0 ) { + bytes = 2; + return ( ( data[ 0 ] & 0x1f ) << 6 ) + | ( data[ 1 ] & 0x3f ); + + } else { + bytes = 1; + return data[ 0 ]; + } +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8GetCodepoint( char const* data ) +{ + assert( data != nullptr ); + if ( ( data[ 0 ] & 0xf8 ) == 0xf0 ) { + return ( ( data[ 0 ] & 0x07 ) << 18 ) + | ( ( data[ 1 ] & 0x3f ) << 12 ) + | ( ( data[ 2 ] & 0x3f ) << 6 ) + | ( data[ 3 ] & 0x3f ); + + } else if ( ( data[ 0 ] & 0xf0 ) == 0xe0 ) { + return ( ( data[ 0 ] & 0x0f ) << 12 ) + | ( ( data[ 1 ] & 0x3f ) << 6 ) + | ( data[ 2 ] & 0x3f ); + + } else if ( ( data[ 0 ] & 0xe0 ) == 0xc0 ) { + return ( ( data[ 0 ] & 0x1f ) << 6 ) + | ( data[ 1 ] & 0x3f ); + + } else { + return data[ 0 ]; + } +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8PutCodepoint( char* output , uint32_t available , uint32_t codepoint ) +{ + if ( codepoint < 0x80 && available >= 1 ) { + *output = char( codepoint ); + return 1; + } else if ( codepoint < 0x800 && available >= 2 ) { + output[ 0 ] = char( ( codepoint >> 6 ) | 0xc0 ); + output[ 1 ] = char( ( codepoint & 0x3f ) | 0x80 ); + return 2; + } else if ( codepoint < 0x10000 && available >= 3 ) { + output[ 0 ] = char( ( codepoint >> 12 ) | 0xe0 ); + output[ 1 ] = char( ( ( codepoint >> 6 ) & 0x3f ) | 0x80 ); + output[ 2 ] = char( ( codepoint & 0x3f ) | 0x80 ); + return 3; + } else if ( codepoint < 0x110000 && available >= 4 ) { + output[ 0 ] = char( ( codepoint >> 18 ) | 0xf0 ); + output[ 1 ] = char( ( ( codepoint >> 12 ) & 0x3f ) | 0x80 ); + output[ 2 ] = char( ( ( codepoint >> 6 ) & 0x3f ) | 0x80 ); + output[ 3 ] = char( ( codepoint & 0x3f ) | 0x80 ); + return 4; + } + return 0; +} + +/*----------------------------------------------------------------------------*/ + +uint32_t lw::UTF8GetMemoryOffset( char const* input , uint32_t index ) +{ + assert( input != nullptr ); + + char const* p = input; + while ( index != 0 ) { + if ( ( *p & 0xf8 ) == 0xf0 ) { + p += 4; + } else if ( ( *p & 0xf0 ) == 0xe0 ) { + p += 3; + } else if ( ( *p & 0xe0 ) == 0xc0 ) { + p += 2; + } else { + p ++; + } + index --; + } + + return p - input; +} + +/*----------------------------------------------------------------------------*/ + +uint64_t lw::UTF8ToUnsignedInteger( char const* input , uint32_t size , bool * ok , + int base , bool useSep , uint32_t separator ) +{ + char const* inputPos( input ); + char const* const inputEnd( input + size ); + + if ( ok ) { + *ok = false; + } + + // Find start + bool checkBase( false ); + bool hadSign( false ); + while ( 1 ) { + if ( inputPos >= inputEnd ) { + return 0; + } + uint32_t nBytes; + const T_Character c( UTF8GetCodepoint( inputPos , nBytes ) ); + + if ( !c.isWhitespace( ) && ( !useSep || c != separator ) ) { + if ( c == '+' ) { + if ( hadSign ) { + return 0; + } + hadSign = true; + } else if ( c.isNumeric( ) || c.isAlpha( ) ) { + checkBase = ( base == 0 && c == '0' ); + if ( checkBase ) { + inputPos += nBytes; + } + break; + } else { + return 0; + } + } + + inputPos += nBytes; + } + + // Detect base + if ( checkBase ) { + if ( inputPos >= inputEnd ) { + if ( ok != nullptr ) { + *ok = true; + } + return 0; + } + uint32_t nBytes; + const T_Character nc( UTF8GetCodepoint( inputPos , nBytes ) ); + + bool next( true ); + if ( nc == 'x' || nc == 'X' ) { + base = 16; + } else if ( nc == 'b' || nc == 'B' ) { + base = 2; + } else { + base = 8; + next = false; + } + if ( next ) { + inputPos += nBytes; + } + } else if ( base == 0 ) { + base = 10; + } + + // Start converting + const uint64_t ubase( base ); + const uint64_t cutoff( UINT64_MAX / ubase ); + const uint64_t limit( UINT64_MAX % ubase ); + uint64_t accum( 0 ); + int any( 0 ); + while ( inputPos < inputEnd ) { + uint32_t nBytes; + const T_Character c( UTF8GetCodepoint( inputPos , nBytes ) ); + inputPos += nBytes; + + if ( useSep && c == separator ) { + continue; + } + + uint32_t value; + if ( c.isNumeric( ) ) { + value = c - '0'; + } else if ( c.isAlpha( ) ) { + value = c.toUpper( ) - 55; + } else { + any = 0; + break; + } + if ( value >= ubase ) { + any = 0; + break; + } + + if ( any < 0 || accum > cutoff || ( accum == cutoff && value > limit ) ) { + any = -1; + } else { + any = 1; + accum = accum * ubase + value; + } + } + + if ( any < 0 ) { + accum = UINT64_MAX; + } else if ( any > 0 && ok ) { + *ok = true; + } + return accum; +} + +/*----------------------------------------------------------------------------*/ + +int64_t lw::UTF8ToInteger( char const* input , uint32_t size , bool * ok , + int base , bool useSep , uint32_t separator ) +{ + char const* inputPos( input ); + char const* const inputEnd( input + size ); + + if ( ok ) { + *ok = false; + } + + // Find start + bool checkBase( false ); + bool hadSign( false ); + bool neg( false ); + while ( 1 ) { + if ( inputPos >= inputEnd ) { + return 0; + } + uint32_t nBytes; + const T_Character c( UTF8GetCodepoint( inputPos , nBytes ) ); + + if ( !c.isWhitespace( ) && ( !useSep || c != separator ) ) { + if ( c == '+' || c == '-' ) { + if ( hadSign ) { + return 0; + } + neg = ( c == '-' ); + hadSign = true; + } else if ( c.isNumeric( ) || c.isAlpha( ) ) { + checkBase = ( base == 0 && c == '0' ); + if ( checkBase ) { + inputPos += nBytes; + } + break; + } else { + return 0; + } + } + + inputPos += nBytes; + } + + // Detect base + if ( checkBase ) { + if ( inputPos >= inputEnd ) { + if ( ok != nullptr ) { + *ok = true; + } + return 0; + } + uint32_t nBytes; + const T_Character nc( UTF8GetCodepoint( inputPos , nBytes ) ); + + bool next( true ); + if ( nc == 'x' || nc == 'X' ) { + base = 16; + } else if ( nc == 'b' || nc == 'B' ) { + base = 2; + } else { + base = 8; + next = false; + } + if ( next ) { + inputPos += nBytes; + } + } else if ( base == 0 ) { + base = 10; + } + + // Start converting + const uint64_t ubase( base ); + const uint64_t max( neg ? ( uint64_t( 0 - ( INT64_MIN + INT64_MAX ) ) + INT64_MAX ) : INT64_MAX ); + const uint64_t cutoff( max / ubase ); + const uint64_t limit( max % ubase ); + uint64_t accum( 0 ); + int any( 0 ); + while ( inputPos < inputEnd ) { + uint32_t nBytes; + const T_Character c( UTF8GetCodepoint( inputPos , nBytes ) ); + inputPos += nBytes; + + if ( useSep && c == separator ) { + continue; + } + + uint32_t value; + if ( c.isNumeric( ) ) { + value = c - '0'; + } else if ( c.isAlpha( ) ) { + value = c.toUpper( ) - 55; + } else { + any = 0; + break; + } + if ( value >= ubase ) { + any = 0; + break; + } + + if ( any < 0 || accum > cutoff || ( accum == cutoff && value > limit ) ) { + any = -1; + } else { + any = 1; + accum = accum * ubase + value; + } + } + + if ( any < 0 ) { + accum = neg ? INT64_MIN : INT64_MAX; + } else if ( any > 0 ) { + if ( neg ) { + accum = (~accum) + 1; + } + if ( ok ) { + *ok = true; + } + } + return accum; +} + +/*----------------------------------------------------------------------------*/ + +double lw::UTF8ToDouble( char const* input , uint32_t size , + bool * ok , uint32_t decimalPoint , + bool useSep , uint32_t separator ) +{ + char const* inputPos( input ); + char const* const inputEnd( input + size ); + char output[ size + 1 ]; + char * outputPos( output ); + + if ( ok ) { + *ok = false; + } + + enum E_State_ { + INIT , + HAD_SIGN , + INT_PART , + HAD_SEP , + FRACT_PART , + AFTER_EXP , + AFTER_EXP_SIGN , + EXP_PART + }; + E_State_ state( INIT ); + + while ( inputPos < inputEnd ) { + uint32_t nBytes; + const T_Character c( UTF8GetCodepoint( inputPos , nBytes ) ); + inputPos += nBytes; + + switch ( state ) { + + case INIT: + if ( c.isWhitespace( ) ) { + continue; + } + if ( c == '+' || c == '-' ) { + *( outputPos ++ ) = c; + state = HAD_SIGN; + } else if ( c == decimalPoint ) { + *( outputPos ++ ) = '.'; + state = FRACT_PART; + } else if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = INT_PART; + } else { + return 0; + } + break; + + case HAD_SIGN: + if ( c == decimalPoint ) { + *( outputPos ++ ) = '.'; + state = FRACT_PART; + } else if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = INT_PART; + } else { + return 0; + } + break; + + case INT_PART: + if ( c == decimalPoint ) { + *( outputPos ++ ) = '.'; + state = FRACT_PART; + } else if ( c == 'e' || c == 'E' ) { + *( outputPos ++ ) = 'e'; + state = AFTER_EXP; + } else if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + } else if ( useSep && c == separator ) { + state = HAD_SEP; + } else { + return 0; + } + break; + + case HAD_SEP: + if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = INT_PART; + } else { + return 0; + } + break; + + case FRACT_PART: + if ( c == 'e' || c == 'E' ) { + *( outputPos ++ ) = 'e'; + state = AFTER_EXP; + } else if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = FRACT_PART; + } else { + return 0; + } + break; + + case AFTER_EXP: + if ( c == '+' || c == '-' ) { + *( outputPos ++ ) = c; + state = AFTER_EXP_SIGN; + } else if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = EXP_PART; + } else { + return 0; + } + break; + + case AFTER_EXP_SIGN: + if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + state = EXP_PART; + } else { + return 0; + } + break; + + case EXP_PART: + if ( c.isNumeric( ) ) { + *( outputPos ++ ) = c; + } else { + return 0; + } + break; + } + } + if ( state == INIT || state == HAD_SEP || state == HAD_SIGN + || state == AFTER_EXP || state == AFTER_EXP_SIGN ) { + return 0; + } + *outputPos = '\0'; + + errno = 0; + auto v( strtod( output , nullptr ) ); + if ( errno == 0 && ok ) { + *ok = true; + } + return v; +} + + +/*= T_Character ==============================================================*/ + +M_DEFINE_OBJECT_READER( T_Character ) +{ + const char first( reader.read< char >( ) ); + if ( ( first & 0xf8 ) == 0xf0 ) { + return ( ( first & 0x07 ) << 18 ) + | ( ( reader.read< char >( ) & 0x3f ) << 12 ) + | ( ( reader.read< char >( ) & 0x3f ) << 6 ) + | ( reader.read< char >( ) & 0x3f ); + + } else if ( ( first & 0xf0 ) == 0xe0 ) { + return ( ( first & 0x0f ) << 12 ) + | ( ( reader.read< char >( ) & 0x3f ) << 6 ) + | ( reader.read< char >( ) & 0x3f ); + + } else if ( ( first & 0xe0 ) == 0xc0 ) { + return ( ( first & 0x1f ) << 6 ) + | ( reader.read< char >( ) & 0x3f ); + + } else { + return first; + } +} + +M_DEFINE_OBJECT_WRITER( T_Character ) +{ + const uint32_t codepoint( item ); + if ( codepoint < 0x80 ) { + writer.write< char >( codepoint ); + + } else if ( codepoint < 0x800 ) { + writer.write< char >( ( codepoint >> 6 ) | 0xc0 ); + writer.write< char >( ( codepoint & 0x3f ) | 0x80 ); + + } else if ( codepoint < 0x10000 ) { + writer.write< char >( ( codepoint >> 12 ) | 0xe0 ); + writer.write< char >( ( ( codepoint >> 6 ) & 0x3f ) | 0x80 ); + writer.write< char >( ( codepoint & 0x3f ) | 0x80 ); + + } else { + writer.write< char >( ( codepoint >> 18 ) | 0xf0 ); + writer.write< char >( ( ( codepoint >> 12 ) & 0x3f ) | 0x80 ); + writer.write< char >( ( ( codepoint >> 6 ) & 0x3f ) | 0x80 ); + writer.write< char >( ( codepoint & 0x3f ) | 0x80 ); + } +} + +/*= A_StringData =============================================================*/ + +inline A_StringData::~A_StringData( ) +{ } + + +/*= A_StringDataInternal =====================================================*/ + +inline A_StringDataInternal::A_StringDataInternal( bool poolable ) + : poolable( poolable ) +{ } + +A_StringDataInternal::~A_StringDataInternal( ) +{ } + +void A_StringDataInternal::addUser( ) +{ } + +void A_StringDataInternal::removeUser( ) +{ } + +inline M_DEFINE_HASH( A_StringDataInternal ) +{ + return HashData( reinterpret_cast< uint8_t const* >( item.data( ) ) , + item.size( ) ); +} + + +/*= T_EmptyString ============================================================*/ + +T_EmptyString::T_EmptyString( ) + : A_StringDataInternal( false ) +{ + data_ = nullptr; + size_ = 0; + valid_ = true; + length_ = 0; +} + +T_EmptyString T_EmptyString::EmptyString; + + +/*= T_StaticString ===========================================================*/ + +T_StaticString::T_StaticString( char const* data , uint32_t size ) + : A_StringDataInternal( false ) +{ + size_ = size; + data_ = ( char* )::operator new ( size_ ); + memcpy( data_ , data , size_ ); + valid_ = UTF8BufferInfo( data , size , length_ ); +} + +T_StaticString::~T_StaticString( ) +{ + ::operator delete ( data_ ); +} + + +/*= A_RefCountedString =======================================================*/ + +inline A_RefCountedString::A_RefCountedString( ) + : A_StringDataInternal( true ) , users_( 1 ) +{ } + +void A_RefCountedString::addUser( ) +{ + users_.fetch_add( 1 , std::memory_order_acq_rel ); +} + +void A_RefCountedString::removeUser( ) +{ + if ( users_.fetch_sub( 1 , std::memory_order_acq_rel ) == 1 ) { + std::atomic_thread_fence( std::memory_order_acq_rel ); + if ( users_.load( std::memory_order_acq_rel ) == 0 ) { + delete this; + } + } +} + + +/*= T_DynamicString ==========================================================*/ + +namespace { +static thread_local T_ThreadedPoolAllocator< + sizeof( T_DynamicString ) , alignof( T_DynamicString ) , + 32 , 16 + > DynamicStringAllocator_; + +} + +void* T_DynamicString::operator new( + const size_t size ) noexcept +{ + return DynamicStringAllocator_.allocate( size ); +} + +void T_DynamicString::operator delete( + void* const object ) noexcept +{ + DynamicStringAllocator_.free( object ); +} + +/*----------------------------------------------------------------------------*/ + + +T_DynamicString::T_DynamicString( char const* data , uint32_t size , bool nodup ) + : A_RefCountedString( ) +{ + size_ = size; + if ( nodup ) { + data_ = const_cast< char* >( data ); + } else { + data_ = ( char* )::operator new ( size_ ); + memcpy( data_ , data , size_ ); + } + valid_ = UTF8BufferInfo( data , size , length_ ); +} + +T_DynamicString::~T_DynamicString( ) +{ + ::operator delete ( data_ ); +} + + +/*= T_Substring ==============================================================*/ + +namespace { +static thread_local T_ThreadedPoolAllocator< + sizeof( T_Substring ) , alignof( T_Substring ) , + 32 , 4 + > SubstringAllocator_; + +} + +void* T_Substring::operator new( + const size_t size ) noexcept +{ + return SubstringAllocator_.allocate( size ); +} + +void T_Substring::operator delete( + void* const object ) noexcept +{ + SubstringAllocator_.free( object ); +} + +/*----------------------------------------------------------------------------*/ + +T_Substring::T_Substring( RP_StringDataInternal source , uint32_t offset , uint32_t size ) + : A_RefCountedString( ) , source_( source ) +{ + assert( size + offset <= source_->size( ) ); + + source_->addUser( ); + data_ = const_cast< char* >( source->data( ) + offset ); + size_ = size; + valid_ = UTF8BufferInfo( data_ , size_ , length_ ); +} + +T_Substring::~T_Substring( ) +{ + source_->removeUser( ); +} + + +/*= T_StringPool =============================================================*/ + +T_StringPool T_StringPool::Pool; +T_ReadWriteMutex T_StringPool::Mutex; + +/*----------------------------------------------------------------------------*/ + +T_StringPool::T_StringPool( ) + : index_( 16384 , 4096 , 4096 ) , strings_( 4096 ) +{ } + +/*----------------------------------------------------------------------------*/ + +RP_StringDataInternal T_StringPool::add( char const* data , uint32_t size ) +{ + T_ReadLock lock( T_StringPool::Mutex ); + const auto hash( HashData( (uint8_t const*) data , size ) ); + const auto idx( find( data , size , hash ) ); + + if ( idx == T_HashIndex::INVALID_INDEX ) { + const T_WriteLock wLock( lock.upgrade( ) ); + index_.add( hash ); + const auto str( strings_.add( NewOwned< T_StaticString >( data , size ) ) ); + return strings_[ str ].get( ); + } else { + return strings_[ idx ].get( ); + } +} + +/*----------------------------------------------------------------------------*/ + +RP_StringDataInternal T_StringPool::get( char const* data , uint32_t size ) const +{ + const T_ReadLock lock( T_StringPool::Mutex ); + const auto hash( HashData( reinterpret_cast< uint8_t const* >( data ) , size ) ); + const auto idx( find( data , size , hash ) ); + + if ( idx == T_HashIndex::INVALID_INDEX ) { + return nullptr; + } else { + return strings_[ idx ].get( ); + } +} + +/*----------------------------------------------------------------------------*/ + +uint32_t T_StringPool::find( char const* data , uint32_t sz , uint32_t hash ) const +{ + uint32_t idx = index_.first( hash ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + auto const& p( strings_[ idx ] ); + if ( p->size( ) == sz && !memcmp( p->data( ) , data , sz ) ) { + break; + } + idx = index_.next( idx ); + } + return idx; +} + + +/*= T_StringIterator =========================================================*/ + +T_StringIterator::T_StringIterator( RP_StringData data , uint32_t index ) + : data_( data ) , index_( index ) +{ + if ( data_ != nullptr ) { + dynamic_cast< RP_StringDataInternal >( data_ )->addUser( ); + pos_ = UTF8GetMemoryOffset( data_->data( ) , index ); + codepoint_ = UTF8GetCodepoint( data_->data( ) + pos_ , bytes_ ); + } else { + pos_ = codepoint_ = bytes_ = 0; + } +} + +T_StringIterator::T_StringIterator( T_StringIterator const& other ) + : data_( other.data_ ) , pos_( other.pos_ ) , index_( other.index_ ) , + codepoint_( other.codepoint_ ) , bytes_( other.bytes_ ) +{ + if ( data_ != nullptr ) { + dynamic_cast< RP_StringDataInternal >( data_ )->addUser( ); + } +} + +/*----------------------------------------------------------------------------*/ + +T_StringIterator::~T_StringIterator( ) +{ + if ( data_ != nullptr ) { + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + } +} + +/*----------------------------------------------------------------------------*/ + +T_StringIterator& T_StringIterator::operator= ( T_StringIterator const& other ) +{ + if ( data_ != nullptr ) { + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + } + data_ = other.data_; + pos_ = other.pos_; + index_ = other.index_; + codepoint_ = other.codepoint_; + bytes_ = other.bytes_; + if ( data_ != nullptr ) { + dynamic_cast< RP_StringDataInternal >( data_ )->addUser( ); + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_StringIterator& lhs , T_StringIterator& rhs ) noexcept +{ + using std::swap; + swap( lhs.data_ , rhs.data_ ); + swap( lhs.pos_ , rhs.pos_ ); + swap( lhs.index_ , rhs.index_ ); + swap( lhs.codepoint_ , rhs.codepoint_ ); + swap( lhs.bytes_ , rhs.bytes_ ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_StringIterator::next( ) +{ + if ( atEnd( ) ) { + return false; + } + pos_ += bytes_; + if ( pos_ == data_->size( ) ) { + codepoint_ = bytes_ = 0; + } else { + codepoint_ = UTF8GetCodepoint( data_->data( ) + pos_ , + bytes_ ); + index_ ++; + } + return true; +} + + +/*= T_String =================================================================*/ + +T_String::T_String( ) noexcept + : data_( &T_EmptyString::EmptyString ) +{ } + +T_String::T_String( char const* initial ) +{ + if ( initial == nullptr || *initial == 0 ) { + data_ = &T_EmptyString::EmptyString; + } else { + const uint32_t len( strlen( initial ) ); + data_ = T_StringPool::Pool.get( initial , len ); + if ( data_ == nullptr ) { + data_ = new T_DynamicString( initial , len , false ); + } + } +} + +T_String::T_String( T_StringBuilder&& sb ) + : T_String( sb.data_ , sb.size_ , true ) +{ + sb.data_ = nullptr; + sb.size_ = sb.length_ = sb.capacity_ = 0; +} + +T_String::T_String( T_StringBuilder const& sb ) + : T_String( sb.data_ , sb.size_ ) +{ } + +T_String::T_String( char const* data , uint32_t size , bool nodup ) +{ + if ( data == nullptr || size == 0 ) { + data_ = &T_EmptyString::EmptyString; + } else { + data_ = new T_DynamicString( data , size , nodup ); + } +} + +/*----------------------------------------------------------------------------*/ + +T_String::T_String( T_String const& source ) + : data_( source.data_ ) +{ + dynamic_cast< RP_StringDataInternal >( data_ )->addUser( ); +} + +T_String::T_String( T_String&& source ) noexcept + : data_( &T_EmptyString::EmptyString ) +{ + swap( *this , source ); +} + +/*----------------------------------------------------------------------------*/ + +T_String::~T_String( ) +{ + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); +} + +/*----------------------------------------------------------------------------*/ + +T_String& T_String::operator= ( T_String&& string ) noexcept +{ + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + data_ = string.data_; + string.data_ = &T_EmptyString::EmptyString; + return *this; +} + +T_String& T_String::operator= ( T_String const& string ) +{ + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + data_ = string.data_; + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->addUser( ); + return *this; +} + +T_String& T_String::operator= ( T_StringBuilder&& sb ) +{ + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + data_ = new T_DynamicString( sb.data_ , sb.size_ , true ); + sb.data_ = nullptr; + sb.size_ = sb.length_ = sb.capacity_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_String& T_String::operator= ( T_StringBuilder const& sb ) +{ + assert( data_ != nullptr ); + dynamic_cast< RP_StringDataInternal >( data_ )->removeUser( ); + data_ = new T_DynamicString( sb.data_ , sb.size_ , false ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_String& lhs , T_String& rhs ) noexcept +{ + using std::swap; + swap( lhs.data_ , rhs.data_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_String T_String::Pooled( char const* data , uint32_t size ) +{ + assert( data != nullptr ); + T_String s; + if ( size ) { + s.data_ = T_StringPool::Pool.add( data , size ); + } + return s; +} + +/*----------------------------------------------------------------------------*/ + +T_String& T_String::addToPool( ) +{ + const auto d( dynamic_cast< RP_StringDataInternal >( data_ ) ); + if ( d->poolable ) { + data_ = T_StringPool::Pool.add( d->data( ) , d->size( ) ); + d->removeUser( ); + } + return *this; +} + + +T_String& T_String::usePool( ) +{ + const auto d( dynamic_cast< RP_StringDataInternal >( data_ ) ); + if ( d->poolable ) { + const auto nd( T_StringPool::Pool.get( d->data( ) , d->size( ) ) ); + if ( nd != nullptr ) { + data_ = nd; + d->removeUser( ); + } + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_String T_String::left( uint32_t count ) const +{ + if ( count >= length( ) ) { + return *this; + } + + const auto end( UTF8GetMemoryOffset( data( ) , count ) ); + T_String s; + s.data_ = new T_Substring( dynamic_cast< RP_StringDataInternal >( data_ ) , 0 , end ); + return s; +} + +T_String T_String::right( uint32_t count ) const +{ + if ( count >= length( ) ) { + return *this; + } + + const auto start( UTF8GetMemoryOffset( data( ) , length( ) - count ) ); + T_String s; + s.data_ = new T_Substring( dynamic_cast< RP_StringDataInternal >( data_ ) , + start , data_->size( ) - start ); + return s; +} + +T_String T_String::substr( uint32_t offset , uint32_t count ) const +{ + // Special cases + if ( offset == 0 ) { + return left( count ); + } + if ( offset >= length( ) || count == 0 ) { + return T_String( ); + } + + const auto end( uint64_t( offset ) + count ); + if ( end >= length( ) ) { + return right( end > length( ) + ? ( length( ) - offset ) + : count ); + } + + const auto start( UTF8GetMemoryOffset( data( ) , offset ) ); + const auto bytes( UTF8GetMemoryOffset( data( ) + start , count ) ); + T_String s; + s.data_ = new T_Substring( dynamic_cast< RP_StringDataInternal >( data_ ) , + start , bytes ); + return s; +} + +/*----------------------------------------------------------------------------*/ + +T_String T_String::trim( ) const noexcept +{ + if ( length( ) == 0 ) { + return T_String( ); + } + + T_Optional< uint32_t > firstNws; + uint32_t lastNws = 0; + T_StringIterator it( *this ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + if ( !c.isWhitespace( ) ) { + if ( !firstNws.present( ) ) { + firstNws = it.index( ); + } + lastNws = it.index( ); + } + it.next( ); + } + + if ( !firstNws.present( ) ) { + return T_String( ); + } + return range( firstNws , lastNws ); +} + +/*----------------------------------------------------------------------------*/ + +int32_t T_String::compare( T_String const& other ) const +{ + if ( this == &other || data_ == other.data_ ) { + return 0; + } + if ( !( valid( ) && other.valid( ) ) ) { + return 0; + } + + T_StringIterator it1( *this ) , it2( other ); + while ( !( it1.atEnd( ) || it2.atEnd( ) ) ) { + T_Character c1( it1 ) , + c2( it2 ); + if ( c1 < c2 ) { + return -1; + } else if ( c1 > c2 ) { + return 1; + } + it1.next( ); + it2.next( ); + } + + if ( !it2.atEnd( ) ) { + return -1; + } else if ( !it1.atEnd( ) ) { + return 1; + } else { + return 0; + } +} + +int32_t T_String::compareIgnoreCase( T_String const& other ) const +{ + if ( !( valid( ) && other.valid( ) ) ) { + return 0; + } + + T_StringIterator it1( *this ) , it2( other ); + while ( !( it1.atEnd( ) || it2.atEnd( ) ) ) { + auto c1( T_Character( it1 ).toLower( ) ) , + c2( T_Character( it2 ).toLower( ) ); + if ( c1 < c2 ) { + return -1; + } else if ( c1 > c2 ) { + return 1; + } + it1.next( ); + it2.next( ); + } + + if ( !it2.atEnd( ) ) { + return -1; + } else if ( !it1.atEnd( ) ) { + return 1; + } else { + return 0; + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_String::startsWith( T_String const& other ) const +{ + if ( !( valid( ) && other.valid( ) ) ) { + return false; + } else if ( other.length( ) > length( ) ) { + return false; + } else if ( other.length( ) == 0 ) { + return true; + } else { + return !memcmp( data( ) , other.data( ) , other.size( ) ); + } +} + +bool T_String::endsWith( T_String const& other ) const +{ + if ( !( valid( ) && other.valid( ) ) ) { + return false; + } else if ( other.length( ) > length( ) ) { + return false; + } else if ( other.length( ) == 0 ) { + return true; + } else { + return !memcmp( data( ) + size( ) - other.size( ) , + other.data( ) , other.size( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +int32_t T_String::find( T_String const& other , uint32_t from ) const +{ + if ( from > length( ) + || length( ) - from < other.length( ) + || !( valid( ) && other.valid( ) ) ) { + return -1; + } + + if ( other.length( ) == 0 ) { + return from; + } + + uint32_t pos = from , byte = UTF8GetMemoryOffset( data( ) , from ); + while ( byte + other.size( ) <= size( ) ) { + char const* tp = data( ) + byte; + char const* op = other.data( ); + if ( !memcmp( tp , op , other.size( ) ) ) { + return pos; + } + byte += UTF8GetMemoryOffset( tp , 1 ); + pos ++; + } + return -1; +} + +/*----------------------------------------------------------------------------*/ + +int32_t T_String::find( T_Character character , uint32_t from ) const +{ + if ( !( valid( ) && character.isValid( ) && from < length( ) ) ) { + return -1; + } + + T_StringIterator it( getIterator( from ) ); + while ( !it.atEnd( ) ) { + if ( T_Character( it ) == character ) { + return it.index( ); + } + it.next( ); + } + return -1; +} + +/*----------------------------------------------------------------------------*/ + +T_String T_String::replace( T_Character initial , T_Character replacement ) const +{ + if ( !( valid( ) && initial.isValid( ) && replacement.isValid( ) ) ) { + return T_String( ); + } + if ( initial == replacement || !*this ) { + return *this; + } + + T_StringBuilder sb; + sb.ensureCapacity( size( ) ); + T_StringIterator it( *this ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + it.next( ); + if ( c == initial ) { + sb << replacement; + } else { + sb << c; + } + } + return T_String( std::move( sb ) ); +} + +T_String T_String::replace( T_String const& initial , T_String const& replacement ) const +{ + if ( !( valid( ) && initial.valid( ) && replacement.valid( ) ) ) { + return T_String( ); + } + + if ( !initial || initial == replacement || initial.length( ) > length( ) || !*this ) { + return *this; + } + if ( initial.length( ) == length( ) && *this == initial ) { + return replacement; + } + + char const* const tgt( initial.data( ) ); + const auto tgtSize( initial.size( ) ); + char const* const src( data( ) ); + const auto srcSize( size( ) ); + + T_StringBuilder sb; + uint32_t pos = 0; + while ( pos < srcSize ) { + uint32_t delta; + if ( pos + tgtSize <= size( ) && !memcmp( src + pos , tgt , tgtSize ) ) { + sb << replacement; + delta = tgtSize; + } else { + sb << T_Character( UTF8GetCodepoint( src + pos , delta ) ); + } + pos += delta; + } + return T_String( std::move( sb ) ); +} + +/*----------------------------------------------------------------------------*/ + +T_Buffer< char > T_String::toOSString( ) const +{ + if ( !valid( ) ) { + return T_Buffer< char >( ); + } + +#ifdef _WIN32 + + const auto n( data_->length( ) ); + T_Buffer< char > output( ( n + 1 ) * 2 ); + if ( !MultiByteToWideChar( CP_UTF8 , 0 , + data( ) , data_->size( ) , + ( wchar_t* ) output.data( ) , n ) ) + { + return T_Buffer< char >( ); + } + output[ n * 2 ] = output[ n * 2 + 1 ] = 0; + return output; + +#else + + const auto n( data_->size( ) ); + T_Buffer< char > output( n + 1 ); + memcpy( output.data( ) , data( ) , n ); + output[ n ] = 0; + return output; + +#endif +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_OBJECT_READER( T_String ) +{ + const uint32_t size( reader.read< uint32_t >( ) ); + if ( size == 0 ) { + return T_String( ); + } + + char* const buffer = ( char* )::operator new ( size ); + const uint32_t r = reader.stream( ).read( buffer , size ); + if ( r != size ) { + throw X_StreamError( E_StreamError::BAD_DATA ); + } + return T_String( buffer , size , true ); +} + +M_DEFINE_OBJECT_WRITER( T_String ) +{ + const uint32_t s( item.size( ) ); + writer.write( s ); + if ( s != 0 ) { + const uint32_t w( writer.stream( ).write( item.data( ) , s ) ); + if ( w != s ) { + throw X_StreamError( E_StreamError::BAD_DATA ); + } + } +} + + +/*= T_StringBuilder ==========================================================*/ + +T_StringBuilder::T_StringBuilder( T_StringBuilder const& other ) + : data_( nullptr ) , capacity_( 0 ) , size_( other.size_ ) , + length_( other.length_ ) +{ + ensureCapacity( size_ ); + if ( size_ != 0 ) { + memcpy( data_ , other.data_ , size_ ); + } +} + +T_StringBuilder::T_StringBuilder( T_StringBuilder&& other ) noexcept + : T_StringBuilder( ) +{ + swap( *this , other ); +} + +T_StringBuilder::T_StringBuilder( char const* data , uint32_t size ) + : T_StringBuilder( ) +{ + ensureCapacity( size ); + memcpy( data_ , data , size ); + size_ = size; + UTF8BufferInfo( data , size , length_ ); +} + +T_StringBuilder::T_StringBuilder( T_String const& string ) + : T_StringBuilder( ) +{ + size_ = string.size( ); + length_ = string.length( ); + ensureCapacity( size_ ); + memcpy( data_ , string.data( ) , size_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder::~T_StringBuilder( ) +{ + ::operator delete ( data_ ); +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_StringBuilder& lhs , T_StringBuilder& rhs ) +{ + using std::swap; + swap( lhs.data_ , rhs.data_ ); + swap( lhs.capacity_ , rhs.capacity_ ); + swap( lhs.size_ , rhs.size_ ); + swap( lhs.length_ , rhs.length_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::operator=( T_StringBuilder const& other ) +{ + ensureCapacity( other.size( ) ); + size_ = other.size_; + length_ = other.length_; + if ( size_ != 0 ) { + memcpy( data_ , other.data_ , size_ ); + } + return *this; +} + +T_StringBuilder& T_StringBuilder::operator=( T_StringBuilder&& other ) noexcept +{ + if ( data_ ) { + ::operator delete( data_ ); + } + data_ = other.data_; + capacity_ = other.capacity_; + size_ = other.size_; + length_ = other.length_; + other.data_ = nullptr; + other.capacity_ = other.size_ = other.length_ = 0; + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::ensureCapacity( uint32_t minCap ) +{ + if ( minCap > capacity_ ) { + const uint32_t mod = minCap % C_GROWTH; + const uint32_t nCap = ( mod != 0 ) + ? ( minCap + C_GROWTH - mod ) : minCap; + char* const nData = ( char* )::operator new ( nCap ); + if ( data_ != nullptr ) { + memcpy( nData , data_ , size_ ); + ::operator delete ( data_ ); + } + data_ = nData; + capacity_ = nCap; + } + return *this; +} + +T_StringBuilder& T_StringBuilder::free( ) +{ + ::operator delete ( data_ ); + capacity_ = 0; + data_ = nullptr; + return clear( ); +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::append( T_StringBuilder const& other ) +{ + const uint32_t sz = other.size_; + if ( sz != 0 ) { + ensureCapacity( size_ + sz ); + memcpy( data_ + size_ , other.data_ , sz ); + size_ += sz; + length_ += other.length_; + } + return *this; +} + +T_StringBuilder& T_StringBuilder::append( T_StringBuilder&& other ) +{ + if ( size_ == 0 && capacity_ <= other.capacity_ ) { + swap( *this , other ); + return *this; + } else { + return append( ( T_StringBuilder const& ) other ); + } +} + +T_StringBuilder& T_StringBuilder::append( T_String const& string ) +{ + const uint32_t sz = string.size( ); + if ( string.size( ) != 0 ) { + ensureCapacity( size_ + sz ); + memcpy( data_ + size_ , string.data( ) , sz ); + size_ += sz; + length_ += string.length( ); + } + return *this; +} + +T_StringBuilder& T_StringBuilder::append( char const* string , uint32_t size ) +{ + if ( size != 0 ) { + uint32_t len; + UTF8BufferInfo( string , size , len ); + ensureCapacity( size_ + size ); + memcpy( data_ + size_ , string , size ); + size_ += size; + length_ += len; + } + return *this; +} + +T_StringBuilder& T_StringBuilder::append( char character ) +{ + if ( uint8_t( character ) < 128 ) { + ensureCapacity( size_ + 1 ); + data_[ size_ ++ ] = character; + length_ ++; + } + return *this; +} + +T_StringBuilder& T_StringBuilder::append( T_Character character ) +{ + if ( character.isValid( ) ) { + ensureCapacity( capacity_ + 4 ); + uint32_t w = UTF8PutCodepoint( data_ + size_ , + capacity_ - size_ , character ); + assert( w != 0 ); + size_ += w; + length_ ++; + } + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::appendNumeric( int64_t value , int base , bool useSep , T_Character sep , + int sepEvery ) +{ + assert( base >= 2 && base <= 36 ); + assert( sepEvery > 0 ); + assert( sep.isValid( ) ); + + if ( value == 0 ) { + return append( '0' ); + } + + const bool neg = value < 0; + const uint32_t size = 64 + ( useSep ? ( 64 / sepEvery ) : 0 ) + + ( neg ? 1 : 0 ); + uint32_t output[ size ]; + uint32_t len = 0 , ecap = 0; + int sepl = 0; + + if ( neg ) { + value = -value; + } + + while ( value != 0 ) { + const uint32_t mod = value % base; + output[ size - ( len + 1 ) ] = mod + + ( mod < 10 ? '0' : ( 'A' - 10 ) ); + len ++; + sepl ++; + ecap ++; + assert( len < size ); + + value = ( value - mod ) / base; + if ( useSep && sepl == sepEvery && value != 0 ) { + output[ size - ( len + 1 ) ] = sep; + len ++; + sepl = 0; + ecap += 4; + assert( len < size ); + } + } + + if ( neg ) { + output[ size - ( len + 1 ) ] = '-'; + len ++; + ecap ++; + assert( len < size ); + } + + ensureCapacity( size_ + ecap ); + ecap = 0; + char* ptr = data_ + size_; + for ( uint32_t i = size - len ; i < size ; i ++ ) { + const uint32_t wr = UTF8PutCodepoint( ptr , 4 , output[ i ] ); + ptr += wr; + ecap += wr; + } + size_ += ecap; + length_ += len; + + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::appendNumeric( uint64_t value , int base , bool useSep , T_Character sep , + int sepEvery ) +{ + assert( base >= 2 && base <= 36 ); + assert( sepEvery > 0 ); + assert( sep.isValid( ) ); + + if ( value == 0 ) { + return append( '0' ); + } + + const uint32_t size = 64 + ( useSep ? ( 64 / sepEvery ) : 0 ); + uint32_t output[ size ]; + uint32_t len = 0 , ecap = 0; + int sepl = 0; + + while ( value != 0 ) { + const uint32_t mod = value % base; + output[ size - ( len + 1 ) ] = mod + + ( mod < 10 ? '0' : ( 'A' - 10 ) ); + len ++; + sepl ++; + ecap ++; + assert( len < size ); + + value = ( value - mod ) / base; + if ( useSep && sepl == sepEvery && value != 0 ) { + output[ size - ( len + 1 ) ] = sep; + len ++; + sepl = 0; + ecap += 4; + assert( len < size ); + } + } + + ensureCapacity( size_ + ecap ); + ecap = 0; + char* ptr = data_ + size_; + for ( uint32_t i = size - len ; i < size ; i ++ ) { + const uint32_t wr = UTF8PutCodepoint( ptr , 4 , output[ i ] ); + ptr += wr; + ecap += wr; + } + size_ += ecap; + length_ += len; + + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_StringBuilder& T_StringBuilder::appendDouble( double value , uint32_t precision , bool trailingZeros ) +{ + char const* const fmt = trailingZeros ? "%.*f" : "%.*g"; + const int nchars = snprintf( nullptr , 0 , fmt , precision , value ); + char buffer[ nchars + 1 ]; + snprintf( buffer , nchars + 1 , fmt , precision , value ); + return append( buffer , nchars ); +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_OBJECT_WRITER( T_StringBuilder ) +{ + writer.write( item.size( ) ); + writer.stream( ).write( item.data( ) , item.size( ) ); +} diff --git a/src/TemplateInstantiation.cc b/src/TemplateInstantiation.cc new file mode 100644 index 0000000..7910c15 --- /dev/null +++ b/src/TemplateInstantiation.cc @@ -0,0 +1,32 @@ +/******************************************************************************/ +/* INSTANTIATION OF VARIOUS TEMPLATE CLASSES **********************************/ +/******************************************************************************/ + +#include +#include +#include +#include +#include + +namespace lw { + +/*----------------------------------------------------------------------------*/ + +template struct T_Comparator< uint32_t >; + +/*----------------------------------------------------------------------------*/ + +template class T_Array< uint32_t >; +template class T_Array< bool >; +template class T_Array< T_String >; +template class T_Array< T_SRDToken >; +template class T_Array< T_SRDInputItem >; + +/*----------------------------------------------------------------------------*/ + +template class T_MultiArray< uint32_t >; +template class T_MultiArray< T_String >; + +/*----------------------------------------------------------------------------*/ + +} diff --git a/src/TextFileLogger.cc b/src/TextFileLogger.cc new file mode 100644 index 0000000..ce7f509 --- /dev/null +++ b/src/TextFileLogger.cc @@ -0,0 +1,159 @@ +/******************************************************************************/ +/* LOGGING SYSTEM - BUILT-IN LOGGERS - TEXT FILE LOGGER ***********************/ +/******************************************************************************/ + +#include +#include +using namespace lw; + + +namespace { + + +char const* const V_Name_ = "text-file"; +inline T_String Name_( ) { return T_String::Pooled( V_Name_ ); } + +bool TFLCPath_( T_SRDParserData const& data ) +{ + auto const& ptok( (*data.input)[ 1 ] ); + T_VFSPath path( ptok.stringValue( ) ); + path = path.normalize( ); + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + data.errors.add( "invalid path" , ptok ); + } else { + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setPath( std::move( path ) ); + } + return true; +} + +bool TFLCAppend_( T_SRDParserData const& data ) +{ + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( true ); + return true; +} + +bool TFLCTruncate_( T_SRDParserData const& data ) +{ + auto lconf( data.targetData->value< RP_LogWriterConfiguration >( ) ); + ( dynamic_cast< T_TextFileLogWriterCfg* >( lconf ) )->setAppend( false ); + return true; +} + +} + + +/*= T_TextFileLogWriterFactory ==================================================*/ + +T_TextFileLogWriterFactory::T_TextFileLogWriterFactory( T_VFS& vfs ) + : A_LogWriterFactory( Name_( ) ) , vfs_( vfs ) +{ } + +RP_LogWriterConfiguration T_TextFileLogWriterFactory::createConfiguration( T_String const& name ) const +{ + RP_LogWriterConfiguration p( new T_TextFileLogWriterCfg( ) ); + p->setName( name ); + return p; +} + +OP_LogWriter T_TextFileLogWriterFactory::createLogWriter( OP_LogWriterConfiguration&& configuration ) const +{ + T_TextFileLogWriter* p( new T_TextFileLogWriter( std::move( configuration ) , vfs_ ) ); + return OwnRawPointer( p ); +} + +void T_TextFileLogWriterFactory::initializeSyntax( T_SRDParserDefs& , T_SRDContext& main ) const +{ + using namespace lw::SRD; + main << ( Rule( ) << "file" << Text( ) << TFLCPath_ ); + main << ( Rule( ) << "append" << TFLCAppend_ ); + main << ( Rule( ) << "truncate" << TFLCTruncate_ ); +} + + +/*= T_TextFileLogWriterCfg ======================================================*/ + +T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( ) + : T_LogWriterConfiguration( Name_( ) ) +{ } + +T_TextFileLogWriterCfg::T_TextFileLogWriterCfg( T_TextFileLogWriterCfg const& source ) + : T_LogWriterConfiguration( source ) , path_( source.path_ ) , + append_( source.append_ ) +{ } + +OP_LogWriterConfiguration T_TextFileLogWriterCfg::clone( ) +{ + T_TextFileLogWriterCfg* ptr( new T_TextFileLogWriterCfg( *this ) ); + return OwnRawPointer( ptr ); +} + +void T_TextFileLogWriterCfg::check( T_SRDErrors& errors , T_SRDList const& input ) +{ + T_LogWriterConfiguration::check( errors , input ); + if ( path_.type( ) == E_VFSPathType::UNKNOWN || path_.elements( ) == 0 ) { + errors.add( "no file selected" , input[ 0 ] ); + } +} + + +/*= T_TextFileLogWriter =========================================================*/ + +T_TextFileLogWriter::T_TextFileLogWriter( OP_LogWriterConfiguration&& configuration , T_VFS& vfs ) + : A_LogWriter( std::move( configuration ) ) , vfs_( vfs ) +{ } + +void T_TextFileLogWriter::log( T_LogTimestamp const& timestamp , + E_LogLevel level , T_LogPath const& path , + T_LogStringData const& data , uint32_t size ) +{ + using namespace std::chrono; + + char timeBuffer[ 128 ]; + std::time_t tst( T_LogTimestamp::clock::to_time_t( timestamp ) ); + std::strftime( timeBuffer , 128 , "%Y-%m-%d %H:%M:%S" , std::gmtime( &tst ) ); + const auto ms( ( duration_cast< milliseconds >( timestamp - T_LogTimestamp( ) ) ).count( ) % 1000 ); + + T_StringBuilder sb; + + sb << timeBuffer << '.'; + if ( ms < 100 ) { + sb << '0'; + if ( ms < 10 ) { + sb << '0'; + } + } + sb << ms << ' ' << path.toString( ) << " - " << level << ": "; + sb.append( &( (*data) [ 0 ] ) , size ); + sb << '\n'; + + auto const& cfg( configuration< T_TextFileLogWriterCfg >( ) ); + auto const& p( cfg.path( ) ); + if ( !file_ ) { + vfs_.mkdir( p.parent( ) ); + file_ = vfs_.file( p , cfg.append( ) ? E_FileMode::READ_WRITE : E_FileMode::OVERWRITE ); + if ( !file_ ) { + disable( ); + return; + } + + try { + file_->open( ); + } catch ( X_StreamError const& ) { + disable( ); + file_.clear( ); + return; + } + } + + try { + file_->position( 0 , true ); + file_->write( sb.data( ) , sb.size( ) ); + file_->flush( ); + } catch ( X_StreamError ) { + disable( ); + file_.clear( ); + return; + } +} diff --git a/src/Utilities.cc b/src/Utilities.cc new file mode 100644 index 0000000..9e1c83b --- /dev/null +++ b/src/Utilities.cc @@ -0,0 +1,160 @@ +/******************************************************************************/ +/* VARIOUS UTILITIES **********************************************************/ +/******************************************************************************/ + + +#include +using namespace lw; + + +namespace { + + +/*= SORTING INTERNALS - QUICKSORT ============================================*/ + +static const int32_t C_MIN_QS_ITEMS_ = 4; + +/*----------------------------------------------------------------------------*/ +// T_Stack_ - Quicksort stack + +struct T_Stack_ +{ + uint8_t* low; + uint8_t* high; +}; + + +inline void QSPush_( T_Stack_*& stack , uint8_t* low , uint8_t* high ) +{ + stack->low = low; + stack->high = high; + stack ++; +} + +inline void QSPop_( T_Stack_*& stack , uint8_t*& low , uint8_t*& high ) +{ + stack --; + low = stack->low; + high = stack->high; +} + +/*----------------------------------------------------------------------------*/ + +inline void Quicksort_( uint8_t* data , uint32_t itemSize , uint32_t items , T_Sorter_::F_Swap swap , + T_Sorter_::F_Cmp cmp ) +{ + const int32_t minSize = itemSize * C_MIN_QS_ITEMS_; + + uint8_t* low( data ); + uint8_t* high( low + itemSize * ( items - 1 ) ); + + T_Stack_ stackData[ 8 * sizeof( uint32_t ) ]; + T_Stack_* stack( &stackData[ 0 ] ); + QSPush_( stack , nullptr , nullptr ); + + while ( stack > &stackData[ 0 ] ) { + // Select pivot, sort low/pivot/high + uint8_t* mid( low + itemSize * ( ( high - low ) / itemSize >> 1 ) ); + if ( cmp( mid , low ) < 0 ) { + swap( mid , low ); + } + if ( cmp( high , mid ) < 0 ) { + swap( mid , high ); + if ( cmp( mid , low ) < 0 ) { + swap( mid , low ); + } + } + + // Partitioning + uint8_t* left( low + itemSize ) , + *right( high - itemSize ); + do { + while ( cmp( left , mid ) < 0 ) { + left += itemSize; + } + while ( cmp( mid , right ) < 0 ) { + right -= itemSize; + } + + if ( left < right ) { + swap( left , right ); + if ( left == mid ) { + mid = right; + } else if ( right == mid ) { + mid = left; + } + } + if ( left <= right ) { + left += itemSize; + right -= itemSize; + } + } while ( left <= right ); + + // Setup for next iteration + if ( right - low <= minSize ) { + if ( high - left <= minSize ) { + QSPop_( stack , low , high ); + } else { + low = left; + } + } else if ( high - left <= minSize ) { + high = right; + } else if ( right - low > high - left ) { + QSPush_( stack , low , right ); + low = left; + } else { + QSPush_( stack , left , high ); + high = right; + } + } +} + + +/*= SORTING INTERNALS - INSERTION SORT =======================================*/ + +inline void InsertionSort_( uint8_t* data , uint32_t itemSize , uint32_t items , T_Sorter_::F_Swap swap , + T_Sorter_::F_Cmp cmp ) +{ + uint8_t* const end( data + itemSize* items ); + for ( uint8_t* i( data + itemSize ) ; i < end ; i += itemSize ) { + auto j = i; + while ( j != data && cmp( j - itemSize , j ) > 0 ) { + swap( j , j - itemSize ); + j -= itemSize; + } + } +} + +} // namespace + + +/*= T_Sorter_ ================================================================*/ + +void T_Sorter_::sort( uint8_t* data , uint32_t itemSize , uint32_t items , F_Swap swap , F_Cmp cmp ) +{ + if ( items != 0 ) { + if ( items > C_MIN_QS_ITEMS_ ) { + Quicksort_( data , itemSize , items , swap , cmp ); + } + InsertionSort_( data , itemSize , items , swap , cmp ); + } +} + + +/*= HashData( ) ==============================================================*/ + +uint32_t lw::HashData( uint8_t const* data , uint32_t size ) +{ + uint32_t hash = 111119; + while ( size > 0 ) { + hash += *data; + hash += ( hash << 10 ); + hash ^= ( hash >> 6 ); + data ++; + size --; + } + hash += ( hash << 3 ); + hash ^= ( hash >> 11 ); + hash += ( hash << 15 ); + return hash; +} diff --git a/src/VFS.cc b/src/VFS.cc new file mode 100644 index 0000000..26619b2 --- /dev/null +++ b/src/VFS.cc @@ -0,0 +1,930 @@ +/******************************************************************************/ +/* VIRTUAL FILE SYSTEM ********************************************************/ +/******************************************************************************/ + +#include +#ifndef _WIN32 +# include +# include +# include +#endif +using namespace lw; + + +/*= VARIOUS INTERNALS ========================================================*/ + +namespace { + +bool MkDir_( T_String const& path ) +{ + const auto chars( path.toOSString( ) ); +#ifdef _WIN32 + return CreateDirectoryW( ( wchar_t const* ) &chars[ 0 ] , nullptr ); +#else + return mkdir( ( char const* ) &chars[ 0 ] , 0755 ) == 0; +#endif +} + +E_VFSEntryType TypeOfPath_( T_String const& path ) +{ + const auto chars( path.toOSString( ) ); + +#ifdef _WIN32 + + uint32_t fa( GetFileAttributesW( (wchar_t const*) & chars[ 0 ] ) ); + if ( fa == INVALID_FILE_ATTRIBUTES ) { + return E_VFSEntryType::NONE; + } else if ( ( fa & FILE_ATTRIBUTE_DIRECTORY ) != 0 ) { + return E_VFSEntryType::DIRECTORY; + } else if ( ( fa & FILE_ATTRIBUTE_NORMAL ) != 0 ) { + return E_VFSEntryType::FILE; + } else { + return E_VFSEntryType::OTHER; + } + +#else + + struct stat fa; + if ( stat( ( char const* ) &chars[ 0 ] , &fa ) ) { + return E_VFSEntryType::NONE; + } + + const auto masked( fa.st_mode & S_IFMT ); + switch ( masked ) { + case S_IFREG: + return E_VFSEntryType::FILE; + + case S_IFDIR: + return E_VFSEntryType::DIRECTORY; + + default: + return E_VFSEntryType::OTHER; + } + +#endif +} + +} // namespace + + +/*= T_DirLister_ =============================================================*/ + +namespace { + +class T_DirLister_ +{ + private: + T_Buffer< char > path_; +#ifdef _WIN32 + HANDLE handle_; + WIN32_FIND_DATAW output_; +#else + DIR* dir_; + struct dirent* output_; +#endif + + public: + explicit T_DirLister_( T_Buffer< char > path ); + ~T_DirLister_( ); + + bool start( ); + void next( ); + bool hasValue( ) const; + T_String getName( ) const; +}; + + +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_DirLister_::T_DirLister_( T_Buffer< char > path ) + : path_( std::move( path ) ) +{ +#ifdef _WIN32 +# error "Not implemented" +#else + dir_ = nullptr; + output_ = nullptr; +#endif +} + +T_DirLister_::~T_DirLister_( ) +{ +#ifdef _WIN32 +# error "Not implemented" +#else + if ( dir_ != nullptr ) { + closedir( dir_ ); + } +#endif +} + +inline bool T_DirLister_::start( ) +{ +#ifdef _WIN32 +# error "Not implemented" +#else + dir_ = opendir( &path_[ 0 ] ); + if ( dir_ == nullptr ) { + return false; + } + output_ = readdir( dir_ ); + return true; +#endif +} + +inline void T_DirLister_::next( ) +{ +#ifdef _WIN32 +# error "Not implemented" +#else + output_ = readdir( dir_ ); +#endif +} + +inline bool T_DirLister_::hasValue( ) const +{ +#ifdef _WIN32 +# error "Not implemented" +#else + return output_ != nullptr; +#endif +} + +inline T_String T_DirLister_::getName( ) const +{ +#ifdef _WIN32 +# error "Not implemented" +#else + return T_String( output_->d_name , strlen( output_->d_name ) ); +#endif +} + + +/*= T_VFSPath ================================================================*/ + +T_VFSPath::T_VFSPath( E_VFSPathType type ) + : type_( type ) , elements_( 16 ) +{ } + + +T_VFSPath::T_VFSPath( T_String const& path ) + : T_VFSPath( ) +{ + extractPath( path ); +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath::T_VFSPath( T_VFSPath const& parent , T_VFSPath const& child ) + : T_VFSPath( parent.type_ ) +{ + if ( child.type( ) == E_VFSPathType::UNKNOWN ) { + type_ = E_VFSPathType::UNKNOWN; + } + if ( type_ == E_VFSPathType::UNKNOWN || child.type( ) == E_VFSPathType::UNKNOWN ) { + return; + } + + if ( child.type_ == E_VFSPathType::INVALID ) { + type_ = E_VFSPathType::INVALID; + } + elements_.ensureCapacity( parent.elements( ) + child.elements( ) ); + elements_.addAll( parent.elements_ ); + elements_.addAll( child.elements_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath::T_VFSPath( T_VFSPath const& other ) + : type_( other.type_ ) , elements_( other.elements_ ) +{ } + +T_VFSPath::T_VFSPath( T_VFSPath&& other ) noexcept + : T_VFSPath( ) +{ + swap( *this , other ); +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath& T_VFSPath::operator= ( T_VFSPath const& other ) +{ + type_ = other.type_; + elements_ = other.elements_; + return *this; +} + +T_VFSPath& T_VFSPath::operator= ( T_VFSPath&& other ) noexcept +{ + swap( *this , other ); + return *this; +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath::operator T_String ( ) const +{ + T_StringBuilder sb; + if ( type_ == E_VFSPathType::ABSOLUTE ) { + sb << '/'; + } + + const auto n( elements_.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + if ( i > 0 ) { + sb << '/'; + } + sb << elements_[ i ]; + } + + return T_String( std::move( sb ) ); +} + +/*----------------------------------------------------------------------------*/ + +void lw::swap( T_VFSPath& lhs , T_VFSPath& rhs ) noexcept +{ + using std::swap; + swap( lhs.type_ , rhs.type_ ); + swap( lhs.elements_ , rhs.elements_ ); +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath T_VFSPath::normalize( ) const +{ + T_VFSPath normed; + if ( type_ == E_VFSPathType::UNKNOWN || type_ == E_VFSPathType::INVALID ) { + return normed; + } + + normed.type_ = type_; + const auto absolute( type_ == E_VFSPathType::ABSOLUTE ); + const auto n( elements_.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto const& e( elements_[ i ] ); + + // Simply remove .'s + if ( e == "." ) { + continue; + } + + if ( e == ".." ) { + auto const te( normed.elements( ) ); + + // Remove ..'s at the start of an absolute path + if ( te == 0 && absolute ) { + continue; + } + + // Use ..'s to remove stuff like "dir/../wat/../wut/.." + if ( te != 0 && normed.elements_[ te - 1 ] != ".." ) { + normed.elements_.remove( te - 1 ); + continue; + } + } + normed.elements_.add( e ); + } + + if ( normed.elements( ) == 0 && normed.type( ) == E_VFSPathType::RELATIVE ) { + normed.elements_.add( T_String::Pooled( "." ) ); + } + + return normed; +} + +/*----------------------------------------------------------------------------*/ + +T_VFSPath T_VFSPath::parent( ) const +{ + if ( type_ == E_VFSPathType::UNKNOWN || type_ == E_VFSPathType::INVALID + || elements_.size( ) == 0 ) { + return *this; + } + + if ( type_ == E_VFSPathType::RELATIVE && elements_.size( ) == 1 ) { + T_VFSPath p( E_VFSPathType::RELATIVE ); + p.elements_.add( T_String::Pooled( "." ) ); + return p; + } + + T_VFSPath p( *this ); + p.elements_.remove( elements_.size( ) - 1 ); + return p; +} + +/*----------------------------------------------------------------------------*/ + +bool T_VFSPath::operator== ( T_VFSPath const& other ) const +{ + if ( this == &other ) { + return true; + } + + const auto n( elements( ) ); + if ( type_ != other.type_ || n != other.elements( ) ) { + return false; + } + + for ( uint32_t i = 0 ; i < n ; i ++ ) { + if ( elements_[ i ] != other[ i ] ) { + return false; + } + } + + return true; +} + +/*----------------------------------------------------------------------------*/ + +void T_VFSPath::extractPath( T_String const& path ) +{ + // Extract the path's elements + T_StringIterator it( path ); + bool elementStarted( false ) , absolute( false ) , invalid( false ); + uint32_t startIndex , index( 0 ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + + if ( c == '/' ) { + if ( elementStarted ) { + elements_.add( path.range( startIndex , it.index( ) - 1 ) ); + elementStarted = false; + } + if ( it.index( ) == 0 ) { + absolute = true; + } + } else { + if ( !( c.isNumeric( ) || c.isLowercase( ) + || c == '-' || c == '_' || c == '.' ) ) { + invalid = true; + } + if ( !elementStarted ) { + elementStarted = true; + startIndex = it.index( ); + } + } + + it.next( ); + index ++; + } + + if ( elementStarted ) { + elements_.add( path.substr( startIndex ) ); + } + + // Only "." and ".." should start with a dot + if ( !invalid ) { + const auto n( elements_.size( ) ); + for ( uint32_t i = 0 ; i < n && !invalid ; i ++ ) { + T_String const& s( elements_[ i ] ); + if ( s[ 0 ] == '.' ) { + invalid = ( s != "." && s != ".." ); + } + } + } + + // Set the type + if ( invalid ) { + type_ = E_VFSPathType::INVALID; + } else if ( absolute ) { + type_ = E_VFSPathType::ABSOLUTE; + } else if ( elements_.size( ) == 0 ) { + type_ = E_VFSPathType::UNKNOWN; + } else { + type_ = E_VFSPathType::RELATIVE; + } +} + +/*----------------------------------------------------------------------------*/ + +M_DEFINE_HASH( T_VFSPath ) +{ + uint32_t hash( uint32_t( item.type( ) ) << 16 ); + const auto n( item.elements( ) ); + hash ^= n; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + hash = ( hash << 11 ) | ( hash >> 21 ); + hash ^= ComputeHash( item[ i ] ); + } + return hash; +} + +/*= A_VFSDriver ==============================================================*/ + +bool A_VFSDriver::init( ) +{ + return true; +} + +void A_VFSDriver::shutdown( ) +{ } + +OP_File A_VFSDriver::file( T_VFSPath const& ) +{ + return OP_File( ); +} + + +/*= T_VFSFilesystemDriver ====================================================*/ + +#ifdef _WIN32 +# define C_SEPARATOR_ '\\' +#else +# define C_SEPARATOR_ '/' +#endif + +T_VFSFilesystemDriver::T_VFSFilesystemDriver( T_String root ) + : root_( root ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_VFSFilesystemDriver::init( ) +{ + const auto path( root_.toOSString( ) ); + +#ifdef _WIN32 + + uint32_t fa( GetFileAttributesW( (wchar_t const*) & path[ 0 ] ) ); + return fa != INVALID_FILE_ATTRIBUTES && ( fa & FILE_ATTRIBUTE_DIRECTORY ) != 0; + +#else + + struct stat fa; + if ( stat( ( char const* ) &path[ 0 ] , &fa ) ) { + return false; + } + return ( fa.st_mode & S_IFMT ) == S_IFDIR; + +#endif +} + +/*----------------------------------------------------------------------------*/ + +E_VFSEntryType T_VFSFilesystemDriver::typeOf( T_VFSPath const& path ) +{ + return TypeOfPath_( getFullPath( path ) ); +} + +bool T_VFSFilesystemDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index ) +{ + T_DirLister_ dl( getOSPath( path ) ); + if ( !dl.start( ) ) { + return false; + } + while ( dl.hasValue( ) ) { + const auto v( dl.getName( ) ); + dl.next( ); + if ( v == "." || v == ".." ) { + continue; + } + + const T_VFSPath entry( v ); + if ( entry.type( ) == E_VFSPathType::INVALID ) { + continue; + } + + const uint32_t hash( ComputeHash( entry ) ); + uint32_t idx( index.first( hash ) ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( values[ idx ] == entry ) { + break; + } + idx = index.next( idx ); + } + if ( idx == T_HashIndex::INVALID_INDEX ) { + index.add( hash ); + values.add( entry ); + } + } + return true; +} + +OP_InputStream T_VFSFilesystemDriver::read( T_VFSPath const& path ) +{ + if ( typeOf( path ) != E_VFSEntryType::FILE ) { + return OP_InputStream( ); + } else { + auto f( file( path ) ); + if ( f ) { + return NewOwned< T_FileInputStream >( std::move( f ) ); + } else { + return OP_InputStream( ); + } + } +} + +OP_File T_VFSFilesystemDriver::file( T_VFSPath const& path ) +{ + if ( typeOf( path ) != E_VFSEntryType::FILE ) { + return OP_File( ); + } + return NewOwned< T_File >( getFullPath( path ) , E_FileMode::READ_ONLY ); +} + +/*----------------------------------------------------------------------------*/ + +T_String T_VFSFilesystemDriver::getFullPath( T_VFSPath const& path ) const +{ + T_StringBuilder sb( root_ ); + const auto n( path.elements( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + sb << C_SEPARATOR_ << path[ i ]; + } + return T_String( std::move( sb ) ); +} + +inline T_Buffer< char > T_VFSFilesystemDriver::getOSPath( T_VFSPath const& path ) const +{ + return getFullPath( path ).toOSString( ); +} + + +/*= T_VFSUserDirectory_ ======================================================*/ + +namespace { + +class T_VFSUserDirectory_ : public T_VFSFilesystemDriver +{ + public: + explicit T_VFSUserDirectory_( T_String const& base ); + + bool init( ) override; + + OP_File file( T_VFSPath const& path ) override; + OP_File file( T_VFSPath const& path , E_FileMode mode ); + OP_OutputStream write( T_VFSPath const& path ); + bool mkdir( T_VFSPath const& path ) const; + bool rmdir( T_VFSPath const& path ) const; + bool rm( T_VFSPath const& path ) const; + bool move( T_VFSPath const& from , T_VFSPath const& to ) const; +}; + + +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_VFSUserDirectory_::T_VFSUserDirectory_( T_String const& base ) + : T_VFSFilesystemDriver( base ) +{ } + +bool T_VFSUserDirectory_::init( ) +{ + if ( T_VFSFilesystemDriver::init( ) ) { + return true; + } + + // Try creating the user directory + T_String const& r( root( ) ); + int32_t nextSep( r.find( C_SEPARATOR_ ) ); + while ( 1 ) { + const T_String p( nextSep == -1 ? r : r.substr( 0 , nextSep ) ); + if ( p ) { + const auto t( TypeOfPath_( p ) ); + if ( ( t != E_VFSEntryType::DIRECTORY && t != E_VFSEntryType::NONE ) + || ( t == E_VFSEntryType::NONE && !MkDir_( p ) ) ) { + return false; + } + } + if ( nextSep == -1 ) { + break; + } else { + nextSep = r.find( C_SEPARATOR_ , nextSep + 1 ); + } + } + + return true; +} + +/*----------------------------------------------------------------------------*/ + +OP_File T_VFSUserDirectory_::file( T_VFSPath const& path ) +{ + return T_VFSFilesystemDriver::file( path ); +} + +OP_File T_VFSUserDirectory_::file( T_VFSPath const& path , E_FileMode mode ) +{ + if ( mode == E_FileMode::READ_ONLY ) { + return file( path ); + } + + const auto fp( getFullPath( path ) ); + const auto ft( TypeOfPath_( fp ) ); + if ( ft != E_VFSEntryType::FILE && ft != E_VFSEntryType::NONE ) { + return OP_File( ); + } + return NewOwned< T_File >( fp , mode ); +} + +/*----------------------------------------------------------------------------*/ + +OP_OutputStream T_VFSUserDirectory_::write( T_VFSPath const& path ) +{ + auto f( file( path , E_FileMode::OVERWRITE ) ); + if ( f ) { + return NewOwned< T_FileOutputStream >( std::move( f ) ); + } else { + return OP_OutputStream( ); + } +} + +/*----------------------------------------------------------------------------*/ + +bool T_VFSUserDirectory_::mkdir( T_VFSPath const& path ) const +{ + T_StringBuilder sb( root( ) ); + const auto n( path.elements( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + sb << C_SEPARATOR_ << path[ i ]; + if ( !MkDir_( sb ) ) { + return false; + } + } + return true; +} + +bool T_VFSUserDirectory_::rmdir( T_VFSPath const& path ) const +{ + const auto chars( getOSPath( path ) ); +#ifdef _WIN32 + return RemoveDirectoryW( ( wchar_t const* ) &chars[ 0 ] ); +#else + return ::rmdir( ( char const* ) &chars[ 0 ] ) == 0; +#endif +} + +bool T_VFSUserDirectory_::rm( T_VFSPath const& path ) const +{ + const auto chars( getOSPath( path ) ); +#ifdef _WIN32 + return DeleteFileW( ( wchar_t const* ) &chars[ 0 ] ); +#else + return unlink( ( char const* ) &chars[ 0 ] ) == 0; +#endif +} + +bool T_VFSUserDirectory_::move( T_VFSPath const& from , T_VFSPath const& to ) const +{ + const auto charsFrom( getOSPath( from ) ); + const auto charsTo( getOSPath( to ) ); +#ifdef _WIN32 + return MoveFileW( ( wchar_t const* ) &charsFrom[ 0 ] , + ( wchar_t const* ) &charsTo[ 0 ] ); +#else + return rename( ( char const* ) &charsFrom[ 0 ] , + ( char const* ) &charsTo[ 0 ] ) == 0; +#endif +} + + +/*= T_VFSPrivate_ ============================================================*/ + +namespace { +struct T_VFSPrivate_ +{ + T_RegisteredItem::SP_Unregister unregisterFunction_{ + NewShared< T_RegisteredItem::F_Unregister >( + [this]( void* data ) { + auto const n( drivers_.size( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + auto& p( drivers_[ i ] ); + if ( p.get( ) == data ) { + p->shutdown( ); + drivers_.removeSwap( i ); + break; + } + } + } + ) }; + OP_VFSFilesystemDriver userDir_; + T_Array< OP_VFSDriver > drivers_{ 16 }; + + static T_String findUserDir( ); + void initUserDir( T_String const& dir ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_String T_VFSPrivate_::findUserDir( ) +{ +#ifdef _WIN32 +# error "Not implemented!" +#else + T_String base; + char const* const lwHome( getenv( "LWHOME" ) ); + if ( lwHome == nullptr ) { + char const* const userHome( getenv( "HOME" ) ); + if ( userHome == nullptr ) { + throw X_VFSInitialisationFailure( ); + } else { + T_StringBuilder sb( userHome ); + sb << "/.local/share/LegacyWorlds"; + base = std::move( sb ); + } + } else { + base = T_String( lwHome , strlen( lwHome ) ); + } + return base; +#endif +} + +void T_VFSPrivate_::initUserDir( T_String const& dir ) +{ + assert( !userDir_ ); + userDir_ = NewOwned< T_VFSUserDirectory_ >( dir ); + if ( !userDir_->init( ) ) { + throw X_VFSInitialisationFailure( ); + } +} + + +/*= T_VFS ====================================================================*/ + +T_VFS::T_VFS( ) + : A_PrivateImplementation( new T_VFSPrivate_( ) ) +{ + p< T_VFSPrivate_ >( ).initUserDir( T_VFSPrivate_::findUserDir( ) ); +} + +T_VFS::T_VFS( T_String const& userDir ) + : A_PrivateImplementation( new T_VFSPrivate_( ) ) +{ + p< T_VFSPrivate_ >( ).initUserDir( userDir ); +} + +T_RegisteredItem T_VFS::addDriver( OP_VFSDriver&& driver ) +{ + assert( driver ); + const bool ok( driver->init( ) ); + if ( ok ) { + auto& pi( p< T_VFSPrivate_ >( ) ); + void* const ptr( driver.get( ) ); + pi.drivers_.add( std::move( driver ) ); + return T_RegisteredItem( pi.unregisterFunction_ , ptr ); + } + return T_RegisteredItem( ); +} + +/*----------------------------------------------------------------------------*/ + +E_VFSEntryType T_VFS::typeOf( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return E_VFSEntryType::NONE; + } + + auto& pi( p< T_VFSPrivate_ >( ) ); + const auto np( path.normalize( ) ); + E_VFSEntryType rv( pi.userDir_->typeOf( np ) ); + const auto nd( pi.drivers_.size( ) ); + for ( uint32_t i = nd ; i != 0 && rv == E_VFSEntryType::NONE ; i -- ) { + rv = pi.drivers_[ i - 1 ]->typeOf( np ); + } + + return rv; +} + +bool T_VFS::list( T_VFSPath const& path , T_Array< T_VFSPath >& output ) const +{ + output.clear( ); + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return false; + } + + const auto np( path.normalize( ) ); + auto& pi( p< T_VFSPrivate_ >( ) ); + T_HashIndex index( 1024 , 256 , 256 ); + bool rv( pi.userDir_->list( np , output , index ) ); + const auto nd( pi.drivers_.size( ) ); + for ( uint32_t i = nd ; i != 0 ; i -- ) { + rv = pi.drivers_[ i - 1 ]->list( np , output , index ) || rv; + } + + return rv; +} + +OP_InputStream T_VFS::read( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return OP_InputStream( ); + } + + const auto np( path.normalize( ) ); + auto& pi( p< T_VFSPrivate_ >( ) ); + OP_InputStream rv( pi.userDir_->read( np ) ); + const auto nd( pi.drivers_.size( ) ); + for ( uint32_t i = nd ; i != 0 && !rv ; i -- ) { + rv = pi.drivers_[ i - 1 ]->read( np ); + } + return rv; +} + +OP_File T_VFS::file( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return OP_File( ); + } + + const auto np( path.normalize( ) ); + auto& pi( p< T_VFSPrivate_ >( ) ); + OP_File rv( pi.userDir_->file( np ) ); + const auto nd( pi.drivers_.size( ) ); + for ( uint32_t i = nd ; i != 0 && !rv ; i -- ) { + rv = pi.drivers_[ i - 1 ]->file( np ); + } + + return rv; +} + +/*----------------------------------------------------------------------------*/ + +OP_File T_VFS::file( T_VFSPath const& path , E_FileMode mode ) const +{ + if ( mode == E_FileMode::READ_ONLY ) { + return file( path ); + } else if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return OP_File( ); + } else { + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->file( path.normalize( ) , mode ); + } +} + +OP_OutputStream T_VFS::write( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return OP_OutputStream( ); + } else { + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->write( path.normalize( ) ); + } +} + +bool T_VFS::mkdir( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return false; + } else { + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->mkdir( path.normalize( ) ); + } +} + +bool T_VFS::rmdir( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return false; + } else { + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->rmdir( path.normalize( ) ); + } +} + +bool T_VFS::rm( T_VFSPath const& path ) const +{ + if ( path.type( ) != E_VFSPathType::ABSOLUTE ) { + return false; + } else { + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->rm( path.normalize( ) ); + } +} + +bool T_VFS::move( T_VFSPath const& from , T_VFSPath const& to ) const +{ + if ( from.type( ) != E_VFSPathType::ABSOLUTE ) { + return false; + } + + const auto nFrom( from.normalize( ) ); + if ( from.elements( ) == 0 ) { + return false; + } + + T_VFSPath nTo; + if ( to.type( ) == E_VFSPathType::ABSOLUTE ) { + nTo = to.normalize( ); + } else if ( to.type( ) == E_VFSPathType::RELATIVE ) { + nTo = T_VFSPath( nFrom , T_VFSPath( ".." , to ) ).normalize( ); + } else { + return false; + } + if ( nTo.elements( ) == 0 ) { + return false; + } + + return dynamic_cast< T_VFSUserDirectory_* >( + p< T_VFSPrivate_ >( ).userDir_.get( ) ) + ->move( nFrom , nTo ); +} diff --git a/src/VFSDrivers.cc b/src/VFSDrivers.cc new file mode 100644 index 0000000..dc93d1e --- /dev/null +++ b/src/VFSDrivers.cc @@ -0,0 +1,614 @@ +/******************************************************************************/ +/* VIRTUAL FILE SYSTEM - NON-ESSENTIAL DRIVERS ********************************/ +/******************************************************************************/ + +#include +#include +#include +#include +using namespace lw; + + +/*= T_VFSDataIndexPrivate_ ===================================================*/ + +namespace { +struct T_VFSDataIndexPrivate_ +{ + // Information about an entry. + struct T_EntryInfo { + uint32_t index; + bool isFile; + }; + + const uint32_t magic_; + T_KeyValueTable< T_VFSPath , T_EntryInfo > entries_; + T_MultiArray< T_String > dirEntries_; + T_Array< T_VFSDataIndex::T_FileInfo > files_; + + // --------------------------------------------------------------------- + + explicit T_VFSDataIndexPrivate_( + uint32_t magic ); + + // --------------------------------------------------------------------- + + // Read the index from a stream + bool readIndex( + T_BinaryReader& reader ); + // Free the index + void free( ); + + // --------------------------------------------------------------------- + + // Get the type of an entry + E_VFSEntryType typeOf( T_VFSPath const& path ) const; + + // List a directory + bool list( + T_VFSPath const& path , + T_Array< T_VFSPath >& values , + T_HashIndex& index ) const; + + // Returns the file information record for a given path + T_VFSDataIndex::T_FileInfo const* getFileInfo( + T_VFSPath const& path ) const; +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +inline T_VFSDataIndexPrivate_::T_VFSDataIndexPrivate_( + uint32_t magic ) + : magic_( magic ) +{ } + +/*----------------------------------------------------------------------------*/ + +inline bool T_VFSDataIndexPrivate_::readIndex( + T_BinaryReader& reader ) +{ + /* + * FIXME clean this up later + * + * Format: + * + * magic + * version + * root directory + * other directories + * file data + * + * a directory: + * number of entries + * list of entries + * + * a directory entry: + * Entry's name (T_String) + * is file? (0/1) + * if it's a file: + * size (uint32_t) + * offset relative to start of data (uint32_t) + * + * size of a directory entry: + * size( name ) + 4 + isFile ? 8 : 0 + * + * size of a directory list: + * 4 + sum( size( entry ) for all entries ) + * + */ + if ( reader.read< uint32_t >( ) != magic_ ) { + return false; + } + if ( reader.read< uint32_t >( ) != 0 ) { + return false; + } + + const auto inSize( reader.stream( ).size( ) ); + T_RingBuffer< T_VFSPath > directories; + directories.put( T_VFSPath( "/" ) ); + entries_.add( T_VFSPath( "/" ) , T_EntryInfo{ 0 , false } ); + uint32_t dirReserved( 1 ); + + T_VFSPath current; + while ( directories.size( ) ) { + directories.readNext( current ); + dirEntries_.next( ); + + const auto nEntries( reader.read< uint32_t >( ) ); + for ( uint32_t i = 0 ; i < nEntries ; i ++ ) { + T_String name( reader.read< T_String >( ) ); + const T_VFSPath path( current , name ); + + const bool isFile( reader.read< uint8_t >( ) ); + const uint32_t index( isFile ? files_.size( ) : dirReserved ); + dirEntries_.add( name ); + if ( !entries_.add( path , T_EntryInfo{ index , isFile } ) ) { + return false; + } + + if ( isFile ) { + const auto size( reader.read< uint32_t >( ) ); + const auto offset( reader.read< uint32_t >( ) ); + if ( offset >= inSize || offset + size > inSize ) { + return false; + } + files_.add( T_VFSDataIndex::T_FileInfo{ size , offset } ); + } else { + directories.put( path ); + dirReserved ++; + } + } + } + + return dirReserved == dirEntries_.size( ); +} + +inline void T_VFSDataIndexPrivate_::free( ) +{ + entries_.free( ); + dirEntries_.free( ); + files_.free( ); +} + +/*----------------------------------------------------------------------------*/ + +inline E_VFSEntryType T_VFSDataIndexPrivate_::typeOf( + T_VFSPath const& path ) const +{ + T_EntryInfo const* eptr( entries_.get( path ) ); + if ( eptr == nullptr ) { + return E_VFSEntryType::NONE; + } + return eptr->isFile ? E_VFSEntryType::FILE : E_VFSEntryType::DIRECTORY; +} + +inline bool T_VFSDataIndexPrivate_::list( + T_VFSPath const& path , + T_Array< T_VFSPath >& values , + T_HashIndex& index ) const +{ + T_EntryInfo const* eptr( entries_.get( path ) ); + if ( eptr == nullptr || eptr->isFile ) { + return false; + } + + const auto first( dirEntries_.firstOf( eptr->index ) ); + const auto size( dirEntries_.sizeOf( eptr->index ) ); + for ( uint32_t i = 0 ; i < size ; i ++ ) { + const T_VFSPath p( dirEntries_[ i + first ] ); + const uint32_t hash( ComputeHash( p ) ); + uint32_t idx( index.first( hash ) ); + while ( idx != T_HashIndex::INVALID_INDEX ) { + if ( values[ idx ] == p ) { + break; + } + idx = index.next( idx ); + } + if ( idx == T_HashIndex::INVALID_INDEX ) { + index.add( hash ); + values.add( p ); + } + } + return true; +} + +inline T_VFSDataIndex::T_FileInfo const* T_VFSDataIndexPrivate_::getFileInfo( + T_VFSPath const& path ) const +{ + T_EntryInfo const* eptr( entries_.get( path ) ); + if ( eptr == nullptr || !eptr->isFile ) { + return nullptr; + } + return &files_[ eptr->index ]; +} + + +/*= T_VFSDataIndex ===========================================================*/ + +T_VFSDataIndex::T_VFSDataIndex( + uint32_t magic ) + : A_PrivateImplementation( new T_VFSDataIndexPrivate_( magic ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +bool T_VFSDataIndex::readIndex( + T_BinaryReader& reader ) +{ + return p< T_VFSDataIndexPrivate_ >( ).readIndex( reader ); +} + +void T_VFSDataIndex::free( ) +{ + return p< T_VFSDataIndexPrivate_ >( ).free( ); +} + +/*----------------------------------------------------------------------------*/ + +E_VFSEntryType T_VFSDataIndex::typeOf( + T_VFSPath const& path ) const +{ + return p< T_VFSDataIndexPrivate_ >( ).typeOf( path ); +} + +bool T_VFSDataIndex::list( + T_VFSPath const& path , + T_Array< T_VFSPath >& values , + T_HashIndex& index ) const +{ + return p< T_VFSDataIndexPrivate_ >( ).list( path , values , index ); +} + +T_VFSDataIndex::T_FileInfo const* T_VFSDataIndex::getFileInfo( + T_VFSPath const& path ) const +{ + return p< T_VFSDataIndexPrivate_ >( ).getFileInfo( path ); +} + + +/*= T_VFSRomDriverStream_ ====================================================*/ + +namespace lw { + +M_CLASS_POINTERS( VFSRomDriverStream_ ); +class T_VFSRomDriverStream_ : public A_InputStream +{ + private: + RP_VFSRomDriverStream_& head_; + T_ReadWriteMutex& mutex_; + + RP_VFSRomDriverStream_ prev_; + RP_VFSRomDriverStream_ next_; + OP_MemoryInputStream actual_; + + public: + T_VFSRomDriverStream_( ) = delete; + T_VFSRomDriverStream_( T_VFSRomDriverStream_ const& ) = delete; + T_VFSRomDriverStream_( T_VFSRomDriverStream_&& ) = delete; + + T_VFSRomDriverStream_( RP_VFSRomDriverStream_& head , + T_ReadWriteMutex& mutex , + OP_MemoryInputStream&& stream ); + ~T_VFSRomDriverStream_( ); + + size_t read( void* data , size_t size ) override; + + void disable( ); +}; + +} + +/*----------------------------------------------------------------------------*/ + +T_VFSRomDriverStream_::T_VFSRomDriverStream_( RP_VFSRomDriverStream_& head , + T_ReadWriteMutex& mutex , + OP_MemoryInputStream&& stream ) + : A_InputStream( stream->position( ) , stream->size( ) ) , + head_( head ) , mutex_( mutex ) , + prev_( nullptr ) , next_( head ) , + actual_( std::move( stream ) ) +{ + head = this; + if ( next_ ) { + next_->prev_ = this; + } +} + +T_VFSRomDriverStream_::~T_VFSRomDriverStream_( ) +{ + T_WriteLock lock( mutex_ ); + disable( ); +} + +size_t T_VFSRomDriverStream_::read( void* data , size_t size ) +{ + T_ReadLock lock( mutex_ ); + if ( actual_ ) { + return actual_->read( data , size ); + } else { + throw X_StreamError( E_StreamError::UNAVAILABLE ); + } +} + +void T_VFSRomDriverStream_::disable( ) +{ + actual_.clear( ); + if ( prev_ == nullptr ) { + head_ = next_; + } else { + prev_->next_ = next_; + } + if ( next_ != nullptr ) { + next_->prev_ = prev_; + } +} + + +/*= T_VFSRomDriverPrivate_ ===================================================*/ + +struct T_VFSRomDriverPrivate_ +{ + uint8_t const* data_; + uint32_t size_; + T_VFSDataIndex index_; + T_ReadWriteMutex mutex_; + T_VFSRomDriverStream_* streams_; + + T_VFSRomDriverPrivate_( + uint8_t const* const data , + uint32_t const size ); +}; + +/*----------------------------------------------------------------------------*/ + +inline T_VFSRomDriverPrivate_::T_VFSRomDriverPrivate_( + uint8_t const* const data , + uint32_t const size ) + : data_( data ) , size_( size ) , + index_( T_VFSRomDriver::C_MAGIC ) , + streams_( nullptr ) +{ + assert( data_ != nullptr ); + assert( size_ > 0 ); +} + + +/*= T_VFSRomDriver ===========================================================*/ + +constexpr uint32_t T_VFSRomDriver::C_MAGIC; + +/*----------------------------------------------------------------------------*/ + +T_VFSRomDriver::T_VFSRomDriver( + uint8_t const* const data , + uint32_t const size ) + : A_PrivateImplementation( new T_VFSRomDriverPrivate_( data , size ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +uint8_t const* T_VFSRomDriver::data( ) const +{ + return p< T_VFSRomDriverPrivate_ >( ).data_; +} + +uint32_t T_VFSRomDriver::size( ) const +{ + return p< T_VFSRomDriverPrivate_ >( ).size_; +} + +/*----------------------------------------------------------------------------*/ + +bool T_VFSRomDriver::init( ) +{ + auto& pi( p< T_VFSRomDriverPrivate_ >( ) ); + T_MemoryInputStream input( pi.data_ , pi.size_ ); + T_BinaryReader reader( input , E_Endian::NATIVE ); + bool rv; + try { + rv = pi.index_.readIndex( reader ); + } catch ( X_StreamError const& ) { + rv = false; + } + if ( !rv ) { + pi.index_.free( ); + } + return rv; +} + +void T_VFSRomDriver::shutdown( ) +{ + auto& pi( p< T_VFSRomDriverPrivate_ >( ) ); + T_WriteLock lock( pi.mutex_ ); + while ( pi.streams_ != nullptr ) { + pi.streams_->disable( ); + } + pi.index_.free( ); +} + +/*----------------------------------------------------------------------------*/ + +E_VFSEntryType T_VFSRomDriver::typeOf( T_VFSPath const& path ) +{ + auto& pi( p< T_VFSRomDriverPrivate_ >( ) ); + T_ReadLock lock( pi.mutex_ ); + return pi.index_.typeOf( path ); +} + +bool T_VFSRomDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index ) +{ + auto& pi( p< T_VFSRomDriverPrivate_ >( ) ); + T_ReadLock lock( pi.mutex_ ); + return pi.index_.list( path , values , index ); +} + +OP_InputStream T_VFSRomDriver::read( T_VFSPath const& path ) +{ + auto& pi( p< T_VFSRomDriverPrivate_ >( ) ); + T_ReadLock lock( pi.mutex_ ); + T_VFSDataIndex::T_FileInfo const* p( pi.index_.getFileInfo( path ) ); + if ( p == nullptr ) { + return OP_InputStream( ); + } + + T_WriteLock wLock( lock.upgrade( ) ); + return NewOwned< T_VFSRomDriverStream_ >( pi.streams_ , pi.mutex_ , + NewOwned< T_MemoryInputStream >( pi.data_ + p->offset , p->size ) ); +} + + +/*= T_VFSDataFileDriverStream_ ===============================================*/ + +namespace lw { + +M_CLASS_POINTERS( VFSDataFileDriverStream_ ); +class T_VFSDataFileDriverStream_ : public A_InputStream +{ + private: + RP_VFSDataFileDriverStream_& head_; + T_Mutex& mutex_; + + RP_VFSDataFileDriverStream_ prev_; + RP_VFSDataFileDriverStream_ next_; + OP_FileInputStream actual_; + + public: + T_VFSDataFileDriverStream_( ) = delete; + T_VFSDataFileDriverStream_( T_VFSDataFileDriverStream_ const& ) = delete; + T_VFSDataFileDriverStream_( T_VFSDataFileDriverStream_&& ) = delete; + + T_VFSDataFileDriverStream_( RP_VFSDataFileDriverStream_& head , T_Mutex& mutex , + OP_FileInputStream&& stream ); + ~T_VFSDataFileDriverStream_( ); + + size_t read( void* data , size_t size ) override; + + void disable( ); +}; + +} + +/*----------------------------------------------------------------------------*/ + +T_VFSDataFileDriverStream_::T_VFSDataFileDriverStream_( + RP_VFSDataFileDriverStream_& head , T_Mutex& mutex , + OP_FileInputStream&& stream ) + : A_InputStream( stream->position( ) , stream->size( ) ) , + head_( head ) , mutex_( mutex ) , + prev_( nullptr ) , next_( head ) , + actual_( std::move( stream ) ) +{ + head = this; + if ( next_ ) { + next_->prev_ = this; + } +} + +T_VFSDataFileDriverStream_::~T_VFSDataFileDriverStream_( ) +{ + T_ScopeLock lock( mutex_ ); + disable( ); +} + +size_t T_VFSDataFileDriverStream_::read( void* data , size_t size ) +{ + T_ScopeLock lock( mutex_ ); + if ( actual_ ) { + return actual_->read( data , size ); + } else { + throw X_StreamError( E_StreamError::UNAVAILABLE ); + } +} + +void T_VFSDataFileDriverStream_::disable( ) +{ + actual_.clear( ); + if ( prev_ == nullptr ) { + head_ = next_; + } else { + prev_->next_ = next_; + } + if ( next_ != nullptr ) { + next_->prev_ = prev_; + } +} + + +/*= T_VFSDataFileDriverPrivate_ ==============================================*/ + +namespace { +struct T_VFSDataFileDriverPrivate_ +{ + T_Mutex mutex_; + OP_File file_; + T_VFSDataIndex index_; + T_VFSDataFileDriverStream_* streams_; + + explicit T_VFSDataFileDriverPrivate_( + OP_File&& file ); +}; +} // namespace + +/*----------------------------------------------------------------------------*/ + +T_VFSDataFileDriverPrivate_::T_VFSDataFileDriverPrivate_( + OP_File&& file ) + : file_( std::move( file ) ) , + index_( T_VFSDataFileDriver::C_MAGIC ) , + streams_( nullptr ) +{ + assert( file_ ); +} + + +/*= T_VFSDataFileDriver ======================================================*/ + +T_VFSDataFileDriver::T_VFSDataFileDriver( OP_File&& file ) + : A_PrivateImplementation( new T_VFSDataFileDriverPrivate_( std::move( file ) ) ) +{ } + +/*----------------------------------------------------------------------------*/ + +T_File const& T_VFSDataFileDriver::dataFile( ) const +{ + return *( p< T_VFSDataFileDriverPrivate_ >( ).file_ ); +} + +/*----------------------------------------------------------------------------*/ + +bool T_VFSDataFileDriver::init( ) +{ + auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) ); + bool rv; + try { + T_FileInputStream input( *pi.file_ , 0 ); + T_BinaryReader reader( input , E_Endian::NATIVE ); + rv = pi.index_.readIndex( reader ); + } catch ( X_StreamError const& ) { + rv = false; + } + if ( !rv ) { + pi.index_.free( ); + } + return rv; +} + +void T_VFSDataFileDriver::shutdown( ) +{ + auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) ); + T_ScopeLock lock( pi.mutex_ ); + while ( pi.streams_ != nullptr ) { + pi.streams_->disable( ); + } + pi.index_.free( ); + pi.file_->close( ); +} + +/*----------------------------------------------------------------------------*/ + +E_VFSEntryType T_VFSDataFileDriver::typeOf( T_VFSPath const& path ) +{ + auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) ); + T_ScopeLock lock( pi.mutex_ ); + return pi.index_.typeOf( path ); +} + +bool T_VFSDataFileDriver::list( T_VFSPath const& path , T_Array< T_VFSPath >& values , T_HashIndex& index ) +{ + auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) ); + T_ScopeLock lock( pi.mutex_ ); + return pi.index_.list( path , values , index ); +} + +OP_InputStream T_VFSDataFileDriver::read( T_VFSPath const& path ) +{ + auto& pi( p< T_VFSDataFileDriverPrivate_ >( ) ); + T_ScopeLock lock( pi.mutex_ ); + T_VFSDataIndex::T_FileInfo const* p( pi.index_.getFileInfo( path ) ); + if ( p == nullptr ) { + return OP_InputStream( ); + } + pi.file_->position( p->offset ); + return NewOwned< T_VFSDataFileDriverStream_ >( pi.streams_ , pi.mutex_ , + NewOwned< T_FileInputStream >( *pi.file_ , 0 , p->size ) ); +} diff --git a/src/files.mk b/src/files.mk new file mode 100644 index 0000000..f91ab6e --- /dev/null +++ b/src/files.mk @@ -0,0 +1,36 @@ +MINLIB_SOURCES = \ + src/lib/Files.cc \ + src/lib/HashIndex.cc \ + src/lib/MemoryStreams.cc \ + src/lib/Pointers.cc \ + src/lib/SRDBinary.cc \ + src/lib/SRDData.cc \ + src/lib/SRDDefinitions.cc \ + src/lib/SRDIO.cc \ + src/lib/SRDParser.cc \ + src/lib/SRDParserConfig.cc \ + src/lib/SRDPreproc.cc \ + src/lib/SRDPPCommands.cc \ + src/lib/SRDText.cc \ + src/lib/Streams.cc \ + src/lib/Strings.cc \ + src/lib/TemplateInstantiation.cc \ + src/lib/Utilities.cc \ + src/lib/VFS.cc \ + src/lib/VFSDrivers.cc +# FIXME: VFSDrivers shouldn't be in the minlib +# (but we need it for srdpp at this point) + +LIB_SOURCES = \ + $(MINLIB_SOURCES) \ + src/lib/Console.cc \ + src/lib/ConsoleLogWriter.cc \ + src/lib/CwdFileLogger.cc \ + src/lib/DynLib.cc \ + src/lib/GameLoop.cc \ + src/lib/LW.cc \ + src/lib/Log.cc \ + src/lib/Messages.cc \ + src/lib/ModInterface.cc \ + src/lib/Mods.cc \ + src/lib/TextFileLogger.cc diff --git a/tests/alloc-pool.cc b/tests/alloc-pool.cc new file mode 100644 index 0000000..d82fa43 --- /dev/null +++ b/tests/alloc-pool.cc @@ -0,0 +1,332 @@ +#include +#include +#include +using namespace lw; + + +class AllocPoolTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( AllocPoolTest ); + CPPUNIT_TEST( testUnused ); + + CPPUNIT_TEST( testAllocPartial ); + CPPUNIT_TEST( testAllocFull ); + CPPUNIT_TEST( testAllocNewList ); + + CPPUNIT_TEST( testFreePartialToFree ); + CPPUNIT_TEST( testFreePartialToPartial ); + CPPUNIT_TEST( testFreeFullToPartial ); + + CPPUNIT_TEST( testFreeLists ); + CPPUNIT_TEST( testFreeFragmented ); + CPPUNIT_TEST( testFreeMaxLists ); + + CPPUNIT_TEST( testReallocation ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void setUp( ) override; + + void testUnused( ); + + void testAllocPartial( ); + void testAllocFull( ); + void testAllocNewList( ); + + void testFreePartialToFree( ); + void testFreePartialToPartial( ); + void testFreeFullToPartial( ); + + void testFreeLists( ); + void testFreeFragmented( ); + void testFreeMaxLists( ); + + void testReallocation( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( AllocPoolTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +class T_Test_ +{ + public: + static size_t counters[ 256 ]; + uint8_t index; + + T_Test_( ) = delete; + + T_Test_( const uint8_t v ) noexcept + : index( v ) + { counters[ index ] ++; } + + T_Test_( T_Test_ const& other ) noexcept + : index( other.index ) + { counters[ index ] ++; } + + T_Test_( T_Test_&& other ) noexcept + : index( other.index ) + { counters[ index ] ++; } + + ~T_Test_( ) + { counters[ index ] --; } + + void* operator new( size_t count ) noexcept; + void operator delete( void* object ) noexcept; + + static size_t total( ) noexcept + { + size_t sum = 0; + for ( auto i = 0 ; i < 255 ; i ++ ) { + sum += counters[ i ]; + } + return sum; + } +}; +M_CLASS_POINTERS( Test_ ); + +size_t T_Test_::counters[ 256 ]; + +static constexpr size_t PerList_ = 15; +static constexpr size_t MaxFreeLists_ = 4; +static constexpr size_t MFLObjects_ = PerList_ * MaxFreeLists_; +using T_Pool_ = T_PoolAllocator< + sizeof( T_Test_ ) , alignof( T_Test_ ) , + PerList_ , MaxFreeLists_ >; + +static T_OwnPtr< T_Pool_ > Pool_; + +void InitPool_( ) +{ + Pool_ = NewOwned< T_Pool_ >( ); +} + +void KillPool_( ) +{ + Pool_.clear( ); +} + +void* T_Test_::operator new( + const size_t count ) noexcept +{ + assert( Pool_ ); + return Pool_->allocate( count ); +} + +void T_Test_::operator delete( + void* object ) noexcept +{ + assert( Pool_ ); + Pool_->free( object ); +} + +} + +#define M_CHECK_LISTS_( T , F , U ) \ + do { \ + CPPUNIT_ASSERT_EQUAL( size_t( T ) , Pool_->countFreeLists( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( F ) , Pool_->countPartialLists( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( U ) , Pool_->countFullLists( ) ); \ + } while ( 0 ) + +#define M_CHECK_USAGE_( T , F , U ) \ + do { \ + size_t _t_ , _f_ , _u_; \ + Pool_->getUsage( _t_ , _f_ , _u_ ); \ + CPPUNIT_ASSERT_EQUAL( size_t( T ) , _t_ ); \ + CPPUNIT_ASSERT_EQUAL( size_t( F ) , _f_ ); \ + CPPUNIT_ASSERT_EQUAL( size_t( U ) , _u_ ); \ + } while ( 0 ) + +#define M_CHECK_COUNT_( N ) \ + CPPUNIT_ASSERT_EQUAL( size_t( N ) , T_Test_::total( ) ) + +void AllocPoolTest::setUp( ) +{ + memset( T_Test_::counters , 0 , sizeof( T_Test_::counters ) ); +} + +/*----------------------------------------------------------------------------*/ + +void AllocPoolTest::testUnused( ) +{ + InitPool_( ); + M_CHECK_LISTS_( 1 , 0 , 0 ); + M_CHECK_USAGE_( PerList_ , PerList_ , 0 ); + KillPool_( ); + M_CHECK_COUNT_( 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void AllocPoolTest::testAllocPartial( ) +{ + InitPool_( ); + { + OP_Test_ test( NewOwned< T_Test_ >( 0 ) ); + M_CHECK_COUNT_( 1 ); + M_CHECK_LISTS_( 0 , 1 , 0 ); + M_CHECK_USAGE_( PerList_ , PerList_ - 1 , 1 ); + } + KillPool_( ); + M_CHECK_COUNT_( 0 ); +} + +void AllocPoolTest::testAllocFull( ) +{ + InitPool_( ); + { + OP_Test_ test[ PerList_ ]; + for ( auto i = 0u ; i < PerList_ ; i ++ ) { + test[ i ] = NewOwned< T_Test_ >( i ); + } + M_CHECK_COUNT_( PerList_ ); + M_CHECK_LISTS_( 0 , 0 , 1 ); + M_CHECK_USAGE_( PerList_ , 0 , PerList_ ); + } + KillPool_( ); + M_CHECK_COUNT_( 0 ); +} + +void AllocPoolTest::testAllocNewList( ) +{ + InitPool_( ); + { + OP_Test_ test[ PerList_ + 1 ]; + for ( auto i = 0u ; i <= PerList_ ; i ++ ) { + test[ i ] = NewOwned< T_Test_ >( i ); + } + M_CHECK_COUNT_( PerList_ + 1 ); + M_CHECK_LISTS_( 0 , 1 , 1 ); + M_CHECK_USAGE_( PerList_ * 2 , PerList_ - 1 , PerList_ + 1 ); + } + KillPool_( ); + M_CHECK_COUNT_( 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void AllocPoolTest::testFreePartialToFree( ) +{ + InitPool_( ); + { + OP_Test_ obj( NewOwned< T_Test_ >( 0 ) ); + obj.clear( ); + M_CHECK_COUNT_( 0 ); + M_CHECK_LISTS_( 1 , 0 , 0 ); + M_CHECK_USAGE_( PerList_ , PerList_ , 0 ); + } + KillPool_( ); +} + +void AllocPoolTest::testFreePartialToPartial( ) +{ + InitPool_( ); + { + OP_Test_ persist( NewOwned< T_Test_ >( 0 ) ); + OP_Test_ test( NewOwned< T_Test_ >( 1 ) ); + test.clear( ); + M_CHECK_COUNT_( 1 ); + M_CHECK_LISTS_( 0 , 1 , 0 ); + M_CHECK_USAGE_( PerList_ , PerList_ - 1 , 1 ); + } + KillPool_( ); +} + +void AllocPoolTest::testFreeFullToPartial( ) +{ + InitPool_( ); + { + OP_Test_ objs[ PerList_ ]; + for ( auto i = 0u ; i < PerList_ ; i ++ ) { + objs[ i ] = NewOwned< T_Test_ >( i ); + } + objs[ PerList_ - 1 ].clear( ); + M_CHECK_COUNT_( PerList_ - 1 ); + M_CHECK_LISTS_( 0 , 1 , 0 ); + M_CHECK_USAGE_( PerList_ , 1 , PerList_ - 1 ); + } + KillPool_( ); +} + +/*----------------------------------------------------------------------------*/ + +void AllocPoolTest::testFreeLists( ) +{ + InitPool_( ); + { + OP_Test_ obj[ MFLObjects_ ]; + for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) { + obj[ i ] = NewOwned< T_Test_ >( i ); + } + for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) { + obj[ i ].clear( ); + } + M_CHECK_COUNT_( 0 ); + M_CHECK_LISTS_( MaxFreeLists_ , 0 , 0 ); + M_CHECK_USAGE_( MFLObjects_ , MFLObjects_ , 0 ); + } + KillPool_( ); +} + +void AllocPoolTest::testFreeFragmented( ) +{ + InitPool_( ); + { + OP_Test_ obj[ MFLObjects_ ]; + for ( auto i = 0u ; i < MFLObjects_ ; i ++ ) { + obj[ i ] = NewOwned< T_Test_ >( i ); + } + for ( auto i = 0u ; i < MaxFreeLists_ ; i ++ ) { + obj[ i * PerList_ ].clear( ); + } + M_CHECK_COUNT_( MFLObjects_ - MaxFreeLists_ ); + M_CHECK_LISTS_( 0 , MaxFreeLists_ , 0 ); + M_CHECK_USAGE_( MFLObjects_ , MaxFreeLists_ , MFLObjects_ - MaxFreeLists_ ); + } + KillPool_( ); +} + +void AllocPoolTest::testFreeMaxLists( ) +{ + InitPool_( ); + { + OP_Test_ obj[ MFLObjects_ + PerList_ ]; + for ( auto i = 0u ; i < MFLObjects_ + PerList_ ; i ++ ) { + obj[ i ] = NewOwned< T_Test_ >( i ); + } + for ( auto i = 0u ; i < MFLObjects_ + PerList_ ; i ++ ) { + obj[ i ].clear( ); + } + M_CHECK_COUNT_( 0 ); + M_CHECK_LISTS_( MaxFreeLists_ , 0 , 0 ); + M_CHECK_USAGE_( MFLObjects_ , MFLObjects_ , 0 ); + } + KillPool_( ); +} + +/*----------------------------------------------------------------------------*/ + +void AllocPoolTest::testReallocation( ) +{ + InitPool_( ); + { + OP_Test_ obj[ PerList_ ]; + T_Test_* ptr[ PerList_ ]; + for ( auto i = 0u ; i < PerList_ ; i ++ ) { + obj[ i ] = NewOwned< T_Test_ >( i ); + ptr[ i ] = obj[ i ].get( ); + } + for ( auto i = 1u ; i < PerList_ ; i += 2 ) { + obj[ i ].clear( ); + } + for ( auto i = 1u ; i < PerList_ ; i += 2 ) { + obj[ i ] = NewOwned< T_Test_ >( i ); + CPPUNIT_ASSERT( obj[ i ].get( ) == ptr[ i ] ); + } + M_CHECK_LISTS_( 0 , 0 , 1 ); + M_CHECK_USAGE_( PerList_ , 0 , PerList_ ); + } + KillPool_( ); +} diff --git a/tests/arrays-auto.cc b/tests/arrays-auto.cc new file mode 100644 index 0000000..641f202 --- /dev/null +++ b/tests/arrays-auto.cc @@ -0,0 +1,920 @@ +#include +#include +using namespace lw; + + +class ArraysAutoTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysAutoTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testStaticOps ); + CPPUNIT_TEST( testStaticToDynamic ); + CPPUNIT_TEST( testRemovalKeepsDynamic ); + CPPUNIT_TEST( testClearKeepsDynamic ); + CPPUNIT_TEST( testFreeClearsStatic ); + CPPUNIT_TEST( testFreeMakesStatic ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testRemoveEnd ); + CPPUNIT_TEST( testRemoveMiddle ); + CPPUNIT_TEST( testRemoveSwapEnd ); + CPPUNIT_TEST( testRemoveSwapMiddle ); + + CPPUNIT_TEST( testInsertEmpty ); + CPPUNIT_TEST( testInsertAfter ); + CPPUNIT_TEST( testInsertBefore ); + + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + + CPPUNIT_TEST( testSort ); + + CPPUNIT_TEST( testAddAllSS2S ); + CPPUNIT_TEST( testAddAllSD2S ); + CPPUNIT_TEST( testAddAllSS2D ); + CPPUNIT_TEST( testAddAllSD2D ); + CPPUNIT_TEST( testAddAllDS2D ); + CPPUNIT_TEST( testAddAllDD2D ); + + CPPUNIT_TEST( testAddAllMoveSS2S ); + CPPUNIT_TEST( testAddAllMoveSD2S ); + CPPUNIT_TEST( testAddAllMoveSS2D ); + CPPUNIT_TEST( testAddAllMoveSD2D ); + CPPUNIT_TEST( testAddAllMoveDS2D ); + CPPUNIT_TEST( testAddAllMoveDD2D ); + + CPPUNIT_TEST( testCopyRangeS2S ); + CPPUNIT_TEST( testCopyRangeD2S ); + CPPUNIT_TEST( testCopyRangeD2D ); + + CPPUNIT_TEST( testMoveRangeS2S ); + CPPUNIT_TEST( testMoveRangeD2S ); + CPPUNIT_TEST( testMoveRangeD2D ); + CPPUNIT_TEST_SUITE_END( ); + + public: + void testEmpty( ); + + void testStaticOps( ); + void testStaticToDynamic( ); + void testRemovalKeepsDynamic( ); + void testClearKeepsDynamic( ); + void testFreeClearsStatic( ); + void testFreeMakesStatic( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); + + void testRemoveEnd( ); + void testRemoveMiddle( ); + void testRemoveSwapEnd( ); + void testRemoveSwapMiddle( ); + + void testInsertEmpty( ); + void testInsertAfter( ); + void testInsertBefore( ); + + void testIndexOf( ); + void testIndexOfMissing( ); + void testContains( ); + void testContainsMissing( ); + + void testSort( ); + + void testAddAllSS2S( ); + void testAddAllSD2S( ); + void testAddAllSS2D( ); + void testAddAllSD2D( ); + void testAddAllDS2D( ); + void testAddAllDD2D( ); + + void testAddAllMoveSS2S( ); + void testAddAllMoveSD2S( ); + void testAddAllMoveSS2D( ); + void testAddAllMoveSD2D( ); + void testAddAllMoveDS2D( ); + void testAddAllMoveDD2D( ); + + void testCopyRangeS2S( ); + void testCopyRangeD2S( ); + void testCopyRangeD2D( ); + + void testMoveRangeS2S( ); + void testMoveRangeD2S( ); + void testMoveRangeD2D( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysAutoTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +static constexpr uint32_t StaticSize = 4; +static constexpr uint32_t DynamicGrowth = 8; + +using T_Test_ = T_AutoArray< uint32_t , StaticSize , DynamicGrowth >; +using T_Check_ = T_StaticArray< uint32_t , DynamicGrowth >; + +void GenerateTest_( + const bool dynamic , + T_Test_& test , + T_Check_& check ) +{ + for ( auto i = 1u ; i < ( dynamic ? DynamicGrowth : StaticSize ) ; i ++ ) { + const uint32_t v( std::rand( ) ); + test.add( v ); + check.add( v ); + } +} + +void CheckResult_( + const bool dynamic , + T_Test_ const& test , + T_Check_ const& check ) +{ + CPPUNIT_ASSERT_EQUAL( !dynamic , test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( check.size( ) , test.size( ) ); + for ( auto i = 0u ; i < test.size( ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( check[ i ] , test[ i ] ); + } +} + +} // namespace + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testEmpty( ) +{ + T_Test_ test; + CPPUNIT_ASSERT_EQUAL( 0u , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth , test.growth( ) ); + CPPUNIT_ASSERT( test.isStatic( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testStaticOps( ) +{ + T_Test_ test; + + test << 1 << 2 << 3; + CPPUNIT_ASSERT( test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , test[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( 2u , test[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( 3u , test[ 2 ] ); + + test.insert( 0 , 0 ); + CPPUNIT_ASSERT( test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , test[ 0 ] ); + + test.remove( 3 ); + CPPUNIT_ASSERT( test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , test.size( ) ); +} + +void ArraysAutoTest::testStaticToDynamic( ) +{ + T_Test_ test; + for ( auto i = 0u ; i <= StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test.add( i ) ); + } + CPPUNIT_ASSERT( !test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize + 1 , test.size( ) ); + for ( auto i = 0u ; i <= StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test[ i ] ); + } +} + +void ArraysAutoTest::testRemovalKeepsDynamic( ) +{ + T_Test_ test; + for ( auto i = 0u ; i <= StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test.add( i ) ); + } + test.remove( StaticSize ); + CPPUNIT_ASSERT( !test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , test.size( ) ); +} + +void ArraysAutoTest::testClearKeepsDynamic( ) +{ + T_Test_ test; + for ( auto i = 0u ; i <= StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test.add( i ) ); + } + test.clear( ); + CPPUNIT_ASSERT( !test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , test.size( ) ); +} + +void ArraysAutoTest::testFreeClearsStatic( ) +{ + T_Test_ test; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test.add( i ) ); + } + test.free( ); + CPPUNIT_ASSERT( test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , test.size( ) ); +} + +void ArraysAutoTest::testFreeMakesStatic( ) +{ + T_Test_ test; + for ( auto i = 0u ; i <= StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , test.add( i ) ); + } + test.free( ); + CPPUNIT_ASSERT( test.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , test.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , test.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testCopyCons( ) +{ + { + T_Test_ init; + T_Check_ check; + GenerateTest_( false , init , check ); + T_Test_ test( init ); + CheckResult_( false , init , check ); + CheckResult_( false , test , check ); + } + { + T_Test_ init; + T_Check_ check; + GenerateTest_( true , init , check ); + T_Test_ test( init ); + CheckResult_( true , init , check ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testCopyAss( ) +{ + { + T_Test_ init , test; + T_Check_ check; + GenerateTest_( false , init , check ); + test = init; + CheckResult_( false , init , check ); + CheckResult_( false , test , check ); + } + { + T_Test_ init , test; + T_Check_ check; + GenerateTest_( true , init , check ); + test = init; + CheckResult_( true , init , check ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testMoveCons( ) +{ + { + T_Test_ init; + T_Check_ check; + GenerateTest_( false , init , check ); + T_Test_ test( std::move( init ) ); + CPPUNIT_ASSERT( init.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CheckResult_( false , test , check ); + } + { + T_Test_ init; + T_Check_ check; + GenerateTest_( true , init , check ); + T_Test_ test( std::move( init ) ); + CPPUNIT_ASSERT( !init.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testMoveAss( ) +{ + { + T_Test_ init , test; + T_Check_ check; + GenerateTest_( false , init , check ); + test = std::move( init ); + CPPUNIT_ASSERT( init.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CheckResult_( false , test , check ); + } + { + T_Test_ init , test; + T_Check_ check; + GenerateTest_( true , init , check ); + test = std::move( init ); + CPPUNIT_ASSERT( !init.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testSwap( ) +{ + { + T_Test_ test , other; + T_Check_ check; + GenerateTest_( false , other , check ); + test << 1 << 2; + swap( test , other ); + CPPUNIT_ASSERT_EQUAL( 2u , other.size( ) ); + CheckResult_( false , test , check ); + } + { + T_Test_ test , other; + T_Check_ check; + GenerateTest_( true , other , check ); + test << 1 << 2; + swap( test , other ); + CPPUNIT_ASSERT_EQUAL( 2u , other.size( ) ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testRemoveEnd( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.remove( test.size( ) - 1 ); + check.remove( check.size( ) - 1 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.remove( test.size( ) - 1 ); + check.remove( check.size( ) - 1 ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testRemoveMiddle( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.remove( 1 ); + check.remove( 1 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.remove( 1 ); + check.remove( 1 ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testRemoveSwapEnd( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.removeSwap( test.size( ) - 1 ); + check.removeSwap( check.size( ) - 1 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.removeSwap( test.size( ) - 1 ); + check.removeSwap( check.size( ) - 1 ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testRemoveSwapMiddle( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.removeSwap( 1 ); + check.removeSwap( 1 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.removeSwap( 1 ); + check.removeSwap( 1 ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testInsertEmpty( ) +{ + { + T_Test_ test; + T_Check_ check; + test.insert( 0 , 0 ); + check.insert( 0 , 0 ); + GenerateTest_( false , test , check ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + test.insert( 0 , 0 ); + check.insert( 0 , 0 ); + GenerateTest_( true , test , check ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testInsertAfter( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.insert( test.size( ) , 0 ); + check.insert( check.size( ) , 0 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.insert( test.size( ) , 0 ); + check.insert( check.size( ) , 0 ); + CheckResult_( true , test , check ); + } +} + +void ArraysAutoTest::testInsertBefore( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.insert( 0 , 0 ); + check.insert( 0 , 0 ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.insert( 0 , 0 ); + check.insert( 0 , 0 ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testIndexOf( ) +{ + { + T_Test_ test; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + test.add( i * 2 + 1 ); + } + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( int32_t( i ) , test.indexOf( i * 2 + 1 ) ); + } + } + { + T_Test_ test; + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + test.add( i * 2 + 1 ); + } + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( int32_t( i ) , test.indexOf( i * 2 + 1 ) ); + } + } +} + +void ArraysAutoTest::testIndexOfMissing( ) +{ + { + T_Test_ test; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + test.add( i * 2 + 1 ); + } + CPPUNIT_ASSERT_EQUAL( -1 , test.indexOf( 2 ) ); + } + { + T_Test_ test; + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + test.add( i * 2 + 1 ); + } + CPPUNIT_ASSERT_EQUAL( -1 , test.indexOf( 2 ) ); + } +} + +void ArraysAutoTest::testContains( ) +{ + { + T_Test_ test; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + test.add( i * 2 + 1 ); + } + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT( test.contains( i * 2 + 1 ) ); + } + } + { + T_Test_ test; + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + test.add( i * 2 + 1 ); + } + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + CPPUNIT_ASSERT( test.contains( i * 2 + 1 ) ); + } + } +} + +void ArraysAutoTest::testContainsMissing( ) +{ + { + T_Test_ test; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + test.add( i * 2 + 1 ); + } + CPPUNIT_ASSERT( !test.contains( 2 ) ); + } + { + T_Test_ test; + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + test.add( i * 2 + 1 ); + } + CPPUNIT_ASSERT( !test.contains( 2 ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testSort( ) +{ + { + T_Test_ test; + T_Check_ check; + GenerateTest_( false , test , check ); + test.sort( ); + check.sort( ); + CheckResult_( false , test , check ); + } + { + T_Test_ test; + T_Check_ check; + GenerateTest_( true , test , check ); + test.sort( ); + check.sort( ); + CheckResult_( true , test , check ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testAddAllSS2S( ) +{ + T_Test_ t1 , t2; + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + t1.addAll( t2 ); + CPPUNIT_ASSERT( t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +void ArraysAutoTest::testAddAllSD2S( ) +{ + T_Test_ t1 , t2; + t2.ensureCapacity( DynamicGrowth * 2 ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + + t1.addAll( t2 ); + CPPUNIT_ASSERT( t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +void ArraysAutoTest::testAddAllSS2D( ) +{ + T_Test_ t1 , t2; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize ); + } + t1.addAll( t2 ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize * 2 , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +void ArraysAutoTest::testAddAllSD2D( ) +{ + T_Test_ t1 , t2; + t2.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize ); + } + t1.addAll( t2 ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize * 2 , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +void ArraysAutoTest::testAddAllDS2D( ) +{ + T_Test_ t1 , t2; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + t1.addAll( t2 ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +void ArraysAutoTest::testAddAllDD2D( ) +{ + T_Test_ t1 , t2; + t1.ensureCapacity( DynamicGrowth ); + t2.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + t2.add( i + DynamicGrowth ); + } + t1.addAll( t2 ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth * 2 , t1.size( ) ); + for ( auto i = 0u ; i < DynamicGrowth * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testAddAllMoveSS2S( ) +{ + T_Test_ t1 , t2; + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( t2.isStatic( ) ); +} + +void ArraysAutoTest::testAddAllMoveSD2S( ) +{ + T_Test_ t1 , t2; + t2.ensureCapacity( DynamicGrowth * 2 ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( t2.isStatic( ) ); +} + +void ArraysAutoTest::testAddAllMoveSS2D( ) +{ + T_Test_ t1 , t2; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize ); + } + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize * 2 , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( t2.isStatic( ) ); +} + +void ArraysAutoTest::testAddAllMoveSD2D( ) +{ + T_Test_ t1 , t2; + t2.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize ); + } + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize * 2 , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( t2.isStatic( ) ); +} + +void ArraysAutoTest::testAddAllMoveDS2D( ) +{ + T_Test_ t1 , t2; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + t1.add( i ); + t2.add( i + StaticSize / 2 ); + } + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t1.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( t2.isStatic( ) ); +} + +void ArraysAutoTest::testAddAllMoveDD2D( ) +{ + T_Test_ t1 , t2; + t1.ensureCapacity( DynamicGrowth ); + t2.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + t2.add( i + DynamicGrowth ); + } + t1.addAll( std::move( t2 ) ); + CPPUNIT_ASSERT( !t1.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( DynamicGrowth * 2 , t1.size( ) ); + for ( auto i = 0u ; i < DynamicGrowth * 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , t1[ i ] ); + } + CPPUNIT_ASSERT( t2.size( ) == 0 ); + CPPUNIT_ASSERT( !t2.isStatic( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testCopyRangeS2S( ) +{ + T_Test_ t1; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.copyRange( StaticSize / 4 , StaticSize / 4 + StaticSize / 2 - 1 ) ); + CPPUNIT_ASSERT( t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize / 2 , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i + StaticSize / 4 ] , t2[ i ] ); + } +} + +void ArraysAutoTest::testCopyRangeD2S( ) +{ + T_Test_ t1; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.copyRange( 0 , StaticSize - 1 ) ); + CPPUNIT_ASSERT( t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i ] , t2[ i ] ); + } +} + +void ArraysAutoTest::testCopyRangeD2D( ) +{ + T_Test_ t1; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.copyRange( 0 , StaticSize ) ); + CPPUNIT_ASSERT( !t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize + 1 , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize + 1 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i ] , t2[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysAutoTest::testMoveRangeS2S( ) +{ + T_Test_ t1; + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.moveRange( StaticSize / 4 , StaticSize / 4 + StaticSize / 2 - 1 ) ); + CPPUNIT_ASSERT( t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize / 2 , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize / 2 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i + StaticSize / 4 ] , t2[ i ] ); + } +} + +void ArraysAutoTest::testMoveRangeD2S( ) +{ + T_Test_ t1; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.moveRange( 0 , StaticSize - 1 ) ); + CPPUNIT_ASSERT( t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i ] , t2[ i ] ); + } +} + +void ArraysAutoTest::testMoveRangeD2D( ) +{ + T_Test_ t1; + t1.ensureCapacity( DynamicGrowth ); + for ( auto i = 0u ; i < DynamicGrowth ; i ++ ) { + t1.add( i ); + } + + T_Test_ t2( t1.moveRange( 0 , StaticSize ) ); + CPPUNIT_ASSERT( !t2.isStatic( ) ); + CPPUNIT_ASSERT_EQUAL( StaticSize + 1 , t2.size( ) ); + for ( auto i = 0u ; i < StaticSize + 1 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( t1[ i ] , t2[ i ] ); + } +} + diff --git a/tests/arrays-basic.cc b/tests/arrays-basic.cc new file mode 100644 index 0000000..a9a586c --- /dev/null +++ b/tests/arrays-basic.cc @@ -0,0 +1,506 @@ +#include +#include +using namespace lw; + + +class ArraysBasicTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysBasicTest ); + CPPUNIT_TEST( testInitialValues ); + + CPPUNIT_TEST( testAppendOne ); + CPPUNIT_TEST( testAppendTwo ); + CPPUNIT_TEST( testAppendExpand ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testEnsureFactor ); + CPPUNIT_TEST( testEnsureOther ); + CPPUNIT_TEST( testEnsureKeepData ); + + CPPUNIT_TEST( testRemoveEnd ); + CPPUNIT_TEST( testRemoveMiddle ); + CPPUNIT_TEST( testRemoveSwapEnd ); + CPPUNIT_TEST( testRemoveSwapMiddle ); + + CPPUNIT_TEST( testAddAllEmpty ); + CPPUNIT_TEST( testAddAll ); + + CPPUNIT_TEST( testInsertEmpty ); + CPPUNIT_TEST( testInsertAfter ); + CPPUNIT_TEST( testInsertBefore ); + + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + + CPPUNIT_TEST( testSort ); + CPPUNIT_TEST( testSortComparator ); + CPPUNIT_TEST( testSortPartial ); + + CPPUNIT_TEST( testRangeOutside ); + CPPUNIT_TEST( testRangeLarger ); + CPPUNIT_TEST( testRangeSub ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInitialValues( ); + + void testAppendOne( ); + void testAppendTwo( ); + void testAppendExpand( ); + + void testClear( ); + void testFree( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); + + void testEnsureFactor( ); + void testEnsureOther( ); + void testEnsureKeepData( ); + + void testRemoveEnd( ); + void testRemoveMiddle( ); + void testRemoveSwapEnd( ); + void testRemoveSwapMiddle( ); + + void testAddAllEmpty( ); + void testAddAll( ); + + void testInsertEmpty( ); + void testInsertAfter( ); + void testInsertBefore( ); + + void testIndexOf( ); + void testIndexOfMissing( ); + void testContains( ); + void testContainsMissing( ); + + void testSort( ); + void testSortComparator( ); + void testSortPartial( ); + + void testRangeOutside( ); + void testRangeLarger( ); + void testRangeSub( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysBasicTest ); + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testInitialValues( ) +{ + T_Array< uint32_t > array; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4096 / sizeof( uint32_t ) ) , array.growth( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testAppendOne( ) +{ + T_Array< uint32_t > array; + array.add( 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); +} + +void ArraysBasicTest::testAppendTwo( ) +{ + T_Array< uint32_t > array; + array << 123 << 456; + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +void ArraysBasicTest::testAppendExpand( ) +{ + T_Array< uint32_t > array; + const uint32_t n = array.growth( ) * 3 / 2; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + array.add( i ); + } + + CPPUNIT_ASSERT_EQUAL( n , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2 * array.growth( ) , array.capacity( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint32_t( i ) , array[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testClear( ) +{ + T_Array< uint32_t > array; + const uint32_t n = array.growth( ) * 3 / 2; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + array.add( i ); + } + array.clear( ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2 * array.growth( ) , array.capacity( ) ); +} + +void ArraysBasicTest::testFree( ) +{ + T_Array< uint32_t > array; + const uint32_t n = array.growth( ) * 3 / 2; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + array.add( i ); + } + array.free( ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.capacity( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testCopyCons( ) +{ + T_Array< uint32_t > array; + array.ensureCapacity( array.growth( ) * 2 ); + array.add( 1 ); + + T_Array< uint32_t > copy( array ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , copy.growth( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy[ 0 ] ); + + copy.add( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); +} + +void ArraysBasicTest::testCopyAss( ) +{ + T_Array< uint32_t > array; + array.ensureCapacity( array.growth( ) * 2 ); + array.add( 1 ); + + T_Array< uint32_t > copy; + copy = array; + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , copy.growth( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy[ 0 ] ); + + copy.add( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testMoveCons( ) +{ + T_Array< uint32_t > array( std::move( + T_Array< uint32_t >( 128 ).ensureCapacity( 256 ) + << 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , array.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 256 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 0 ] ); +} + +void ArraysBasicTest::testMoveAss( ) +{ + T_Array< uint32_t > array; + array = std::move( T_Array< uint32_t > ( 128 ).ensureCapacity( 256 ) + << 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , array.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 256 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testSwap( ) +{ + T_Array< uint32_t > a1; + T_Array< uint32_t > a2; + a1 << 1 << 2 << 3; + a2 << 2 << 4 << 6 << 8; + swap( a1 , a2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , a1.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , a2.size( ) ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT( a1[ i ] == a2[ i ] * 2 ); + } + CPPUNIT_ASSERT( a1[ 3 ] == 8 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testEnsureFactor( ) +{ + T_Array< uint32_t > array; + array.ensureCapacity( array.growth( ) * 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2 * array.growth( ) , array.capacity( ) ); +} + +void ArraysBasicTest::testEnsureOther( ) +{ + T_Array< uint32_t > array; + array.ensureCapacity( array.growth( ) * 3 / 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2 * array.growth( ) , array.capacity( ) ); +} + +void ArraysBasicTest::testEnsureKeepData( ) +{ + T_Array< uint32_t > array; + array.add( 123 ); + array.ensureCapacity( array.growth( ) * 2 ); + CPPUNIT_ASSERT_EQUAL( 2 * array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testRemoveEnd( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + array.remove( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +void ArraysBasicTest::testRemoveMiddle( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + array.remove( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 1 ] ); +} + +void ArraysBasicTest::testRemoveSwapEnd( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + array.removeSwap( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +void ArraysBasicTest::testRemoveSwapMiddle( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + array.removeSwap( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testAddAllEmpty( ) +{ + T_Array< uint32_t > empty; + T_Array< uint32_t > array; + array << 123 << 456 << 789; + array.addAll( empty ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +void ArraysBasicTest::testAddAll( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + + T_Array< uint32_t > other; + other << 627 << 951 << 843; + array.addAll( other ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 6 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( other[ i ] , array[ i + 3 ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testInsertEmpty( ) +{ + T_Array< uint32_t > array; + array.insert( 0 , 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); +} + +void ArraysBasicTest::testInsertAfter( ) +{ + T_Array< uint32_t > array; + array << 123 << 456; + array.insert( 2 , 789 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +void ArraysBasicTest::testInsertBefore( ) +{ + T_Array< uint32_t > array; + array << 456 << 789; + array.insert( 0 , 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testIndexOf( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , array.indexOf( 456 ) ); +} + +void ArraysBasicTest::testIndexOfMissing( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , array.indexOf( 101112 ) ); +} + +void ArraysBasicTest::testContains( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT( array.contains( 456 ) ); +} + +void ArraysBasicTest::testContainsMissing( ) +{ + T_Array< uint32_t > array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT( !array.contains( 101112 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testSort( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + array.sort( ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] >= array[ i - 1 ] ); + } +} + +void ArraysBasicTest::testSortComparator( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + array.sort( []( uint32_t const& a , uint32_t const& b ) { + return T_Comparator< uint32_t >::compare( b , a ); + } ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] <= array[ i - 1 ] ); + } +} + +void ArraysBasicTest::testSortPartial( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + T_Array< uint32_t > copy( array ); + array.sort( 1000 , 1000 ); + + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ] , array[ i ] ); + } + for ( uint32_t i = 1001 ; i < 2000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] >= array[ i - 1 ] ); + } + for ( uint32_t i = 2000 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ] , array[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysBasicTest::testRangeOutside( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + T_Array< uint32_t > range( array.copyRange( 7000 , 1000 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , range.size( ) ); +} + +void ArraysBasicTest::testRangeLarger( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + T_Array< uint32_t > range( array.copyRange( 1000 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4000 ) , range.size( ) ); + for ( uint32_t i = 0 ; i < 4000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ] , range[ i ] ); + } +} + +void ArraysBasicTest::testRangeSub( ) +{ + T_Array< uint32_t > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + T_Array< uint32_t > range( array.copyRange( 1000 , 1999 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1000 ) , range.size( ) ); + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ] , range[ i ] ); + } +} diff --git a/tests/arrays-multi.cc b/tests/arrays-multi.cc new file mode 100644 index 0000000..a932b66 --- /dev/null +++ b/tests/arrays-multi.cc @@ -0,0 +1,867 @@ +#include +#include +using namespace lw; + + +class ArraysMultiTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysMultiTest ); + CPPUNIT_TEST( testInitialSize ); + + CPPUNIT_TEST( testNext ); + CPPUNIT_TEST( testNextMultiple ); + + CPPUNIT_TEST( testAddToEmpty ); + CPPUNIT_TEST( testAdd ); + CPPUNIT_TEST( testAddNewToEmpty ); + CPPUNIT_TEST( testAddNew ); + CPPUNIT_TEST( testNextAfterAdd ); + + CPPUNIT_TEST( testCopyFromToEmpty ); + CPPUNIT_TEST( testCopyFrom ); + CPPUNIT_TEST( testCopyFromEmpty ); + + CPPUNIT_TEST( testGetRead ); + CPPUNIT_TEST( testGetWrite ); + CPPUNIT_TEST( testDirectRead ); + CPPUNIT_TEST( testDirectWrite ); + + CPPUNIT_TEST( testContainsYes ); + CPPUNIT_TEST( testContainsNo ); + + CPPUNIT_TEST( testCopyFromSelf ); + CPPUNIT_TEST( testCopyFromSelfEmptySrc ); + CPPUNIT_TEST( testCopyFromSelfEmptyDst ); + CPPUNIT_TEST( testCopyFromOther ); + CPPUNIT_TEST( testCopyFromOtherEmptySrc ); + CPPUNIT_TEST( testCopyFromOtherEmptyDst ); + + CPPUNIT_TEST( testCopyUniqueFromSelfNoDups ); + CPPUNIT_TEST( testCopyUniqueFromSelfDupsInSource ); + CPPUNIT_TEST( testCopyUniqueFromSelfDups ); + CPPUNIT_TEST( testCopyUniqueFromSelfEmptySrc ); + CPPUNIT_TEST( testCopyUniqueFromSelfEmptyDst ); + + CPPUNIT_TEST( testCopyUniqueFromOtherNoDups ); + CPPUNIT_TEST( testCopyUniqueFromOtherDupsInSource ); + CPPUNIT_TEST( testCopyUniqueFromOtherDups ); + CPPUNIT_TEST( testCopyUniqueFromOtherEmptySrc ); + CPPUNIT_TEST( testCopyUniqueFromOtherEmptyDst ); + + CPPUNIT_TEST( testSort ); + + CPPUNIT_TEST( testFree ); + CPPUNIT_TEST( testClear ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInitialSize( ); + + void testNext( ); + void testNextMultiple( ); + + void testAddToEmpty( ); + void testAdd( ); + void testAddNewToEmpty( ); + void testAddNew( ); + void testNextAfterAdd( ); + + void testCopyFromToEmpty( ); + void testCopyFrom( ); + void testCopyFromEmpty( ); + + void testGetRead( ); + void testGetWrite( ); + void testDirectRead( ); + void testDirectWrite( ); + + void testContainsYes( ); + void testContainsNo( ); + + void testCopyFromSelf( ); + void testCopyFromSelfEmptySrc( ); + void testCopyFromSelfEmptyDst( ); + void testCopyFromOther( ); + void testCopyFromOtherEmptySrc( ); + void testCopyFromOtherEmptyDst( ); + + void testCopyUniqueFromSelfNoDups( ); + void testCopyUniqueFromSelfDupsInSource( ); + void testCopyUniqueFromSelfDups( ); + void testCopyUniqueFromSelfEmptySrc( ); + void testCopyUniqueFromSelfEmptyDst( ); + + void testCopyUniqueFromOtherNoDups( ); + void testCopyUniqueFromOtherDupsInSource( ); + void testCopyUniqueFromOtherDups( ); + void testCopyUniqueFromOtherEmptySrc( ); + void testCopyUniqueFromOtherEmptyDst( ); + + void testSort( ); + + void testFree( ); + void testClear( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysMultiTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +struct T_Obj +{ + uint32_t x; + uint32_t y; + + T_Obj( ) = delete; + T_Obj( uint32_t x , uint32_t y ) : x(x) , y(y) {} + T_Obj( T_Obj const& ) = delete; + T_Obj( T_Obj&& ) = default; +}; + +using T_Test = T_MultiArray< uint32_t >; +using T_ObjTest = T_MultiArray< T_Obj >; + +}; + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testInitialSize( ) +{ + T_Test empty; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , empty.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testNext( ) +{ + T_Test test; + test.next( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testNextMultiple( ) +{ + T_Test test; + test.next( ); + test.next( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testAddToEmpty( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testAdd( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.add( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testAddNewToEmpty( ) +{ + T_ObjTest test; + test.next( ); + test.addNew( 1 , 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testAddNew( ) +{ + T_ObjTest test; + test.next( ); + test.addNew( 1 , 2 ); + test.addNew( 3 , 4 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testNextAfterAdd( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 0 ); + test.add( 0 ); + test.next( ); + test.add( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.firstOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.firstOf( 2 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testCopyFromToEmpty( ) +{ + T_Test::T_Data values; + values << 1 << 2 << 3; + + T_Test test; + test.next( ); + test.copyFrom( values ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testCopyFrom( ) +{ + T_Test::T_Data values; + values << 1 << 2 << 3; + + T_Test test; + test.next( ); + test.add( 0 ); + test.copyFrom( values ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +void ArraysMultiTest::testCopyFromEmpty( ) +{ + T_Test::T_Data values; + T_Test test; + test.next( ); + test.copyFrom( values ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.firstOf( 0 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testGetRead( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 1 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 1 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); +} + +void ArraysMultiTest::testGetWrite( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + test.get( 1 , 0 ) = 12; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 12 ) , test.get( 1 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 1 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); +} + +void ArraysMultiTest::testDirectRead( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + + for ( uint32_t i = 0 ; i < test.sizeOf( 1 ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( test.get( 1 , i ) , test[ test.firstOf( 1 ) + i ] ); + } +} + +void ArraysMultiTest::testDirectWrite( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + test[ 1 ] = 12; + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 12 ) , test.get( 1 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 1 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testContainsYes( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + CPPUNIT_ASSERT( test.contains( 1 , 1 ) ); + CPPUNIT_ASSERT( test.contains( 1 , 2 ) ); +} + +void ArraysMultiTest::testContainsNo( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + CPPUNIT_ASSERT( !test.contains( 1 , 0 ) ); + CPPUNIT_ASSERT( !test.contains( 1 , 3 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testCopyFromSelf( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + + test.copyFrom( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 2 ) ); +} + +void ArraysMultiTest::testCopyFromSelfEmptySrc( ) +{ + T_Test test; + test.next( ); + test.next( ); + test.add( 3 ); + + test.copyFrom( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 1 ) ); +} + +void ArraysMultiTest::testCopyFromSelfEmptyDst( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + + test.copyFrom( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 1 ) ); +} + +void ArraysMultiTest::testCopyFromOther( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 1 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + test.add( 4 ); + + test.copyFrom( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 2 ) ); +} + +void ArraysMultiTest::testCopyFromOtherEmptySrc( ) +{ + T_Test src; + src.next( ); + src.next( ); + src.add( 1 ); + + T_Test test; + test.next( ); + test.add( 4 ); + + test.copyFrom( src , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , test.get( 0 , 0 ) ); +} + +void ArraysMultiTest::testCopyFromOtherEmptyDst( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 1 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + + test.copyFrom( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testCopyUniqueFromSelfNoDups( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + + test.copyUnique( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 2 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromSelfDupsInSource( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + + test.copyUnique( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.get( 2 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 3 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromSelfDups( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.add( 2 ); + test.next( ); + test.add( 2 ); + + test.copyUnique( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 1 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromSelfEmptySrc( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.next( ); + test.add( 2 ); + + test.copyUnique( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 2 , 0 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromSelfEmptyDst( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.next( ); + + test.copyUnique( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 2 , 0 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testCopyUniqueFromOtherNoDups( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 1 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + test.add( 0 ); + + test.copyUnique( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 2 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromOtherDupsInSource( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 2 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + test.add( 0 ); + + test.copyUnique( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 2 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromOtherDups( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 1 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + test.add( 1 ); + + test.copyUnique( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 1 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromOtherEmptySrc( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + test.add( 1 ); + + test.copyUnique( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 0 ) ); +} + +void ArraysMultiTest::testCopyUniqueFromOtherEmptyDst( ) +{ + T_Test src; + src.next( ); + src.add( 0 ); + src.next( ); + src.add( 1 ); + src.add( 2 ); + src.next( ); + src.add( 3 ); + + T_Test test; + test.next( ); + + test.copyUnique( src , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test.get( 0 , 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testSort( ) +{ + T_Test main; + T_Test::T_Data values[ 3 ]; + for ( uint32_t j = 0 ; j < 3 ; j ++ ) { + main.next( ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + const uint32_t v( std::rand( ) ); + values[ j ] << v; + main.add( v ); + } + } + main.sort( 1 ); + + for ( uint32_t j = 0 ; j < 2 ; j ++ ) { + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( values[ j * 2 ][ i ] , main.get( j * 2 , i ) ); + } + } + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( main.get( 1 , i ) >= main.get( 1 , i - 1 ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testFree( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + test.free( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.size( ) ); +} + +void ArraysMultiTest::testClear( ) +{ + T_Test test; + test.next( ); + test.add( 0 ); + test.next( ); + test.add( 1 ); + test.add( 2 ); + test.next( ); + test.add( 3 ); + test.clear( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysMultiTest::testCopyCons( ) +{ + T_Test src; + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + src.next( ); + const uint32_t n( std::rand( ) % 64 + 1 ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + src.add( std::rand( ) ); + } + } + + T_Test test( src ); + const uint32_t msz( src.size( ) ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); + for ( uint32_t j = 0 ; j < msz ; j ++ ) { + CPPUNIT_ASSERT_EQUAL( src.sizeOf( j ) , test.sizeOf( j ) ); + CPPUNIT_ASSERT_EQUAL( src.firstOf( j ) , test.firstOf( j ) ); + const uint32_t sz( test.sizeOf( j ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( src.get( j , i ) , test.get( j , i ) ); + } + } + src.clear( ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); +} + +void ArraysMultiTest::testCopyAss( ) +{ + T_Test src; + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + src.next( ); + const uint32_t n( std::rand( ) % 64 + 1 ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + src.add( std::rand( ) ); + } + } + + T_Test test; + test = src; + const uint32_t msz( src.size( ) ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); + for ( uint32_t j = 0 ; j < msz ; j ++ ) { + CPPUNIT_ASSERT_EQUAL( src.sizeOf( j ) , test.sizeOf( j ) ); + CPPUNIT_ASSERT_EQUAL( src.firstOf( j ) , test.firstOf( j ) ); + const uint32_t sz( test.sizeOf( j ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( src.get( j , i ) , test.get( j , i ) ); + } + } + src.clear( ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); +} + +void ArraysMultiTest::testMoveCons( ) +{ + T_Array< uint32_t > sizes; + T_Test::T_Data data; + T_Test src; + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + src.next( ); + const uint32_t n( std::rand( ) % 64 + 1 ); + sizes << n; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + const uint32_t v( std::rand( ) ); + src.add( v ); + data.add( v ); + } + } + + const uint32_t msz( src.size( ) ); + T_Test test( std::move( src ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , src.size( ) ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); + for ( uint32_t j = 0 , idx = 0 ; j < msz ; j ++ ) { + CPPUNIT_ASSERT_EQUAL( sizes[ j ] , test.sizeOf( j ) ); + CPPUNIT_ASSERT_EQUAL( idx , test.firstOf( j ) ); + const uint32_t sz( test.sizeOf( j ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ , idx ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ idx ] , test.get( j , i ) ); + } + } +} + +void ArraysMultiTest::testMoveAss( ) +{ + T_Array< uint32_t > sizes; + T_Test::T_Data data; + T_Test src; + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + src.next( ); + const uint32_t n( std::rand( ) % 64 + 1 ); + sizes << n; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + const uint32_t v( std::rand( ) ); + src.add( v ); + data.add( v ); + } + } + + const uint32_t msz( src.size( ) ); + T_Test test; + test = std::move( src ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , src.size( ) ); + CPPUNIT_ASSERT_EQUAL( msz , test.size( ) ); + for ( uint32_t j = 0 , idx = 0 ; j < msz ; j ++ ) { + CPPUNIT_ASSERT_EQUAL( sizes[ j ] , test.sizeOf( j ) ); + CPPUNIT_ASSERT_EQUAL( idx , test.firstOf( j ) ); + const uint32_t sz( test.sizeOf( j ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ , idx ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ idx ] , test.get( j , i ) ); + } + } +} + +void ArraysMultiTest::testSwap( ) +{ + T_Test test1 , test2; + test1.next( ); + test1.add( 1 ); + test1.add( 2 ); + test1.next( ); + test1.add( 3 ); + + test2.next( ); + test2.add( 4 ); + + swap( test1 , test2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test1.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test1.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , test1.get( 0 , 0 ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test2.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test2.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test2.get( 0 , 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , test2.get( 0 , 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , test2.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , test2.get( 1 , 0 ) ); +} diff --git a/tests/arrays-objects.cc b/tests/arrays-objects.cc new file mode 100644 index 0000000..7910921 --- /dev/null +++ b/tests/arrays-objects.cc @@ -0,0 +1,875 @@ +#include +#include +using namespace lw; + + +class ArraysObjectsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysObjectsTest ); + CPPUNIT_TEST( testDefaults ); + + CPPUNIT_TEST( testAppendCopy ); + CPPUNIT_TEST( testAppendMove ); + CPPUNIT_TEST( testAppendNew ); + CPPUNIT_TEST( testAppendTwo ); + CPPUNIT_TEST( testExpand ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testEnsureFactor ); + CPPUNIT_TEST( testEnsureOther ); + CPPUNIT_TEST( testEnsureMoves ); + + CPPUNIT_TEST( testRemoveEnd ); + CPPUNIT_TEST( testRemoveMiddle ); + CPPUNIT_TEST( testRemoveSwapEnd ); + CPPUNIT_TEST( testRemoveSwapMiddle ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + + CPPUNIT_TEST( testInsertCopyEmpty ); + CPPUNIT_TEST( testInsertCopyBefore ); + CPPUNIT_TEST( testInsertCopyAfter ); + CPPUNIT_TEST( testInsertMoveEmpty ); + CPPUNIT_TEST( testInsertMoveBefore ); + CPPUNIT_TEST( testInsertMoveAfter ); + CPPUNIT_TEST( testInsertNewEmpty ); + CPPUNIT_TEST( testInsertNewBefore ); + CPPUNIT_TEST( testInsertNewAfter ); + + CPPUNIT_TEST( testSort ); + CPPUNIT_TEST( testSortComparator ); + CPPUNIT_TEST( testSortPartial ); + + CPPUNIT_TEST( testRangeOutside ); + CPPUNIT_TEST( testRangeLarger ); + CPPUNIT_TEST( testRangeSub ); + + CPPUNIT_TEST( testMoveRangeOutside ); + CPPUNIT_TEST( testMoveRangeLarger ); + CPPUNIT_TEST( testMoveRangeSub ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testDefaults( ); + + void testAppendCopy( ); + void testAppendMove( ); + void testAppendNew( ); + void testAppendTwo( ); + void testExpand( ); + + void testClear( ); + void testFree( ); + + void testEnsureFactor( ); + void testEnsureOther( ); + void testEnsureMoves( ); + + void testRemoveEnd( ); + void testRemoveMiddle( ); + void testRemoveSwapEnd( ); + void testRemoveSwapMiddle( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); + + void testIndexOf( ); + void testIndexOfMissing( ); + void testContains( ); + void testContainsMissing( ); + + void testInsertCopyEmpty( ); + void testInsertCopyBefore( ); + void testInsertCopyAfter( ); + void testInsertMoveEmpty( ); + void testInsertMoveBefore( ); + void testInsertMoveAfter( ); + void testInsertNewEmpty( ); + void testInsertNewBefore( ); + void testInsertNewAfter( ); + + void testSort( ); + void testSortComparator( ); + void testSortPartial( ); + + void testRangeOutside( ); + void testRangeLarger( ); + void testRangeSub( ); + + void testMoveRangeOutside( ); + void testMoveRangeLarger( ); + void testMoveRangeSub( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysObjectsTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +struct T_Test +{ + static uint32_t nConsDefault; + static uint32_t nConsCopy; + static uint32_t nConsMove; + static uint32_t nDestr; + + static void reset( ) + { + nConsDefault = 0; + nConsCopy = 0; + nConsMove = 0; + nDestr = 0; + } + + bool ignoreMe; + uint32_t serial; + + T_Test( ) : ignoreMe( false ) + { nConsDefault ++; } + + T_Test( uint32_t serial ) + : ignoreMe( true ) , serial( serial ) + { } + + T_Test( T_Test const& o ) + : ignoreMe( false ) , serial( o.serial ) + { nConsCopy ++; } + + T_Test( T_Test && o ) + : ignoreMe( false ) , serial( o.serial ) + { nConsMove ++; } + + ~T_Test( ) + { if ( !ignoreMe ) { nDestr ++; } } + + T_Test& operator =( T_Test const& source ) + { + serial = source.serial; + ignoreMe = false; + return *this; + } + + T_Test& operator =( T_Test&& source ) + { + std::swap( source.serial , serial ); + ignoreMe = false; + return *this; + } + + bool operator ==( T_Test const& other ) + { + return other.serial == serial; + } + + bool operator !=( T_Test const& other ) + { + return other.serial != serial; + } +}; + +uint32_t T_Test::nConsDefault = 0; +uint32_t T_Test::nConsCopy = 0; +uint32_t T_Test::nConsMove = 0; +uint32_t T_Test::nDestr = 0; + +void swap( T_Test& lhs , T_Test& rhs ) +{ + using std::swap; + swap( lhs.serial , rhs.serial ); + swap( lhs.ignoreMe , rhs.ignoreMe ); +} + +} // namespace + +namespace lw { +M_DECLARE_COMPARATOR( T_Test ); +M_DEFINE_COMPARATOR( T_Test ) +{ + return T_Comparator< uint32_t >::compare( a.serial , b.serial ); +} +} // namespace lw + +void ArraysObjectsTest::tearDown( ) +{ + T_Test::reset( ); +} + +#define M_CHECK_(ncd,ncc,ncm,nd) \ + do { \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncd ) , \ + T_Test::nConsDefault ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncc ) , T_Test::nConsCopy ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncm ) , T_Test::nConsMove ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( nd ) , T_Test::nDestr ); \ + } while ( 0 ) + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testDefaults( ) +{ + T_Array< T_Test > array; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4096 / sizeof( T_Test ) ) , + array.growth( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testAppendCopy( ) +{ + { + T_Array< T_Test > array; + T_Test obj( 0 ); + array.add( obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 1 ); +} + +void ArraysObjectsTest::testAppendMove( ) +{ + { + T_Array< T_Test > array; + array.add( T_Test( 0 ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 1 ); +} + +void ArraysObjectsTest::testAppendNew( ) +{ + { + T_Array< T_Test > array; + array.addNew( 0 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void ArraysObjectsTest::testAppendTwo( ) +{ + { + T_Array< T_Test > array; + T_Test obj1( 0 ) , obj2( 1 ); + array << obj1 << obj2; + M_CHECK_( 0 , 2 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 1 ].serial ); + } + M_CHECK_( 0 , 2 , 0 , 2 ); +} + +void ArraysObjectsTest::testExpand( ) +{ + uint32_t g , n; + { + T_Array< T_Test > array; + g = array.growth( ); + n = g * 3 / 2; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + T_Test obj( i ); + array.add( obj ); + } + M_CHECK_( 0 , n , g , g ); + + CPPUNIT_ASSERT_EQUAL( n , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2 * g , array.capacity( ) ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , array[ i ].serial ); + } + + } + M_CHECK_( 0 , n , g , g + n ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testClear( ) +{ + uint32_t n; + { + T_Array< T_Test > array; + n = array.growth( ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + T_Test obj( i ); + array.add( obj ); + } + array.clear( ); + M_CHECK_( 0 , n , 0 , n ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + } + M_CHECK_( 0 , n , 0 , n ); +} + +void ArraysObjectsTest::testFree( ) +{ + uint32_t n; + { + T_Array< T_Test > array; + n = array.growth( ); + for ( uint32_t i = 0 ; i < n ; i ++ ) { + T_Test obj( i ); + array.add( obj ); + } + array.free( ); + M_CHECK_( 0 , n , 0 , n ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.capacity( ) ); + } + M_CHECK_( 0 , n , 0 , n ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testEnsureFactor( ) +{ + { + T_Array< T_Test > array; + array.ensureCapacity( array.growth( ) * 2 ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) * 2 , array.capacity( ) ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void ArraysObjectsTest::testEnsureOther( ) +{ + { + T_Array< T_Test > array; + array.ensureCapacity( array.growth( ) * 3 / 2 ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) * 2 , array.capacity( ) ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void ArraysObjectsTest::testEnsureMoves( ) +{ + { + T_Array< T_Test > array; + array.add( T_Test( 0 ) ); + array.ensureCapacity( array.growth( ) * 2 ); + M_CHECK_( 0 , 0 , 2 , 1 ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) * 2 , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 2 , 2 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testRemoveEnd( ) +{ + { + T_Array< T_Test > array; + array << T_Test( 0 ) << T_Test( 1 ) << T_Test( 2 ); + M_CHECK_( 0 , 0 , 3 , 0 ); + + array.remove( 2 ); + M_CHECK_( 0 , 0 , 3 , 1 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 3 , 3 ); +} + +void ArraysObjectsTest::testRemoveMiddle( ) +{ + { + T_Array< T_Test > array; + array << T_Test( 0 ) << T_Test( 1 ) << T_Test( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + M_CHECK_( 0 , 0 , 3 , 0 ); + + array.remove( 1 ); + M_CHECK_( 0 , 0 , 3 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 3 , 3 ); +} + +void ArraysObjectsTest::testRemoveSwapEnd( ) +{ + { + T_Array< T_Test > array; + array << T_Test( 0 ) << T_Test( 1 ) << T_Test( 2 ); + M_CHECK_( 0 , 0 , 3 , 0 ); + + array.removeSwap( 2 ); + M_CHECK_( 0 , 0 , 3 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 3 , 3 ); +} + +void ArraysObjectsTest::testRemoveSwapMiddle( ) +{ + { + T_Array< T_Test > array; + array << T_Test( 0 ) << T_Test( 1 ) << T_Test( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + M_CHECK_( 0 , 0 , 3 , 0 ); + + array.removeSwap( 0 ); + M_CHECK_( 0 , 0 , 3 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 3 , 3 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testCopyCons( ) +{ + { + T_Array< T_Test > array; + array.ensureCapacity( array.growth( ) * 2 ); + array.add( T_Test( 0 ) ); + + T_Array< T_Test > copy( array ); + M_CHECK_( 0 , 1 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , copy.growth( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , copy[ 0 ].serial ); + + copy.add( T_Test( 1 ) ); + M_CHECK_( 0 , 1 , 2 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + } + M_CHECK_( 0 , 1 , 2 , 3 ); +} + +void ArraysObjectsTest::testCopyAss( ) +{ + { + T_Array< T_Test > array; + array.ensureCapacity( array.growth( ) * 2 ); + array.add( T_Test( 0 ) ); + + T_Array< T_Test > copy; + copy.add( T_Test( 1 ) ); + M_CHECK_( 0 , 0 , 2 , 0 ); + + copy = array; + M_CHECK_( 0 , 1 , 2 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , copy.growth( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , copy[ 0 ].serial ); + + copy.add( T_Test( 1 ) ); + M_CHECK_( 0 , 1 , 3 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + } + M_CHECK_( 0 , 1 , 3 , 4 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testMoveCons( ) +{ + { + T_Array< T_Test > array( std::move( + T_Array< T_Test >( 128 ).ensureCapacity( 256 ) + << T_Test( 0 ) ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , array.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 256 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 1 ); +} + +void ArraysObjectsTest::testMoveAss( ) +{ + { + T_Array< T_Test > array; + array << T_Test( 1 ); + array = std::move( T_Array< T_Test >( 128 ).ensureCapacity( 256 ) << T_Test( 0 ) ); + M_CHECK_( 0 , 0 , 2 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , array.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 256 ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 2 , 2 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testSwap( ) +{ + T_Array< T_Test > a1; + T_Array< T_Test > a2; + a1 << 1 << 2 << 3; + a2 << 2 << 4 << 6 << 8; + swap( a1 , a2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , a1.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , a2.size( ) ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT( a1[ i ].serial == a2[ i ].serial * 2 ); + } + CPPUNIT_ASSERT( a1[ 3 ].serial == 8 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testIndexOf( ) +{ + T_Array< T_Test > a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , a1.indexOf( T_Test( 2 ) ) ); +} + +void ArraysObjectsTest::testIndexOfMissing( ) +{ + T_Array< T_Test > a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , a1.indexOf( T_Test( 8 ) ) ); +} + +void ArraysObjectsTest::testContains( ) +{ + T_Array< T_Test > a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT( a1.contains( T_Test( 2 ) ) ); +} + +void ArraysObjectsTest::testContainsMissing( ) +{ + T_Array< T_Test > a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT( !a1.contains( T_Test( 4 ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testInsertCopyEmpty( ) +{ + { + T_Array< T_Test > array; + T_Test obj( 0 ); + array.insert( 0 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 1 ); +} + +void ArraysObjectsTest::testInsertCopyBefore( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << i; + } + T_Test::reset( ); + + T_Test obj( 15 ); + array.insert( 0 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 11 ); +} + +void ArraysObjectsTest::testInsertCopyAfter( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << i; + } + T_Test::reset( ); + + T_Test obj( 15 ); + array.insert( 10 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 10 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 11 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testInsertMoveEmpty( ) +{ + { + T_Array< T_Test > array; + T_Test obj( 15 ); + array.insert( 0 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 1 ); +} + +void ArraysObjectsTest::testInsertMoveBefore( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << i; + } + T_Test::reset( ); + + T_Test obj( 15 ); + array.insert( 0 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 11 ); +} + +void ArraysObjectsTest::testInsertMoveAfter( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << i; + } + T_Test::reset( ); + + T_Test obj( 15 ); + array.insert( 10 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 10 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 11 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testInsertNewEmpty( ) +{ + { + T_Array< T_Test > array; + array.insertNew( 0 , 15 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void ArraysObjectsTest::testInsertNewBefore( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << ( i + 2 ); + } + + array.insertNew( 0 , 15 ); + M_CHECK_( 0 , 0 , 10 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 10 , 10 ); +} + +void ArraysObjectsTest::testInsertNewAfter( ) +{ + { + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 10 ; i ++ ) { + array << ( i + 2 ); + } + + array.insertNew( 10 , 15 ); + M_CHECK_( 0 , 0 , 10 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 11 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.growth( ) , array.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 15 ) , array[ 10 ].serial ); + } + M_CHECK_( 0 , 0 , 10 , 10 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testSort( ) +{ + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << T_Test( std::rand( ) ); + } + array.sort( ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial >= array[ i - 1 ].serial ); + } +} + +void ArraysObjectsTest::testSortComparator( ) +{ + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << T_Test( std::rand( ) ); + } + array.sort( []( T_Test const& a , T_Test const& b ){ + return T_Comparator< T_Test >::compare( b , a ); + } ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial <= array[ i - 1 ].serial ); + } +} + +void ArraysObjectsTest::testSortPartial( ) +{ + T_Array< T_Test > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + T_Array< T_Test > copy( array ); + array.sort( 1000 , 1000 ); + + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ].serial , array[ i ].serial ); + } + for ( uint32_t i = 1001 ; i < 2000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial >= array[ i - 1 ].serial ); + } + for ( uint32_t i = 2000 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ].serial , array[ i ].serial ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testRangeOutside( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.copyRange( 7000 , 1000 ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , range.size( ) ); +} + +void ArraysObjectsTest::testRangeLarger( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.copyRange( 1000 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4000 ) , range.size( ) ); + M_CHECK_( 0 , 4000 , 0 , 0 ); + for ( uint32_t i = 0 ; i < 4000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +void ArraysObjectsTest::testRangeSub( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.copyRange( 1000 , 1999 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1000 ) , range.size( ) ); + M_CHECK_( 0 , 1000 , 0 , 0 ); + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysObjectsTest::testMoveRangeOutside( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.moveRange( 7000 , 1000 ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , range.size( ) ); +} + +void ArraysObjectsTest::testMoveRangeLarger( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.moveRange( 1000 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4000 ) , range.size( ) ); + M_CHECK_( 0 , 0 , 4000 , 0 ); + for ( uint32_t i = 0 ; i < 4000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +void ArraysObjectsTest::testMoveRangeSub( ) +{ + T_Array< T_Test > array( 5000 ); + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_Array< T_Test > range( array.moveRange( 1000 , 1999 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1000 ) , range.size( ) ); + M_CHECK_( 0 , 0 , 1000 , 0 ); + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} diff --git a/tests/arrays-static-basic.cc b/tests/arrays-static-basic.cc new file mode 100644 index 0000000..dae7617 --- /dev/null +++ b/tests/arrays-static-basic.cc @@ -0,0 +1,468 @@ +#include +#include +using namespace lw; + + +class ArraysStaticBasicTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysStaticBasicTest ); + CPPUNIT_TEST( testInitialValues ); + + CPPUNIT_TEST( testAppendOne ); + CPPUNIT_TEST( testAppendTwo ); + + CPPUNIT_TEST( testClear ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testAddAllEmpty ); + CPPUNIT_TEST( testAddAll ); + CPPUNIT_TEST( testAddAllLimit ); + CPPUNIT_TEST( testAddAllMove ); + + CPPUNIT_TEST( testInsertEmpty ); + CPPUNIT_TEST( testInsertAfter ); + CPPUNIT_TEST( testInsertBefore ); + + CPPUNIT_TEST( testRemoveEnd ); + CPPUNIT_TEST( testRemoveMiddle ); + CPPUNIT_TEST( testRemoveSwapEnd ); + CPPUNIT_TEST( testRemoveSwapMiddle ); + + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + + CPPUNIT_TEST( testSort ); + CPPUNIT_TEST( testSortComparator ); + CPPUNIT_TEST( testSortPartial ); + + CPPUNIT_TEST( testRangeOutside ); + CPPUNIT_TEST( testRangeLarger ); + CPPUNIT_TEST( testRangeSub ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInitialValues( ); + + void testAppendOne( ); + void testAppendTwo( ); + + void testClear( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); + + void testRemoveEnd( ); + void testRemoveMiddle( ); + void testRemoveSwapEnd( ); + void testRemoveSwapMiddle( ); + + void testAddAllEmpty( ); + void testAddAll( ); + void testAddAllLimit( ); + void testAddAllMove( ); + + void testInsertEmpty( ); + void testInsertAfter( ); + void testInsertBefore( ); + + void testIndexOf( ); + void testIndexOfMissing( ); + void testContains( ); + void testContainsMissing( ); + + void testSort( ); + void testSortComparator( ); + void testSortPartial( ); + + void testRangeOutside( ); + void testRangeLarger( ); + void testRangeSub( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysStaticBasicTest ); + +/*----------------------------------------------------------------------------*/ + +using T_Test_ = T_StaticArray< uint32_t , 8 >; + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testInitialValues( ) +{ + T_Test_ array; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , array.capacity( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testAppendOne( ) +{ + T_Test_ array; + array.add( 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); +} + +void ArraysStaticBasicTest::testAppendTwo( ) +{ + T_Test_ array; + array.add( 123 ); + array.add( 456 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testClear( ) +{ + T_Test_ array; + const uint32_t n = 8; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + array.add( i ); + } + array.clear( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , array.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testCopyCons( ) +{ + T_Test_ array; + array.add( 1 ); + + T_Test_ copy( array ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy[ 0 ] ); + + copy.add( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); +} + +void ArraysStaticBasicTest::testCopyAss( ) +{ + T_Test_ array; + array.add( 1 ); + + T_Test_ copy; + copy = array; + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( array.capacity( ) , copy.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , copy[ 0 ] ); + + copy.add( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testMoveCons( ) +{ + T_Test_ init; + init.add( 1 ); + T_Test_ array( std::move( init ) ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 0 ] ); +} + +void ArraysStaticBasicTest::testMoveAss( ) +{ + T_Test_ init; + init.add( 1 ); + + T_Test_ array; + array = std::move( init ); + CPPUNIT_ASSERT_EQUAL( 0u , init.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testSwap( ) +{ + T_Test_ a1 , a2; + a1 << 1 << 2 << 3; + a2 << 2 << 4 << 6 << 8; + swap( a1 , a2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , a1.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , a2.size( ) ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT( a1[ i ] == a2[ i ] * 2 ); + } + CPPUNIT_ASSERT( a1[ 3 ] == 8 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testAddAllEmpty( ) +{ + T_Test_ empty , array; + array << 123 << 456 << 789; + array.addAll( empty ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +void ArraysStaticBasicTest::testAddAll( ) +{ + T_Test_ array , other; + array << 123 << 456 << 789; + other << 627 << 951 << 843; + array.addAll( other ); + + CPPUNIT_ASSERT_EQUAL( 6u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , other.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( other[ i ] , array[ i + 3 ] ); + } +} + +void ArraysStaticBasicTest::testAddAllLimit( ) +{ + T_Test_ array , other; + for ( auto i = 0u ; i < 4 ; i ++ ) { + array.add( i ); + other.add( i + 4 ); + } + array.addAll( other ); + + CPPUNIT_ASSERT_EQUAL( 8u , array.size( ) ); + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( i , array[ i ] ); + } +} + +void ArraysStaticBasicTest::testAddAllMove( ) +{ + T_Test_ array , other; + array << 123 << 456 << 789; + other << 627 << 951 << 843; + array.addAll( std::move( other ) ); + + CPPUNIT_ASSERT_EQUAL( 6u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , other.size( ) ); + CPPUNIT_ASSERT_EQUAL( 123u , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( 456u , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( 789u , array[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( 627u , array[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( 951u , array[ 4 ] ); + CPPUNIT_ASSERT_EQUAL( 843u , array[ 5 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testInsertEmpty( ) +{ + T_Test_ array; + array.insert( 0 , 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); +} + +void ArraysStaticBasicTest::testInsertAfter( ) +{ + T_Test_ array; + array << 123 << 456; + array.insert( 2 , 789 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +void ArraysStaticBasicTest::testInsertBefore( ) +{ + T_Test_ array; + array << 456 << 789; + array.insert( 0 , 123 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 2 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testRemoveEnd( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + array.remove( 2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +void ArraysStaticBasicTest::testRemoveMiddle( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + array.remove( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 1 ] ); +} + +void ArraysStaticBasicTest::testRemoveSwapEnd( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + array.removeSwap( 2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +void ArraysStaticBasicTest::testRemoveSwapMiddle( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + array.removeSwap( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , array[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , array[ 1 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testIndexOf( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , array.indexOf( 456 ) ); +} + +void ArraysStaticBasicTest::testIndexOfMissing( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , array.indexOf( 101112 ) ); +} + +void ArraysStaticBasicTest::testContains( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT( array.contains( 456 ) ); +} + +void ArraysStaticBasicTest::testContainsMissing( ) +{ + T_Test_ array; + array << 123 << 456 << 789; + CPPUNIT_ASSERT( !array.contains( 101112 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testSort( ) +{ + T_StaticArray< uint32_t , 5000 > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + array.sort( ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] >= array[ i - 1 ] ); + } +} + +void ArraysStaticBasicTest::testSortComparator( ) +{ + T_StaticArray< uint32_t , 5000 > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + array.sort( []( uint32_t const& a , uint32_t const& b ) { + return T_Comparator< uint32_t >::compare( b , a ); + } ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] <= array[ i - 1 ] ); + } +} + +void ArraysStaticBasicTest::testSortPartial( ) +{ + T_StaticArray< uint32_t , 5000 > array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << std::rand( ); + } + decltype( array ) copy( array ); + array.sort( 1000 , 1000 ); + + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ] , array[ i ] ); + } + for ( uint32_t i = 1001 ; i < 2000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ] >= array[ i - 1 ] ); + } + for ( uint32_t i = 2000 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ] , array[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticBasicTest::testRangeOutside( ) +{ + T_Test_ array; + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + array << std::rand( ); + } + T_Test_ range( array.copyRange( 100 , 1000 ) ); + CPPUNIT_ASSERT_EQUAL( 0u , range.size( ) ); +} + +void ArraysStaticBasicTest::testRangeLarger( ) +{ + T_Test_ array; + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + array << std::rand( ); + } + T_Test_ range( array.copyRange( 4 ) ); + CPPUNIT_ASSERT_EQUAL( 4u , range.size( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 4 ] , range[ i ] ); + } +} + +void ArraysStaticBasicTest::testRangeSub( ) +{ + T_Test_ array; + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + array << std::rand( ); + } + T_Test_ range( array.copyRange( 2 , 5 ) ); + CPPUNIT_ASSERT_EQUAL( 4u , range.size( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 2 ] , range[ i ] ); + } +} diff --git a/tests/arrays-static-objects.cc b/tests/arrays-static-objects.cc new file mode 100644 index 0000000..3a0c530 --- /dev/null +++ b/tests/arrays-static-objects.cc @@ -0,0 +1,745 @@ +#include +#include +using namespace lw; + + +class ArraysStaticObjectsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ArraysStaticObjectsTest ); + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testAppendCopy ); + CPPUNIT_TEST( testAppendMove ); + CPPUNIT_TEST( testAppendNew ); + CPPUNIT_TEST( testAppendTwo ); + + CPPUNIT_TEST( testInsertCopyEmpty ); + CPPUNIT_TEST( testInsertCopyBefore ); + CPPUNIT_TEST( testInsertCopyAfter ); + CPPUNIT_TEST( testInsertMoveEmpty ); + CPPUNIT_TEST( testInsertMoveBefore ); + CPPUNIT_TEST( testInsertMoveAfter ); + CPPUNIT_TEST( testInsertNewEmpty ); + CPPUNIT_TEST( testInsertNewBefore ); + CPPUNIT_TEST( testInsertNewAfter ); + + CPPUNIT_TEST( testRemoveEnd ); + CPPUNIT_TEST( testRemoveMiddle ); + CPPUNIT_TEST( testRemoveSwapEnd ); + CPPUNIT_TEST( testRemoveSwapMiddle ); + + CPPUNIT_TEST( testClear ); + + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + + CPPUNIT_TEST( testSort ); + CPPUNIT_TEST( testSortComparator ); + CPPUNIT_TEST( testSortPartial ); + + CPPUNIT_TEST( testCopyRangeOutside ); + CPPUNIT_TEST( testCopyRangeLarger ); + CPPUNIT_TEST( testCopyRangeSub ); + + CPPUNIT_TEST( testMoveRangeOutside ); + CPPUNIT_TEST( testMoveRangeLarger ); + CPPUNIT_TEST( testMoveRangeSub ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); + + void testAppendCopy( ); + void testAppendMove( ); + void testAppendNew( ); + void testAppendTwo( ); + + void testInsertCopyEmpty( ); + void testInsertCopyBefore( ); + void testInsertCopyAfter( ); + void testInsertMoveEmpty( ); + void testInsertMoveBefore( ); + void testInsertMoveAfter( ); + void testInsertNewEmpty( ); + void testInsertNewBefore( ); + void testInsertNewAfter( ); + + void testRemoveEnd( ); + void testRemoveMiddle( ); + void testRemoveSwapEnd( ); + void testRemoveSwapMiddle( ); + + void testClear( ); + + void testIndexOf( ); + void testIndexOfMissing( ); + void testContains( ); + void testContainsMissing( ); + + void testSort( ); + void testSortComparator( ); + void testSortPartial( ); + + void testCopyRangeOutside( ); + void testCopyRangeLarger( ); + void testCopyRangeSub( ); + + void testMoveRangeOutside( ); + void testMoveRangeLarger( ); + void testMoveRangeSub( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ArraysStaticObjectsTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +struct T_TestObject_ +{ + static uint32_t nConsDefault; + static uint32_t nConsCopy; + static uint32_t nConsMove; + static uint32_t nDestr; + + static void reset( ) + { + nConsDefault = 0; + nConsCopy = 0; + nConsMove = 0; + nDestr = 0; + } + + uint32_t serial; + + T_TestObject_( ) noexcept + { nConsDefault ++; } + + T_TestObject_( uint32_t serial ) noexcept + : serial( serial ) + { } + + T_TestObject_( T_TestObject_ const& o ) noexcept + : serial( o.serial ) + { nConsCopy ++; } + + T_TestObject_( T_TestObject_ && o ) noexcept + : serial( o.serial ) + { nConsMove ++; } + + ~T_TestObject_( ) + { nDestr ++; } + + T_TestObject_& operator =( T_TestObject_ const& source ) noexcept + { + serial = source.serial; + nConsCopy ++; + return *this; + } + + T_TestObject_& operator =( T_TestObject_ && source ) noexcept + { + std::swap( source.serial , serial ); + nConsMove ++; + return *this; + } + + bool operator ==( T_TestObject_ const& other ) const noexcept + { + return other.serial == serial; + } + + bool operator !=( T_TestObject_ const& other ) const noexcept + { + return other.serial != serial; + } +}; + +uint32_t T_TestObject_::nConsDefault = 0; +uint32_t T_TestObject_::nConsCopy = 0; +uint32_t T_TestObject_::nConsMove = 0; +uint32_t T_TestObject_::nDestr = 0; + +void swap( T_TestObject_& lhs , T_TestObject_& rhs ) +{ + using std::swap; + swap( lhs.serial , rhs.serial ); +} + +} // namespace + +namespace lw { +M_DECLARE_COMPARATOR( T_TestObject_ ); +M_DEFINE_COMPARATOR( T_TestObject_ ) +{ + return T_Comparator< uint32_t >::compare( a.serial , b.serial ); +} +} // namespace lw + +void ArraysStaticObjectsTest::tearDown( ) +{ + T_TestObject_::reset( ); +} + +using T_Test_ = T_StaticArray< T_TestObject_ , 8 >; +using T_LargeTest_ = T_StaticArray< T_TestObject_ , 5000 >; + +#define M_CHECK_(ncd,ncc,ncm,nd) \ + do { \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncd ) , \ + T_TestObject_::nConsDefault ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncc ) , T_TestObject_::nConsCopy ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( ncm ) , T_TestObject_::nConsMove ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( nd ) , T_TestObject_::nDestr ); \ + } while ( 0 ) + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testCopyCons( ) +{ + { + T_Test_ array; + array.addNew( 0 ); + + T_Test_ copy( array ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , copy[ 0 ].serial ); + + copy.addNew( 1 ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + } + M_CHECK_( 0 , 1 , 0 , 3 ); +} + +void ArraysStaticObjectsTest::testCopyAss( ) +{ + { + T_Test_ array; + array.addNew( 0 ); + + T_Test_ copy; + copy.addNew( 1 ); + + copy = array; + M_CHECK_( 0 , 1 , 0 , 1 ); + CPPUNIT_ASSERT_EQUAL( 1u , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , copy[ 0 ].serial ); + + copy.addNew( 1 ); + M_CHECK_( 0 , 1 , 0 , 1 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + } + M_CHECK_( 0 , 1 , 0 , 4 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testMoveCons( ) +{ + { + T_Test_ source; + source.addNew( 0 ); + T_Test_ array( std::move( source ) ); + + M_CHECK_( 0 , 0 , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( 0u , source.size( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 2 ); +} + +void ArraysStaticObjectsTest::testMoveAss( ) +{ + { + T_Test_ source; + source.addNew( 0 ); + T_Test_ array; + array.addNew( 1 ); + array = std::move( source ); + M_CHECK_( 0 , 0 , 1 , 2 ); + CPPUNIT_ASSERT_EQUAL( 0u , source.size( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 3 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testSwap( ) +{ + T_Test_ a1 , a2; + a1 << 1 << 2 << 3; + a2 << 2 << 4 << 6 << 8; + swap( a1 , a2 ); + + CPPUNIT_ASSERT_EQUAL( 4u , a1.size( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , a2.size( ) ); + for ( uint32_t i = 0 ; i < 3 ; i ++ ) { + CPPUNIT_ASSERT( a1[ i ].serial == a2[ i ].serial * 2 ); + } + CPPUNIT_ASSERT( a1[ 3 ].serial == 8 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testAppendCopy( ) +{ + { + T_Test_ array; + T_TestObject_ obj( 0 ); + array.add( obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 2 ); +} + +void ArraysStaticObjectsTest::testAppendMove( ) +{ + { + T_Test_ array; + array.add( T_TestObject_( 0 ) ); + M_CHECK_( 0 , 0 , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 2 ); +} + +void ArraysStaticObjectsTest::testAppendNew( ) +{ + { + T_Test_ array; + array.addNew( 0 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 1 ); +} + +void ArraysStaticObjectsTest::testAppendTwo( ) +{ + { + T_Test_ array; + T_TestObject_ obj1( 0 ) , obj2( 1 ); + array << obj1 << obj2; + M_CHECK_( 0 , 2 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 2u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( 1u , array[ 1 ].serial ); + } + M_CHECK_( 0 , 2 , 0 , 4 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testInsertCopyEmpty( ) +{ + { + T_Test_ array; + T_TestObject_ obj( 0 ); + array.insert( 0 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 2 ); +} + +void ArraysStaticObjectsTest::testInsertCopyBefore( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << i; + } + T_TestObject_::reset( ); + + T_TestObject_ obj( 15 ); + array.insert( 0 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 6 ); +} + +void ArraysStaticObjectsTest::testInsertCopyAfter( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << i; + } + T_TestObject_::reset( ); + + T_TestObject_ obj( 15 ); + array.insert( 4 , obj ); + M_CHECK_( 0 , 1 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 4 ].serial ); + } + M_CHECK_( 0 , 1 , 0 , 6 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testInsertMoveEmpty( ) +{ + { + T_Test_ array; + T_TestObject_ obj( 15 ); + array.insert( 0 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 2 ); +} + +void ArraysStaticObjectsTest::testInsertMoveBefore( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << i; + } + T_TestObject_::reset( ); + + T_TestObject_ obj( 15 ); + array.insert( 0 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 6 ); +} + +void ArraysStaticObjectsTest::testInsertMoveAfter( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << i; + } + T_TestObject_::reset( ); + + T_TestObject_ obj( 15 ); + array.insert( 4 , std::move( obj ) ); + M_CHECK_( 0 , 0 , 1 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 4 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 6 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testInsertNewEmpty( ) +{ + { + T_Test_ array; + array.insertNew( 0 , 15 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 1u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 1 ); +} + +void ArraysStaticObjectsTest::testInsertNewBefore( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << ( i + 2 ); + } + T_TestObject_::reset( ); + + array.insertNew( 0 , 15 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 0 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 5 ); +} + +void ArraysStaticObjectsTest::testInsertNewAfter( ) +{ + { + T_Test_ array; + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + array << ( i + 2 ); + } + T_TestObject_::reset( ); + + array.insertNew( 4 , 15 ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 5u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 15u , array[ 4 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 5 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testRemoveEnd( ) +{ + { + T_Test_ array; + array << 0 << 1 << 2; + T_TestObject_::reset( ); + + array.remove( 2 ); + M_CHECK_( 0 , 0 , 0 , 1 ); + + CPPUNIT_ASSERT_EQUAL( 2u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( 1u , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 3 ); +} + +void ArraysStaticObjectsTest::testRemoveMiddle( ) +{ + { + T_Test_ array; + array << 0 << 1 << 2; + T_TestObject_::reset( ); + + array.remove( 1 ); + M_CHECK_( 0 , 0 , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( 2u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( 2u , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 3 ); +} + +void ArraysStaticObjectsTest::testRemoveSwapEnd( ) +{ + { + T_Test_ array; + array << 0 << 1 << 2; + T_TestObject_::reset( ); + + array.removeSwap( 2 ); + M_CHECK_( 0 , 0 , 0 , 1 ); + CPPUNIT_ASSERT_EQUAL( 2u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( 1u , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 0 , 3 ); +} + +void ArraysStaticObjectsTest::testRemoveSwapMiddle( ) +{ + { + T_Test_ array; + array << 0 << 1 << 2; + T_TestObject_::reset( ); + + array.removeSwap( 0 ); + M_CHECK_( 0 , 0 , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( 2u , array.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2u , array[ 0 ].serial ); + CPPUNIT_ASSERT_EQUAL( 1u , array[ 1 ].serial ); + } + M_CHECK_( 0 , 0 , 1 , 3 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testClear( ) +{ + const uint32_t n = 8u; + { + T_Test_ array; + for ( uint32_t i = 0 ; i < n ; i ++ ) { + array << T_TestObject_( i ); + } + array.clear( ); + M_CHECK_( 0 , 0 , n , n * 2 ); + CPPUNIT_ASSERT_EQUAL( 0u , array.size( ) ); + } + M_CHECK_( 0 , 0 , n , n * 2 ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testIndexOf( ) +{ + T_Test_ a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT_EQUAL( 1 , a1.indexOf( T_TestObject_( 2 ) ) ); +} + +void ArraysStaticObjectsTest::testIndexOfMissing( ) +{ + T_Test_ a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT_EQUAL( -1 , a1.indexOf( T_TestObject_( 8 ) ) ); +} + +void ArraysStaticObjectsTest::testContains( ) +{ + T_Test_ a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT( a1.contains( T_TestObject_( 2 ) ) ); +} + +void ArraysStaticObjectsTest::testContainsMissing( ) +{ + T_Test_ a1; + a1 << 1 << 2 << 3; + CPPUNIT_ASSERT( !a1.contains( T_TestObject_( 4 ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testSort( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << T_TestObject_( std::rand( ) ); + } + array.sort( ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial >= array[ i - 1 ].serial ); + } +} + +void ArraysStaticObjectsTest::testSortComparator( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << T_TestObject_( std::rand( ) ); + } + array.sort( []( T_TestObject_ const& a , T_TestObject_ const& b ){ + return T_Comparator< T_TestObject_ >::compare( b , a ); + } ); + + for ( uint32_t i = 1 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial <= array[ i - 1 ].serial ); + } +} + +void ArraysStaticObjectsTest::testSortPartial( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array << T_TestObject_( std::rand( ) ); + } + T_LargeTest_ copy( array ); + array.sort( 1000 , 1000 ); + + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ].serial , array[ i ].serial ); + } + for ( uint32_t i = 1001 ; i < 2000 ; i ++ ) { + CPPUNIT_ASSERT( array[ i ].serial >= array[ i - 1 ].serial ); + } + for ( uint32_t i = 2000 ; i < 5000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( copy[ i ].serial , array[ i ].serial ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testCopyRangeOutside( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.copyRange( 7000 , 1000 ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( 0u , range.size( ) ); +} + +void ArraysStaticObjectsTest::testCopyRangeLarger( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.copyRange( 1000 ) ); + CPPUNIT_ASSERT_EQUAL( 4000u , range.size( ) ); + M_CHECK_( 0 , 4000 , 4000 , 4000 ); + for ( uint32_t i = 0 ; i < 4000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +void ArraysStaticObjectsTest::testCopyRangeSub( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.copyRange( 1000 , 1999 ) ); + CPPUNIT_ASSERT_EQUAL( 1000u , range.size( ) ); + M_CHECK_( 0 , 1000 , 1000 , 1000 ); + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ArraysStaticObjectsTest::testMoveRangeOutside( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.moveRange( 7000 , 1000 ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , range.size( ) ); +} + +void ArraysStaticObjectsTest::testMoveRangeLarger( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.moveRange( 1000 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4000 ) , range.size( ) ); + M_CHECK_( 0 , 0 , 8000 , 4000 ); + for ( uint32_t i = 0 ; i < 4000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} + +void ArraysStaticObjectsTest::testMoveRangeSub( ) +{ + T_LargeTest_ array; + for ( uint32_t i = 0 ; i < 5000 ; i ++ ) { + array.addNew( std::rand( ) ); + } + T_LargeTest_ range( array.moveRange( 1000 , 1999 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1000 ) , range.size( ) ); + M_CHECK_( 0 , 0 , 2000 , 1000 ); + for ( uint32_t i = 0 ; i < 1000 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( array[ i + 1000 ].serial , range[ i ].serial ); + } +} diff --git a/tests/binary-reader.cc b/tests/binary-reader.cc new file mode 100644 index 0000000..74c37ce --- /dev/null +++ b/tests/binary-reader.cc @@ -0,0 +1,164 @@ +#include +#include +#include +using namespace lw; + + +/* - StreamReaderTest ---------------------------------------------------{{{-*/ +class StreamReaderTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StreamReaderTest ); + CPPUNIT_TEST( testNumericBigEndian8 ); + CPPUNIT_TEST( testNumericLittleEndian8 ); + CPPUNIT_TEST( testNumericBigEndian16 ); + CPPUNIT_TEST( testNumericLittleEndian16 ); + CPPUNIT_TEST( testNumericBigEndian32 ); + CPPUNIT_TEST( testNumericLittleEndian32 ); + CPPUNIT_TEST( testNumericBigEndian32f ); + CPPUNIT_TEST( testNumericLittleEndian32f ); + CPPUNIT_TEST( testNumericBigEndian64 ); + CPPUNIT_TEST( testNumericLittleEndian64 ); + CPPUNIT_TEST( testNumericBigEndian64f ); + CPPUNIT_TEST( testNumericLittleEndian64f ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testNumericBigEndian8( ); + void testNumericLittleEndian8( ); + void testNumericBigEndian16( ); + void testNumericLittleEndian16( ); + void testNumericBigEndian32( ); + void testNumericLittleEndian32( ); + void testNumericBigEndian32f( ); + void testNumericLittleEndian32f( ); + void testNumericBigEndian64( ); + void testNumericLittleEndian64( ); + void testNumericBigEndian64f( ); + void testNumericLittleEndian64f( ); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION( StreamReaderTest ); + +void StreamReaderTest::testNumericBigEndian8( ) // {{{ +{ + const uint8_t data[] = { 0x12 }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , + rd.readNumericBigEndian< uint8_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian8( ) // {{{ +{ + const uint8_t data[] = { 0x12 }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , + rd.readNumericLittleEndian< uint8_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericBigEndian16( ) // {{{ +{ + const uint8_t data[] = { 0xbe , 0xef }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint16_t( 0xbeef ) , + rd.readNumericBigEndian< uint16_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian16( ) // {{{ +{ + const uint8_t data[] = { 0xbe , 0xef }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint16_t( 0xefbe ) , + rd.readNumericLittleEndian< uint16_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericBigEndian32( ) // {{{ +{ + const uint8_t data[] = { 0xde , 0xad , 0xbe , 0xef }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0xdeadbeef ) , + rd.readNumericBigEndian< uint32_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian32( ) // {{{ +{ + const uint8_t data[] = { 0xde , 0xad , 0xbe , 0xef }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0xefbeadde ) , + rd.readNumericLittleEndian< uint32_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericBigEndian32f( ) // {{{ +{ + const uint8_t data[] = { 0x3f , 0x80 , 0x00 , 0x00 }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( 1.0f , rd.readNumericBigEndian< float >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian32f( ) // {{{ +{ + const uint8_t data[] = { 0x00 , 0x00 , 0x80 , 0x3f }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( 1.0f , rd.readNumericLittleEndian< float >( ) ); +} + +// }}} +void StreamReaderTest::testNumericBigEndian64( ) // {{{ +{ + const uint8_t data[] = { + 0xba , 0xdb , 0xa7 , 0x5e , 0xa7 , 0xba , 0xb1 , 0x35 + }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0xbadba75ea7bab135L ) , + rd.readNumericBigEndian< uint64_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian64( ) // {{{ +{ + const uint8_t data[] = { + 0x35 , 0xb1 , 0xba , 0xa7 , 0x5e , 0xa7 , 0xdb , 0xba , + }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0xbadba75ea7bab135L ) , + rd.readNumericLittleEndian< uint64_t >( ) ); +} + +// }}} +void StreamReaderTest::testNumericBigEndian64f( ) // {{{ +{ + const uint8_t data[] = { 0x3f , 0xf0 , 0 , 0 , 0 , 0 , 0 , 0 }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( 1.0 , rd.readNumericBigEndian< double >( ) ); +} + +// }}} +void StreamReaderTest::testNumericLittleEndian64f( ) // {{{ +{ + const uint8_t data[] = { 0 , 0 , 0 , 0 , 0 , 0 , 0xf0 , 0x3f }; + T_MemoryInputStream ms( data , sizeof( data ) ); + T_BinaryReader rd( ms ); + CPPUNIT_ASSERT_EQUAL( 1.0 , rd.readNumericLittleEndian< double >( ) ); +} + +// }}} + +/*------------------------------------------------------------------------}}}-*/ diff --git a/tests/binary-writer.cc b/tests/binary-writer.cc new file mode 100644 index 0000000..e575bb9 --- /dev/null +++ b/tests/binary-writer.cc @@ -0,0 +1,228 @@ +#include +#include +#include +using namespace lw; + + +/* - BinaryWriterTest ---------------------------------------------------{{{-*/ +class BinaryWriterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( BinaryWriterTest ); + CPPUNIT_TEST( testNumericBigEndian8 ); + CPPUNIT_TEST( testNumericLittleEndian8 ); + CPPUNIT_TEST( testNumericBigEndian16 ); + CPPUNIT_TEST( testNumericLittleEndian16 ); + CPPUNIT_TEST( testNumericBigEndian32 ); + CPPUNIT_TEST( testNumericLittleEndian32 ); + CPPUNIT_TEST( testNumericBigEndian32f ); + CPPUNIT_TEST( testNumericLittleEndian32f ); + CPPUNIT_TEST( testNumericBigEndian64 ); + CPPUNIT_TEST( testNumericLittleEndian64 ); + CPPUNIT_TEST( testNumericBigEndian64f ); + CPPUNIT_TEST( testNumericLittleEndian64f ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testNumericBigEndian8( ); + void testNumericLittleEndian8( ); + void testNumericBigEndian16( ); + void testNumericLittleEndian16( ); + void testNumericBigEndian32( ); + void testNumericLittleEndian32( ); + void testNumericBigEndian32f( ); + void testNumericLittleEndian32f( ); + void testNumericBigEndian64( ); + void testNumericLittleEndian64( ); + void testNumericBigEndian64f( ); + void testNumericLittleEndian64f( ); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION( BinaryWriterTest ); + +void BinaryWriterTest::testNumericBigEndian8( ) // {{{ +{ + const uint8_t data[] = { 0x12 }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( uint8_t( 0x12 ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian8( ) // {{{ +{ + const uint8_t data[] = { 0x12 }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( uint8_t( 0x12 ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericBigEndian16( ) // {{{ +{ + const uint8_t data[] = { 0xbe , 0xef }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( uint16_t( 0xbeef ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian16( ) // {{{ +{ + const uint8_t data[] = { 0xbe , 0xef }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( uint16_t( 0xefbe ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericBigEndian32( ) // {{{ +{ + const uint8_t data[] = { 0xde , 0xad , 0xbe , 0xef }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( uint32_t( 0xdeadbeef ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian32( ) // {{{ +{ + const uint8_t data[] = { 0xde , 0xad , 0xbe , 0xef }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( uint32_t( 0xefbeadde ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericBigEndian32f( ) // {{{ +{ + const uint8_t data[] = { 0x3f , 0x80 , 0x00 , 0x00 }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( 1.0f ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian32f( ) // {{{ +{ + const uint8_t data[] = { 0x00 , 0x00 , 0x80 , 0x3f }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( 1.0f ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericBigEndian64( ) // {{{ +{ + const uint8_t data[] = { + 0xba , 0xdb , 0xa7 , 0x5e , 0xa7 , 0xba , 0xb1 , 0x35 + }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( uint64_t( 0xbadba75ea7bab135L ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian64( ) // {{{ +{ + const uint8_t data[] = { + 0x35 , 0xb1 , 0xba , 0xa7 , 0x5e , 0xa7 , 0xdb , 0xba , + }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( uint64_t( 0xbadba75ea7bab135L ) ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericBigEndian64f( ) // {{{ +{ + const uint8_t data[] = { 0x3f , 0xf0 , 0 , 0 , 0 , 0 , 0 , 0 }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericBigEndian( 1.0 ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} +void BinaryWriterTest::testNumericLittleEndian64f( ) // {{{ +{ + const uint8_t data[] = { 0 , 0 , 0 , 0 , 0 , 0 , 0xf0 , 0x3f }; + T_Buffer< uint8_t > buffer; + T_MemoryOutputStream ms( buffer ); + T_BinaryWriter wr( ms ); + wr.writeNumericLittleEndian( 1.0 ); + + CPPUNIT_ASSERT_EQUAL( sizeof( data ) , buffer.size( ) ); + for ( size_t i = 0 ; i < sizeof( data ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , buffer[ i ] ); + } +} + +// }}} + +/*------------------------------------------------------------------------}}}-*/ diff --git a/tests/buffers.cc b/tests/buffers.cc new file mode 100644 index 0000000..1c25a66 --- /dev/null +++ b/tests/buffers.cc @@ -0,0 +1,319 @@ +#include +#include +using namespace lw; + + +class FixedBufferTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( FixedBufferTest ); + CPPUNIT_TEST( testInit ); + CPPUNIT_TEST( testSetAll ); + CPPUNIT_TEST( testWrite ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInit( ); + void testSetAll( ); + void testWrite( ); + void testMoveCons( ); + void testMoveAss( ); + void testCopyCons( ); + void testCopyAss( ); + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( FixedBufferTest ); + +/*----------------------------------------------------------------------------*/ + +class BufferTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( BufferTest ); + CPPUNIT_TEST( testInitEmpty ); + CPPUNIT_TEST( testInitSize ); + CPPUNIT_TEST( testInitData ); + + CPPUNIT_TEST( testWrite ); + CPPUNIT_TEST( testSetAll ); + CPPUNIT_TEST( testCopyAll ); + + CPPUNIT_TEST( testResizeFromZero ); + CPPUNIT_TEST( testResize ); + CPPUNIT_TEST( testResizeToZero ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInitEmpty( ); + void testInitSize( ); + void testInitData( ); + + void testWrite( ); + void testSetAll( ); + void testCopyAll( ); + + void testResizeFromZero( ); + void testResize( ); + void testResizeToZero( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( BufferTest ); + +/*----------------------------------------------------------------------------*/ + +void FixedBufferTest::testInit( ) +{ + T_FixedBuffer< 4096 , char > buffer; + CPPUNIT_ASSERT_EQUAL( size_t( 4096 ) , buffer.bytes( ) ); + T_FixedBuffer< 4096 , int16_t > b2; + CPPUNIT_ASSERT_EQUAL( size_t( 8192 ) , b2.bytes( ) ); +} + +void FixedBufferTest::testSetAll( ) +{ + T_FixedBuffer< 4096 , uint32_t > b; + b.setAll( 0xdeadbeef ); + for ( uint32_t i = 0 ; i < 4096 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , b[ i ] ); + } +} + +void FixedBufferTest::testWrite( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b[ 0 ] ); +} + +void FixedBufferTest::testMoveCons( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + T_FixedBuffer< 1 , uint32_t > b2( std::move( b ) ); + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b2[ 0 ] ); +} + +void FixedBufferTest::testMoveAss( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + T_FixedBuffer< 1 , uint32_t > b2; + b2[ 0 ] = 0xdeadbeefu; + b2 = std::move( b ); + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , b[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b2[ 0 ] ); +} + +void FixedBufferTest::testCopyCons( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + T_FixedBuffer< 1 , uint32_t > b2( b ); + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b2[ 0 ] ); +} + +void FixedBufferTest::testCopyAss( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + T_FixedBuffer< 1 , uint32_t > b2; + b2[ 0 ] = 0xdeadbeefu; + b2 = b; + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b2[ 0 ] ); +} + +void FixedBufferTest::testSwap( ) +{ + T_FixedBuffer< 1 , uint32_t > b; + b[ 0 ] = 0x1a7ef00du; + T_FixedBuffer< 1 , uint32_t > b2; + b2[ 0 ] = 0xdeadbeefu; + swap( b , b2 ); + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , b[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( 0x1a7ef00du , b2[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void BufferTest::testInitEmpty( ) +{ + T_Buffer< uint32_t > test; + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.bytes( ) ); +} + +void BufferTest::testInitSize( ) +{ + T_Buffer< uint32_t > test( 256 ); + CPPUNIT_ASSERT_EQUAL( size_t( 256 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 1024 ) , test.bytes( ) ); +} + +void BufferTest::testInitData( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( data , 4 ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , test[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void BufferTest::testWrite( ) +{ + T_Buffer< uint32_t > test( 4 ); + for ( uint32_t i = 0 ; i < test.size( ) ; i ++ ) { + test[ i ] = 10 + i * 2; + } + for ( uint32_t i = 0 ; i < test.size( ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint32_t( 10 + i * 2 ) , test[ i ] ); + } +} + +void BufferTest::testSetAll( ) +{ + T_Buffer< uint32_t > test( 256 ); + test.setAll( 0xdeadbeefu ); + for ( uint32_t i = 0 ; i < test.size( ) ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , test[ i ] ); + } +} + +void BufferTest::testCopyAll( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( 4 ); + test.copyAll( data ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , test[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void BufferTest::testResizeFromZero( ) +{ + T_Buffer< uint32_t > test; + test.resize( 4 ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test.bytes( ) ); + test[ 3 ] = 0xdeadbeefu; + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , test[ 3 ] ); +} + +void BufferTest::testResize( ) +{ + T_Buffer< uint32_t > test( 256 ); + test.resize( 4 ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test.bytes( ) ); + test[ 3 ] = 0xdeadbeefu; + CPPUNIT_ASSERT_EQUAL( 0xdeadbeefu , test[ 3 ] ); +} + +void BufferTest::testResizeToZero( ) +{ + T_Buffer< uint32_t > test( 256 ); + test.resize( 0 ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.bytes( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void BufferTest::testCopyCons( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( data , 4 ); + T_Buffer< uint32_t > copy( test ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test.bytes( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , copy.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , copy[ i ] ); + } +} + +void BufferTest::testCopyAss( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( data , 4 ); + T_Buffer< uint32_t > copy; + copy = test; + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test.bytes( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , copy.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , copy[ i ] ); + } +} + +void BufferTest::testMoveCons( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( data , 4 ); + T_Buffer< uint32_t > copy( std::move( test ) ); + + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.bytes( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , copy.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , copy[ i ] ); + } +} + +void BufferTest::testMoveAss( ) +{ + static const uint32_t data[] = { 12 , 34 , 56 , 78 }; + T_Buffer< uint32_t > test( data , 4 ); + T_Buffer< uint32_t > copy; + copy = std::move( test ); + + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , test.bytes( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , copy.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data[ i ] , copy[ i ] ); + } +} + +void BufferTest::testSwap( ) +{ + static const uint32_t data1[] = { 12 , 34 , 56 , 78 }; + static const uint32_t data2[] = { 910 , 1112 , 1314 , 1516 }; + T_Buffer< uint32_t > test1( data1 , 4 ); + T_Buffer< uint32_t > test2( data2 , 4 ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test1.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test1.bytes( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , test2.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 16 ) , test2.bytes( ) ); + for ( uint32_t i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( data1[ i ] , test2[ i ] ); + CPPUNIT_ASSERT_EQUAL( data2[ i ] , test1[ i ] ); + } +} + diff --git a/tests/console-edit.cc b/tests/console-edit.cc new file mode 100644 index 0000000..8338f5b --- /dev/null +++ b/tests/console-edit.cc @@ -0,0 +1,468 @@ +#include +#include +using namespace lw; + + +class ConsoleEditTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ConsoleEditTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testSetContents ); + CPPUNIT_TEST( testReset ); + + CPPUNIT_TEST( testCursorStart ); + CPPUNIT_TEST( testCursorStartEmpty ); + CPPUNIT_TEST( testCursorEnd ); + CPPUNIT_TEST( testCursorEndEmpty ); + CPPUNIT_TEST( testCursorLeft ); + CPPUNIT_TEST( testCursorLeftStart ); + CPPUNIT_TEST( testCursorRight ); + CPPUNIT_TEST( testCursorRightEnd ); + + CPPUNIT_TEST( testAppendCharacter ); + CPPUNIT_TEST( testInsertCharacter ); + CPPUNIT_TEST( testAppendBuffer ); + CPPUNIT_TEST( testInsertBuffer ); + + CPPUNIT_TEST( testPrevWordStart ); + CPPUNIT_TEST( testPrevWordInWord ); + CPPUNIT_TEST( testPrevWordSpaces ); + CPPUNIT_TEST( testNextWordEnd ); + CPPUNIT_TEST( testNextWordInWord ); + CPPUNIT_TEST( testNextWordSpaces ); + + CPPUNIT_TEST( testRmCharAtEnd ); + CPPUNIT_TEST( testRmCharLast ); + CPPUNIT_TEST( testRmCharMiddle ); + + CPPUNIT_TEST( testRmWordAtEnd ); + CPPUNIT_TEST( testRmWordMiddle ); + CPPUNIT_TEST( testRmWordAtStart ); + + CPPUNIT_TEST( testRmRestAtStart ); + CPPUNIT_TEST( testRmRestMiddle ); + CPPUNIT_TEST( testRmRestAtEnd ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testSetContents( ); + void testReset( ); + + void testCursorStart( ); + void testCursorStartEmpty( ); + void testCursorEnd( ); + void testCursorEndEmpty( ); + void testCursorLeft( ); + void testCursorLeftStart( ); + void testCursorRight( ); + void testCursorRightEnd( ); + + void testAppendCharacter( ); + void testInsertCharacter( ); + void testAppendBuffer( ); + void testInsertBuffer( ); + + void testPrevWordStart( ); + void testPrevWordInWord( ); + void testPrevWordSpaces( ); + void testNextWordEnd( ); + void testNextWordInWord( ); + void testNextWordSpaces( ); + + void testRmCharAtEnd( ); + void testRmCharLast( ); + void testRmCharMiddle( ); + + void testRmWordAtEnd( ); + void testRmWordMiddle( ); + void testRmWordAtStart( ); + + void testRmRestAtStart( ); + void testRmRestMiddle( ); + void testRmRestAtEnd( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ConsoleEditTest ); + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testEmpty( ) +{ + T_ConsoleLineState s; + CPPUNIT_ASSERT( s.getContents( ) == "" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +void ConsoleEditTest::testSetContents( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 1 ); + CPPUNIT_ASSERT( s.getContents( ) == str ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , s.historyIndex( ) ); +} + +void ConsoleEditTest::testReset( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 1 ); + s.reset( ); + CPPUNIT_ASSERT( s.getContents( ) == "" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testCursorStart( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorStartEmpty( ) +{ + T_ConsoleLineState s; + s.toStart( ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorEnd( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.toEnd( ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorEndEmpty( ) +{ + T_ConsoleLineState s; + s.toEnd( ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorLeft( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.left( ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorLeftStart( ) +{ + T_ConsoleLineState s; + s.left( ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorRight( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.right( ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , s.pos( ) ); +} + +void ConsoleEditTest::testCursorRightEnd( ) +{ + T_ConsoleLineState s; + s.right( ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testAppendCharacter( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.insert( 't' ); + s.insert( 0x20ac ); + s.insert( 's' ); + s.insert( 't' ); + CPPUNIT_ASSERT( s.getContents( ) == str ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +void ConsoleEditTest::testInsertCharacter( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.insert( 0x20ac ); + s.insert( 's' ); + s.insert( 't' ); + s.toStart( ); + s.insert( 't' ); + CPPUNIT_ASSERT( s.getContents( ) == str ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 1u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +void ConsoleEditTest::testAppendBuffer( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_Buffer< uint32_t > b( 4 ); + b[ 0 ] = b[ 3 ] = 't'; + b[ 1 ] = 0x20ac; + b[ 2 ] = 's'; + + T_ConsoleLineState s; + s.insert( b , 4 ); + CPPUNIT_ASSERT( s.getContents( ) == str ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +void ConsoleEditTest::testInsertBuffer( ) +{ + const T_String str( "t\xe2\x82\xacst" ); + T_Buffer< uint32_t > b( 3 ); + b[ 0 ] = 't'; + b[ 1 ] = 0x20ac; + b[ 2 ] = 's'; + + T_ConsoleLineState s; + s.insert( 't' ); + s.toStart( ); + s.insert( b , 3 ); + + CPPUNIT_ASSERT( s.getContents( ) == str ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.historyIndex( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testPrevWordStart( ) +{ + const T_String str( "t\xe2\x82\xacst " ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.prevWord( ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); +} + +void ConsoleEditTest::testPrevWordInWord( ) +{ + const T_String str( " t\xe2\x82\xacst" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.prevWord( ); + CPPUNIT_ASSERT_EQUAL( 1u , s.pos( ) ); +} + +void ConsoleEditTest::testPrevWordSpaces( ) +{ + const T_String str( " t\xe2\x82\xacst " ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.prevWord( ); + CPPUNIT_ASSERT_EQUAL( 1u , s.pos( ) ); +} + +void ConsoleEditTest::testNextWordEnd( ) +{ + const T_String str( "t\xe2\x82\xacst " ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.nextWord( ); + CPPUNIT_ASSERT_EQUAL( s.size( ) , s.pos( ) ); +} + +void ConsoleEditTest::testNextWordInWord( ) +{ + const T_String str( "t\xe2\x82\xacst " ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.nextWord( ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); +} + +void ConsoleEditTest::testNextWordSpaces( ) +{ + const T_String str( " t\xe2\x82\xacst " ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.nextWord( ); + CPPUNIT_ASSERT_EQUAL( 6u , s.pos( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testRmCharAtEnd( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.removeCharacter( ); + CPPUNIT_ASSERT( s.getContents( ) == "test" ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); +} + +void ConsoleEditTest::testRmCharLast( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.left( ); + s.removeCharacter( ); + CPPUNIT_ASSERT( s.getContents( ) == "tes" ); + CPPUNIT_ASSERT_EQUAL( 3u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , s.size( ) ); +} + +void ConsoleEditTest::testRmCharMiddle( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + s.removeCharacter( ); + CPPUNIT_ASSERT( s.getContents( ) == "est" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 3u , s.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testRmWordAtEnd( ) +{ + const T_String str( "test blah" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeWord( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "test " ); + CPPUNIT_ASSERT_EQUAL( 5u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 5u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , cpl ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'b' ) , cpb[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'l' ) , cpb[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'a' ) , cpb[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'h' ) , cpb[ 3 ] ); +} + +void ConsoleEditTest::testRmWordMiddle( ) +{ + const T_String str( "test blah" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.left( ); + s.left( ); + s.left( ); + s.left( ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeWord( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "blah" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 5u , cpl ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 't' ) , cpb[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'e' ) , cpb[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 's' ) , cpb[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 't' ) , cpb[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( ' ' ) , cpb[ 4 ] ); +} + +void ConsoleEditTest::testRmWordAtStart( ) +{ + const T_String str( "test blah" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeWord( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "test blah" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 9u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 572u , cpl ); +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleEditTest::testRmRestAtStart( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.toStart( ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeRestOfLine( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "" ); + CPPUNIT_ASSERT_EQUAL( 0u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 0u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , cpl ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 't' ) , cpb[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 'e' ) , cpb[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 's' ) , cpb[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 't' ) , cpb[ 3 ] ); +} + +void ConsoleEditTest::testRmRestMiddle( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + s.left( ); + s.left( ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeRestOfLine( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "te" ); + CPPUNIT_ASSERT_EQUAL( 2u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 2u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 2u , cpl ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 's' ) , cpb[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 't' ) , cpb[ 1 ] ); +} + +void ConsoleEditTest::testRmRestAtEnd( ) +{ + const T_String str( "test" ); + T_ConsoleLineState s; + s.setContents( str , 0 ); + T_Buffer< uint32_t > cpb; + uint32_t cpl( 572 ); + s.removeRestOfLine( cpb , cpl ); + CPPUNIT_ASSERT( s.getContents( ) == "test" ); + CPPUNIT_ASSERT_EQUAL( 4u , s.pos( ) ); + CPPUNIT_ASSERT_EQUAL( 4u , s.size( ) ); + CPPUNIT_ASSERT_EQUAL( 572u , cpl ); +} diff --git a/tests/console-text.cc b/tests/console-text.cc new file mode 100644 index 0000000..233aabf --- /dev/null +++ b/tests/console-text.cc @@ -0,0 +1,405 @@ +#include +#include +using namespace lw; + + +class ConsoleTextTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ConsoleTextTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testSimpleString ); + + CPPUNIT_TEST( testChangeStyle ); + CPPUNIT_TEST( testChangeStyleTwice ); + CPPUNIT_TEST( testChangeStyleAtEnd ); + + CPPUNIT_TEST( testChangeColor ); + CPPUNIT_TEST( testChangeColorTwice ); + CPPUNIT_TEST( testChangeColorAtEnd ); + + CPPUNIT_TEST( testChangeColorAndStyle ); + + CPPUNIT_TEST( testTextCopyCons ); + CPPUNIT_TEST( testTextCopyAss ); + CPPUNIT_TEST( testTextMoveCons ); + CPPUNIT_TEST( testTextMoveAss ); + CPPUNIT_TEST( testTextSwap ); + + CPPUNIT_TEST( testBuilderCopyCons ); + CPPUNIT_TEST( testBuilderCopyAss ); + CPPUNIT_TEST( testBuilderMoveCons ); + CPPUNIT_TEST( testBuilderMoveAss ); + CPPUNIT_TEST( testBuilderSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testSimpleString( ); + + void testChangeStyle( ); + void testChangeStyleTwice( ); + void testChangeStyleAtEnd( ); + + void testChangeColor( ); + void testChangeColorTwice( ); + void testChangeColorAtEnd( ); + + void testChangeColorAndStyle( ); + + void testTextCopyCons( ); + void testTextCopyAss( ); + void testTextMoveCons( ); + void testTextMoveAss( ); + void testTextSwap( ); + + void testBuilderCopyCons( ); + void testBuilderCopyAss( ); + void testBuilderMoveCons( ); + void testBuilderMoveAss( ); + void testBuilderSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ConsoleTextTest ); + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testEmpty( ) +{ + T_TextBuilder tb; + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "" ); +} + +void ConsoleTextTest::testSimpleString( ) +{ + T_TextBuilder tb; + tb << "test"; + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testChangeStyle( ) +{ + T_TextBuilder tb; + tb << "te"; + tb.addStyle( E_TextStyle::BOLD ); + tb << "st"; + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + if ( i < 2 ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + } else { + CPPUNIT_ASSERT_EQUAL( uint8_t( E_TextStyle::BOLD ) , t.styleAt( i ) ); + } + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testChangeStyleTwice( ) +{ + T_TextBuilder tb; + tb << "te"; + tb.addStyle( E_TextStyle::BOLD ); + tb.addStyle( E_TextStyle::ITALIC ); + tb << "st"; + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + if ( i < 2 ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + } else { + CPPUNIT_ASSERT_EQUAL( + uint8_t( uint8_t( E_TextStyle::BOLD ) | uint8_t( E_TextStyle::ITALIC ) ) , + t.styleAt( i ) ); + } + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testChangeStyleAtEnd( ) +{ + T_TextBuilder tb; + tb << "test"; + tb.addStyle( E_TextStyle::BOLD ); + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testChangeColor( ) +{ + T_TextBuilder tb; + tb << "te"; + tb.setColor( E_TextColor::RED ); + tb << "st"; + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + if ( i < 2 ) { + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } else { + CPPUNIT_ASSERT( E_TextColor::RED == t.colorAt( i ).type ); + } + } +} + +void ConsoleTextTest::testChangeColorTwice( ) +{ + T_TextBuilder tb; + tb << "te"; + tb.setColor( E_TextColor::RED ); + tb.setColor( E_TextColor::GREEN ); + tb << "st"; + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + if ( i < 2 ) { + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } else { + CPPUNIT_ASSERT( E_TextColor::GREEN == t.colorAt( i ).type ); + } + } +} + +void ConsoleTextTest::testChangeColorAtEnd( ) +{ + T_TextBuilder tb; + tb << "test"; + tb.setColor( E_TextColor::GREEN ); + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testChangeColorAndStyle( ) +{ + T_TextBuilder tb; + tb.addStyle( E_TextStyle::BOLD ); + tb << "te"; + tb.removeStyle( E_TextStyle::BOLD ); + tb.setColor( E_TextColor::GREEN ); + tb << "st"; + + T_Text t( tb ); + CPPUNIT_ASSERT( t.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + if ( i < 2 ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( E_TextStyle::BOLD ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + } else { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::GREEN == t.colorAt( i ).type ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testTextCopyCons( ) +{ + T_TextBuilder tb; + tb << "test"; + + T_Text t( tb ); + T_Text t2( t ); + + CPPUNIT_ASSERT( t.string( ) == "test" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testTextCopyAss( ) +{ + T_TextBuilder tb; + tb << "test"; + T_Text t( tb ); + + tb.clear( ); + tb << "blargh"; + T_Text t2( tb ); + + t2 = t; + + CPPUNIT_ASSERT( t.string( ) == "test" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testTextMoveCons( ) +{ + T_TextBuilder tb; + tb << "test"; + T_Text t( tb ); + T_Text t2( std::move( t ) ); + + CPPUNIT_ASSERT( t.string( ) == "" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testTextMoveAss( ) +{ + T_TextBuilder tb; + tb << "te"; + T_Text t2( tb ); + tb << "st"; + T_Text t( tb ); + + t2 = std::move( t ); + + CPPUNIT_ASSERT( t.string( ) == "" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testTextSwap( ) +{ + T_TextBuilder tb; + tb << "test"; + T_Text t( tb ); + + tb.clear( ); + tb << E_TextStyle::BOLD << "blah"; + T_Text t2( tb ); + + swap( t , t2 ); + + CPPUNIT_ASSERT( t.string( ) == "blah" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( E_TextStyle::BOLD ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +/*----------------------------------------------------------------------------*/ + +void ConsoleTextTest::testBuilderCopyCons( ) +{ + T_TextBuilder tb; + tb << "test"; + + T_TextBuilder tb2( tb ); + T_Text t( tb ) , t2( tb2 ); + + CPPUNIT_ASSERT( t.string( ) == "test" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testBuilderCopyAss( ) +{ + T_TextBuilder tb , tb2; + tb << "test"; + tb2 << "blah"; + tb2 = tb; + T_Text t( tb ) , t2( tb2 ); + + CPPUNIT_ASSERT( t.string( ) == "test" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testBuilderMoveCons( ) +{ + T_TextBuilder tb; + tb << "test"; + + T_TextBuilder tb2( std::move( tb ) ); + T_Text t( tb ) , t2( tb2 ); + + CPPUNIT_ASSERT( t.string( ) == "" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testBuilderMoveAss( ) +{ + T_TextBuilder tb , tb2; + tb << "test"; + tb2 << "blah"; + tb2 = std::move( tb ); + + T_Text t( tb ) , t2( tb2 ); + + CPPUNIT_ASSERT( t.string( ) == "" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + +void ConsoleTextTest::testBuilderSwap( ) +{ + T_TextBuilder tb , tb2; + tb << "test"; + tb2 << E_TextStyle::BOLD << "blah"; + swap( tb , tb2 ); + + T_Text t( tb ) , t2( tb2 ); + + CPPUNIT_ASSERT( t.string( ) == "blah" ); + CPPUNIT_ASSERT( t2.string( ) == "test" ); + for ( int i = 0 ; i < 4 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( E_TextStyle::BOLD ) , t.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t.colorAt( i ).type ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0 ) , t2.styleAt( i ) ); + CPPUNIT_ASSERT( E_TextColor::WHITE == t2.colorAt( i ).type ); + } +} + diff --git a/tests/data/inputstream.txt b/tests/data/inputstream.txt new file mode 100644 index 0000000..e60b710 --- /dev/null +++ b/tests/data/inputstream.txt @@ -0,0 +1 @@ +THIS IS A TEST diff --git a/tests/hash-functions.cc b/tests/hash-functions.cc new file mode 100644 index 0000000..01cef45 --- /dev/null +++ b/tests/hash-functions.cc @@ -0,0 +1,83 @@ +#include +#include +using namespace lw; + + +/* - ComputeHashTest ---------------------------------------------------{{{-*/ +class ComputeHashTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ComputeHashTest ); + CPPUNIT_TEST( testInt8 ); + CPPUNIT_TEST( testInt16 ); + CPPUNIT_TEST( testInt32 ); + CPPUNIT_TEST( testInt64 ); + CPPUNIT_TEST( testStructDefault ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testInt8( ); + void testInt16( ); + void testInt32( ); + void testInt64( ); + void testStructDefault( ); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION( ComputeHashTest ); + +void ComputeHashTest::testInt8( ) // {{{ +{ + const uint8_t t8u = 1; + const int8_t t8su = 1 , t8ss = -1; + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t8u ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t8su ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0xff ) , ComputeHash( t8ss ) ); +} + +// }}} +void ComputeHashTest::testInt16( ) // {{{ +{ + const uint16_t t16u = 1; + const int16_t t16su = 1, t16ss = -1; + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t16u ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t16su ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0xffff ) , ComputeHash( t16ss ) ); +} + +// }}} +void ComputeHashTest::testInt32( ) // {{{ +{ + uint32_t t32u = 1; + int32_t t32su = 1, t32ss = -1; + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t32u ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , ComputeHash( t32su ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0xffffffff ) , ComputeHash( t32ss ) ); +} + +// }}} +void ComputeHashTest::testInt64( ) // {{{ +{ + uint64_t t64u = 0x100000010L; + int64_t t64s = -1; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0x11 ) , ComputeHash( t64u ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , ComputeHash( t64s ) ); +} + +// }}} +void ComputeHashTest::testStructDefault( ) // {{{ +{ + struct T_Test_ { + uint8_t meh[ 16 ]; + }; + T_Test_ test; + for ( auto i = 0 ; i < 16 ; i ++ ) { + test.meh[ i ] = i; + } + + CPPUNIT_ASSERT_EQUAL( uint32_t( HashData( test.meh , 16 ) ) , + ComputeHash( test ) ); +} + +// }}} + +/*------------------------------------------------------------------------}}}-*/ diff --git a/tests/hash-index.cc b/tests/hash-index.cc new file mode 100644 index 0000000..a845bd5 --- /dev/null +++ b/tests/hash-index.cc @@ -0,0 +1,276 @@ +#include +#include +using namespace lw; + + +class HashIndexTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( HashIndexTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testAddChain ); + CPPUNIT_TEST( testAddMultiple ); + + CPPUNIT_TEST( testRemoveSingle ); + CPPUNIT_TEST( testRemoveLast ); + CPPUNIT_TEST( testRemoveMiddle ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + + static constexpr uint32_t C_SIZE_ = 16; + + public: + void testEmpty( ); + + void testAddChain( ); + void testAddMultiple( ); + + void testRemoveSingle( ); + void testRemoveLast( ); + void testRemoveMiddle( ); + + void testClear( ); + void testFree( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( HashIndexTest ); + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testEmpty( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < C_SIZE_ * 16 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.first( k ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testAddChain( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + index.add( 0 ); + } + + auto idx( index.first( 0 ) ); + int32_t i( 7 ); + do { + CPPUNIT_ASSERT_EQUAL( uint32_t( i ) , idx ); + i --; + idx = index.next( idx ); + } while ( idx != T_HashIndex::INVALID_INDEX && i >= 0 ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , idx ); + CPPUNIT_ASSERT_EQUAL( i , -1 ); +} + +void HashIndexTest::testAddMultiple( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + index.add( j ); + } + } + + for ( uint32_t j = 0 ; j < 8 ; j ++ ) { + auto idx( index.first( j ) ); + int32_t i( 7 ); + do { + CPPUNIT_ASSERT_EQUAL( uint32_t( i * 8 ) + j , idx ); + i --; + idx = index.next( idx ); + } while ( idx != T_HashIndex::INVALID_INDEX && i >= 0 ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , idx ); + CPPUNIT_ASSERT_EQUAL( i , -1 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testRemoveSingle( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + index.add( 0 ); + index.add( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , index.first( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , index.first( 1 ) ); + index.remove( 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , index.first( 1 ) ); +} + +void HashIndexTest::testRemoveLast( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + index.add( 0 ); + } + index.add( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 7 ) , index.first( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , index.first( 1 ) ); + index.remove( 7 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 6 ) , index.first( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 7 ) , index.first( 1 ) ); +} + +void HashIndexTest::testRemoveMiddle( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t i = 0 ; i < 8 ; i ++ ) { + index.add( 0 ); + } + index.add( 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 7 ) , index.first( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , index.first( 1 ) ); + // Chain 0: 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0 -> X + // Chain 1: 8 -> X + + index.remove( 6 ); + + // Chain 0: 7 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0 -> X + // Chain 1: 6 -> X + CPPUNIT_ASSERT_EQUAL( uint32_t( 7 ) , index.first( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 5 ) , index.next( 7 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 6 ) , index.first( 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testClear( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < C_SIZE_ * 16 ; k ++ ) { + index.add( k >> 2 ); + } + index.clear( ); + for ( uint32_t k = 0 ; k < C_SIZE_ * 16 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.first( k ) ); + } +} + +void HashIndexTest::testFree( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < C_SIZE_ * 16 ; k ++ ) { + index.add( k >> 2 ); + } + index.free( ); + for ( uint32_t k = 0 ; k < C_SIZE_ * 16 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.first( k ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testCopyCons( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k ); + } + T_HashIndex copy( index ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k + 8 ); + } + + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( k , copy.first( k ) ); + CPPUNIT_ASSERT_EQUAL( k , index.first( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.next( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.next( k ) ); + CPPUNIT_ASSERT_EQUAL( k + 8 , index.first( k + 8 ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.next( k + 8 ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.first( k + 8 ) ); + } +} + +void HashIndexTest::testCopyAss( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k ); + } + T_HashIndex copy; + copy = index; + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k + 8 ); + } + + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( k , copy.first( k ) ); + CPPUNIT_ASSERT_EQUAL( k , index.first( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.next( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.next( k ) ); + CPPUNIT_ASSERT_EQUAL( k + 8 , index.first( k + 8 ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.next( k + 8 ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.first( k + 8 ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testMoveCons( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k ); + } + + T_HashIndex copy( std::move( index ) ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( k , copy.first( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.next( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.first( k ) ); + } +} + +void HashIndexTest::testMoveAss( ) +{ + T_HashIndex index( C_SIZE_ , C_SIZE_ , C_SIZE_ ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index.add( k ); + } + + T_HashIndex copy; + copy = std::move( index ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( k , copy.first( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , copy.next( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index.first( k ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void HashIndexTest::testSwap( ) +{ + T_HashIndex index1 , index2; + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + index1.add( k ); + index2.add( k + 8 ); + } + swap( index1 , index2 ); + for ( uint32_t k = 0 ; k < 8 ; k ++ ) { + CPPUNIT_ASSERT_EQUAL( k , index2.first( k ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index2.next( k ) ); + CPPUNIT_ASSERT_EQUAL( k , index1.first( k + 8 ) ); + CPPUNIT_ASSERT_EQUAL( T_HashIndex::INVALID_INDEX , index1.next( k + 8 ) ); + } +} diff --git a/tests/key-value-table.cc b/tests/key-value-table.cc new file mode 100644 index 0000000..558311b --- /dev/null +++ b/tests/key-value-table.cc @@ -0,0 +1,414 @@ +#include +#include +using namespace lw; + + +class KeyValueTableTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( KeyValueTableTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testAdd ); + CPPUNIT_TEST( testAddDuplicate ); + CPPUNIT_TEST( testUpdate ); + CPPUNIT_TEST( testUpdateMissing ); + CPPUNIT_TEST( testSetNew ); + CPPUNIT_TEST( testSetExisting ); + + CPPUNIT_TEST( testRemove ); + CPPUNIT_TEST( testRemoveMissing ); + + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testGet ); + CPPUNIT_TEST( testGetMissing ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + + static constexpr uint32_t C_SIZE_ = 16; + + public: + void testEmpty( ); + + void testAdd( ); + void testAddDuplicate( ); + void testUpdate( ); + void testUpdateMissing( ); + void testSetNew( ); + void testSetExisting( ); + + void testRemove( ); + void testRemoveMissing( ); + + void testContains( ); + void testContainsMissing( ); + void testIndexOf( ); + void testIndexOfMissing( ); + void testGet( ); + void testGetMissing( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testClear( ); + void testFree( ); + + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( KeyValueTableTest ); + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testEmpty( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testAdd( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + bool ok( table.add( 123u , 456u ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table.values( )[ 0 ] ); +} + +void KeyValueTableTest::testAddDuplicate( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.add( 123u , 456u ); + bool ok( table.add( 123u , 789u ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table.values( )[ 0 ] ); +} + +void KeyValueTableTest::testUpdate( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.add( 123u , 456u ); + bool ok( table.update( 123u , 789u ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , table.values( )[ 0 ] ); +} + +void KeyValueTableTest::testUpdateMissing( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + bool ok( table.update( 123u , 789u ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) ); +} + +void KeyValueTableTest::testSetNew( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.set( 123u , 456u ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table.values( )[ 0 ] ); +} + +void KeyValueTableTest::testSetExisting( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.add( 123u , 456u ); + table.set( 123u , 789u ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 789 ) , table.values( )[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testRemove( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.set( 123u , 456u ); + table.set( 456u , 789u ); + bool ok( table.remove( 456 ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table.values( )[ 0 ] ); +} + +void KeyValueTableTest::testRemoveMissing( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.set( 123u , 456u ); + bool ok( table.remove( 456 ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table.values( )[ 0 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testContains( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.set( 123u , 456u ); + CPPUNIT_ASSERT( table.contains( 123 ) ); +} + +void KeyValueTableTest::testContainsMissing( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + table.set( 123u , 456u ); + CPPUNIT_ASSERT( !table.contains( 456 ) ); +} + +void KeyValueTableTest::testIndexOf( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + + const uint32_t index( table.indexOf( 57 ) ); + CPPUNIT_ASSERT( index != T_HashIndex::INVALID_INDEX ); + CPPUNIT_ASSERT( index < table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table[ index ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table.values( )[ index ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , table.keys( )[ index ] ); +} + +void KeyValueTableTest::testIndexOfMissing( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + const uint32_t index( table.indexOf( 57 ) ); + CPPUNIT_ASSERT( index == T_HashIndex::INVALID_INDEX ); +} + +void KeyValueTableTest::testGet( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + + const uint32_t* index( table.get( 57 ) ); + CPPUNIT_ASSERT( index != nullptr ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , *index ); +} + +void KeyValueTableTest::testGetMissing( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + const uint32_t* index( table.get( 57 ) ); + CPPUNIT_ASSERT( index == nullptr ); +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testCopyCons( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + const auto sz( table.size( ) ); + + T_KeyValueTable< uint32_t , uint32_t > copy( table ); + + CPPUNIT_ASSERT_EQUAL( sz , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + auto const& k( table.keys( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + + const uint32_t oValue( table.values( )[ i ] ); + uint32_t const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( oValue , *cPtr ); + } +} + +void KeyValueTableTest::testCopyAss( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + const auto sz( table.size( ) ); + + T_KeyValueTable< uint32_t , uint32_t > copy; + copy = table; + + CPPUNIT_ASSERT_EQUAL( sz , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + auto const& k( table.keys( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + + const uint32_t oValue( table.values( )[ i ] ); + uint32_t const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( oValue , *cPtr ); + } +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testMoveCons( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + const auto sz( table.size( ) ); + T_Array< uint32_t > k( table.keys( ) ); + + T_KeyValueTable< uint32_t , uint32_t > copy( std::move( table ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + CPPUNIT_ASSERT( !table.contains( k[ i ] ) ); + + uint32_t const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , *cPtr ); + } +} + +void KeyValueTableTest::testMoveAss( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + const auto sz( table.size( ) ); + T_Array< uint32_t > k( table.keys( ) ); + + T_KeyValueTable< uint32_t , uint32_t > copy; + copy = std::move( table ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + CPPUNIT_ASSERT( !table.contains( k[ i ] ) ); + + uint32_t const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , *cPtr ); + } +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testClear( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + table.clear( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); +} + +void KeyValueTableTest::testFree( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( i , i * 2 ); + } + table.free( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void KeyValueTableTest::testSwap( ) +{ + T_KeyValueTable< uint32_t , uint32_t > table; + T_KeyValueTable< uint32_t , uint32_t > table2; + for ( uint32_t i = 0 ; i < 12 ; i ++ ) { + table.set( i , i * 2 ); + table2.set( 20 + i , i ); + } + table.set( 123u , 456u ); + + swap( table , table2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 12 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 13 ) , table2.size( ) ); + for ( uint32_t i = 0 ; i < 12 ; i ++ ) { + CPPUNIT_ASSERT( !table.contains( i ) ); + CPPUNIT_ASSERT( table.contains( 20 + i ) ); + CPPUNIT_ASSERT_EQUAL( i , *( table.get( 20 + i ) ) ); + + CPPUNIT_ASSERT( !table2.contains( 20 + i ) ); + CPPUNIT_ASSERT( table2.contains( i ) ); + CPPUNIT_ASSERT_EQUAL( i * 2 , *( table2.get( i ) ) ); + } + CPPUNIT_ASSERT( table2.contains( 123 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , *( table2.get( 123 ) ) ); +} diff --git a/tests/list.mk b/tests/list.mk new file mode 100644 index 0000000..285eddb --- /dev/null +++ b/tests/list.mk @@ -0,0 +1,53 @@ +TESTS = \ + alloc-pool \ + buffers \ + ptr-owned \ + ptr-shared \ + ptr-weak \ + arrays-basic \ + arrays-objects \ + arrays-static-basic \ + arrays-static-objects \ + arrays-auto \ + arrays-multi \ + optional \ + union \ + variant \ + hash-functions \ + hash-index \ + key-value-table \ + object-table \ + ring-buffer \ + strings \ + strings-iterator \ + strings-storage \ + strings-builder \ + binary-reader \ + binary-writer \ + stream-file-input \ + srd-bin-reader \ + srd-bin-writer \ + srd-lexer \ + srd-text-writer \ + srd-mem-target \ + srd-parser \ + srd-parser-defs \ + srd-parser-cfg \ + srd-preproc-core \ + srd-preproc-cmd-core \ + srd-preproc-cmd-variables \ + srd-preproc-cmd-functions \ + srd-preproc-cmd-macros \ + srd-preproc-cmd-introspect \ + srd-preproc-cmd-compare \ + srd-preproc-cmd-casts \ + srd-preproc-cmd-arithmetic \ + srd-preproc-cmd-logic \ + srd-preproc-cmd-strings \ + srd-preproc-cmd-misc \ + srd-preproc-cmd-input \ + srd-preproc-tracking \ + log-data \ + console-text \ + console-edit \ + vfs diff --git a/tests/log-data.cc b/tests/log-data.cc new file mode 100644 index 0000000..009c1f0 --- /dev/null +++ b/tests/log-data.cc @@ -0,0 +1,649 @@ +#include +#include +using namespace lw; + + +class LogLevelTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( LogLevelTest ); + CPPUNIT_TEST( testToString ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testToString( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( LogLevelTest ); + +/*----------------------------------------------------------------------------*/ + +class LogPathTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( LogPathTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testArrayCopy ); + CPPUNIT_TEST( testArrayCopyEmpty ); + CPPUNIT_TEST( testArrayCopyInvalidControl ); + CPPUNIT_TEST( testArrayCopyInvalidSlash ); + CPPUNIT_TEST( testArrayCopyInvalidEmpty ); + + CPPUNIT_TEST( testArrayMove ); + CPPUNIT_TEST( testArrayMoveEmpty ); + CPPUNIT_TEST( testArrayMoveInvalidControl ); + CPPUNIT_TEST( testArrayMoveInvalidSlash ); + CPPUNIT_TEST( testArrayMoveInvalidEmpty ); + + CPPUNIT_TEST( testInitList ); + CPPUNIT_TEST( testInitListEmpty ); + CPPUNIT_TEST( testInitListInvalidControl ); + CPPUNIT_TEST( testInitListInvalidSlash ); + CPPUNIT_TEST( testInitListInvalidEmpty ); + + CPPUNIT_TEST( testFromStringEmpty ); + CPPUNIT_TEST( testFromStringSlash ); + CPPUNIT_TEST( testFromStringSlashes ); + CPPUNIT_TEST( testFromStringValid ); + CPPUNIT_TEST( testFromStringValidSlashes ); + CPPUNIT_TEST( testFromStringValidInitialSlash ); + CPPUNIT_TEST( testFromStringValidTrailingSlash ); + CPPUNIT_TEST( testFromStringInvalid ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testParentEmpty ); + CPPUNIT_TEST( testParentOne ); + CPPUNIT_TEST( testParentValid ); + CPPUNIT_TEST( testParentInvalidChild ); + CPPUNIT_TEST( testParentInvalid ); + + CPPUNIT_TEST( testChildValid ); + CPPUNIT_TEST( testChildInvalidName ); + CPPUNIT_TEST( testChildInvalidParent ); + + CPPUNIT_TEST( testToStringEmpty ); + CPPUNIT_TEST( testToStringValid ); + CPPUNIT_TEST( testToStringInvalidSlash ); + CPPUNIT_TEST( testToStringInvalidControl ); + CPPUNIT_TEST( testToStringInvalidEmpty ); + + CPPUNIT_TEST( testIsParentYes ); + CPPUNIT_TEST( testIsParentNo ); + CPPUNIT_TEST( testIsParentYesInvalidParent ); + CPPUNIT_TEST( testIsParentYesInvalidChild ); + CPPUNIT_TEST( testIsParentNoInvalidParent ); + CPPUNIT_TEST( testIsParentNoInvalidChild ); + + CPPUNIT_TEST( testEquals ); + CPPUNIT_TEST( testNotEquals ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + + void testArrayCopy( ); + void testArrayCopyEmpty( ); + void testArrayCopyInvalidControl( ); + void testArrayCopyInvalidSlash( ); + void testArrayCopyInvalidEmpty( ); + + void testArrayMove( ); + void testArrayMoveEmpty( ); + void testArrayMoveInvalidControl( ); + void testArrayMoveInvalidSlash( ); + void testArrayMoveInvalidEmpty( ); + + void testInitList( ); + void testInitListEmpty( ); + void testInitListInvalidControl( ); + void testInitListInvalidSlash( ); + void testInitListInvalidEmpty( ); + + void testFromStringEmpty( ); + void testFromStringSlash( ); + void testFromStringSlashes( ); + void testFromStringValid( ); + void testFromStringValidSlashes( ); + void testFromStringValidInitialSlash( ); + void testFromStringValidTrailingSlash( ); + void testFromStringInvalid( ); + + void testSwap( ); + + void testParentEmpty( ); + void testParentOne( ); + void testParentValid( ); + void testParentInvalidChild( ); + void testParentInvalid( ); + + void testChildValid( ); + void testChildInvalidName( ); + void testChildInvalidParent( ); + + void testToStringEmpty( ); + void testToStringValid( ); + void testToStringInvalidSlash( ); + void testToStringInvalidControl( ); + void testToStringInvalidEmpty( ); + + void testIsParentYes( ); + void testIsParentNo( ); + void testIsParentYesInvalidParent( ); + void testIsParentYesInvalidChild( ); + void testIsParentNoInvalidParent( ); + void testIsParentNoInvalidChild( ); + + void testEquals( ); + void testNotEquals( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( LogPathTest ); + +/*----------------------------------------------------------------------------*/ + +void LogLevelTest::testToString( ) +{ + T_StringBuilder sb; + sb << E_LogLevel::TRACE << ' ' << E_LogLevel::DEBUG << ' ' + << E_LogLevel::INFO << ' ' << E_LogLevel::NOTICE << ' ' + << E_LogLevel::WARNING << ' ' << E_LogLevel::ERROR << ' ' + << E_LogLevel::CRITICAL; + + T_String s( std::move( sb ) ); + CPPUNIT_ASSERT( s == "trace debug info notice warning error critical" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testEmpty( ) +{ + T_LogPath lp; + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testArrayCopy( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c" ) ); + + T_LogPath lp( test ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testArrayCopyEmpty( ) +{ + T_Array< T_String > test; + T_LogPath lp( test ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testArrayCopyInvalidControl( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c\n" ) ); + + T_LogPath lp( test ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c\n" ); +} + +void LogPathTest::testArrayCopyInvalidSlash( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c/" ) ); + + T_LogPath lp( test ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c/" ); +} + +void LogPathTest::testArrayCopyInvalidEmpty( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "" ) ); + + T_LogPath lp( test ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testArrayMove( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c" ) ); + + RPC_String s0( &test[ 0 ] ); + RPC_String s1( &test[ 1 ] ); + RPC_String s2( &test[ 2 ] ); + + T_LogPath lp( std::move( test ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); + CPPUNIT_ASSERT_EQUAL( s0 , &lp[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( s1 , &lp[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( s2 , &lp[ 2 ] ); +} + +void LogPathTest::testArrayMoveEmpty( ) +{ + T_Array< T_String > test; + T_LogPath lp( std::move( test ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testArrayMoveInvalidControl( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c\n" ) ); + + T_LogPath lp( std::move( test ) ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c\n" ); +} + +void LogPathTest::testArrayMoveInvalidSlash( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "c/" ) ); + + T_LogPath lp( std::move( test ) ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c/" ); +} + +void LogPathTest::testArrayMoveInvalidEmpty( ) +{ + T_Array< T_String > test; + test.add( T_String::Pooled( "a" ) ); + test.add( T_String::Pooled( "b" ) ); + test.add( T_String::Pooled( "" ) ); + + T_LogPath lp( std::move( test ) ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testInitList( ) +{ + T_LogPath lp{ "a" , "b" , "c" }; + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testInitListEmpty( ) +{ + T_LogPath lp{ }; + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testInitListInvalidControl( ) +{ + T_LogPath lp{ "a" , "b" , "c\n" }; + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c\n" ); +} + +void LogPathTest::testInitListInvalidSlash( ) +{ + T_LogPath lp{ "a" , "b" , "c/" }; + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c/" ); +} + +void LogPathTest::testInitListInvalidEmpty( ) +{ + T_LogPath lp{ "a" , "b" , "" }; + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testFromStringEmpty( ) +{ + T_LogPath lp( T_LogPath::FromString( "" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testFromStringSlash( ) +{ + T_LogPath lp( T_LogPath::FromString( "/" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testFromStringSlashes( ) +{ + T_LogPath lp( T_LogPath::FromString( "/////" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , lp.size( ) ); +} + +void LogPathTest::testFromStringValid( ) +{ + T_LogPath lp( T_LogPath::FromString( "a/b/c" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testFromStringValidSlashes( ) +{ + T_LogPath lp( T_LogPath::FromString( "a//b////c" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testFromStringValidInitialSlash( ) +{ + T_LogPath lp( T_LogPath::FromString( "/a/b/c" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testFromStringValidTrailingSlash( ) +{ + T_LogPath lp( T_LogPath::FromString( "a/b/c/" ) ); + CPPUNIT_ASSERT( lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +void LogPathTest::testFromStringInvalid( ) +{ + T_LogPath lp( T_LogPath::FromString( "a/b\n/c" ) ); + CPPUNIT_ASSERT( !lp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , lp.size( ) ); + CPPUNIT_ASSERT( lp[ 0 ] == "a" ); + CPPUNIT_ASSERT( lp[ 1 ] == "b\n" ); + CPPUNIT_ASSERT( lp[ 2 ] == "c" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testSwap( ) +{ + T_LogPath lp1{ "a" }; + T_LogPath lp2{ "b" , "c" }; + + RPC_String p[] = { + &lp1[ 0 ] , &lp2[ 0 ] , &lp2[ 1 ] + }; + + swap( lp1 , lp2 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , lp1.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , lp2.size( ) ); + CPPUNIT_ASSERT_EQUAL( p[ 0 ] , &lp2[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( p[ 1 ] , &lp1[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( p[ 2 ] , &lp1[ 1 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testParentEmpty( ) +{ + T_LogPath lp; + T_LogPath plp( lp.parent( ) ); + CPPUNIT_ASSERT( plp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , plp.size( ) ); +} + +void LogPathTest::testParentOne( ) +{ + T_LogPath lp{ "a" }; + T_LogPath plp( lp.parent( ) ); + CPPUNIT_ASSERT( plp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , plp.size( ) ); +} + +void LogPathTest::testParentValid( ) +{ + T_LogPath lp{ "a" , "b" }; + T_LogPath plp( lp.parent( ) ); + CPPUNIT_ASSERT( plp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , plp.size( ) ); + CPPUNIT_ASSERT( plp[ 0 ] == "a" ); +} + +void LogPathTest::testParentInvalidChild( ) +{ + T_LogPath lp{ "a" , "b/" }; + T_LogPath plp( lp.parent( ) ); + CPPUNIT_ASSERT( plp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , plp.size( ) ); + CPPUNIT_ASSERT( plp[ 0 ] == "a" ); +} + +void LogPathTest::testParentInvalid( ) +{ + T_LogPath lp{ "a/" , "b" }; + T_LogPath plp( lp.parent( ) ); + CPPUNIT_ASSERT( !plp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , plp.size( ) ); + CPPUNIT_ASSERT( plp[ 0 ] == "a/" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testChildValid( ) +{ + T_LogPath lp{ "a" }; + T_LogPath clp( lp.child( "b" ) ); + + CPPUNIT_ASSERT( clp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , clp.size( ) ); + CPPUNIT_ASSERT( clp[ 0 ] == "a" ); + CPPUNIT_ASSERT( clp[ 1 ] == "b" ); +} + +void LogPathTest::testChildInvalidName( ) +{ + T_LogPath lp{ "a" }; + T_LogPath clp( lp.child( "b\n" ) ); + + CPPUNIT_ASSERT( !clp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , clp.size( ) ); + CPPUNIT_ASSERT( clp[ 0 ] == "a" ); + CPPUNIT_ASSERT( clp[ 1 ] == "b\n" ); +} + +void LogPathTest::testChildInvalidParent( ) +{ + T_LogPath lp{ "a\n" }; + T_LogPath clp( lp.child( "b" ) ); + + CPPUNIT_ASSERT( !clp.isValid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , clp.size( ) ); + CPPUNIT_ASSERT( clp[ 0 ] == "a\n" ); + CPPUNIT_ASSERT( clp[ 1 ] == "b" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testToStringEmpty( ) +{ + T_LogPath lp; + CPPUNIT_ASSERT( lp.toString( ) == "/" ); +} + +void LogPathTest::testToStringValid( ) +{ + T_LogPath lp{ "a" , "b" , "c" }; + CPPUNIT_ASSERT( lp.toString( ) == "/a/b/c" ); +} + +void LogPathTest::testToStringInvalidSlash( ) +{ + T_LogPath lp{ "a" , "/b" , "c" }; + CPPUNIT_ASSERT( lp.toString( ) == "/a//b/c" ); +} + +void LogPathTest::testToStringInvalidControl( ) +{ + T_LogPath lp{ "a" , "b\n" , "c" }; + CPPUNIT_ASSERT( lp.toString( ) == "/a/b\n/c" ); +} + +void LogPathTest::testToStringInvalidEmpty( ) +{ + T_LogPath lp{ "a" , "" , "c" }; + CPPUNIT_ASSERT( lp.toString( ) == "/a//c" ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testIsParentYes( ) +{ + T_LogPath gc{ "a" , "b" }; + T_LogPath c{ "a" }; + T_LogPath p; + + CPPUNIT_ASSERT( p.isParentOf( c ) ); + CPPUNIT_ASSERT( c.isParentOf( gc ) ); + + CPPUNIT_ASSERT( c.isChildOf( p ) ); + CPPUNIT_ASSERT( gc.isChildOf( c ) ); +} + +void LogPathTest::testIsParentNo( ) +{ + T_LogPath p1{ "a" , "b" , "c" }; + T_LogPath p2{ "a" , "b" , "d" , "e" }; + CPPUNIT_ASSERT( !p1.isParentOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isParentOf( p1 ) ); + CPPUNIT_ASSERT( !p1.isChildOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isChildOf( p1 ) ); +} + +void LogPathTest::testIsParentYesInvalidParent( ) +{ + T_LogPath p1{ "a\n" }; + T_LogPath p2{ "a\n" , "b" }; + CPPUNIT_ASSERT( p1.isParentOf( p2 ) ); + CPPUNIT_ASSERT( p2.isChildOf( p1 ) ); +} + +void LogPathTest::testIsParentYesInvalidChild( ) +{ + T_LogPath p1{ "a" }; + T_LogPath p2{ "a" , "b\n" }; + CPPUNIT_ASSERT( p1.isParentOf( p2 ) ); + CPPUNIT_ASSERT( p2.isChildOf( p1 ) ); +} + +void LogPathTest::testIsParentNoInvalidParent( ) +{ + T_LogPath p1{ "b\n" }; + T_LogPath p2{ "c" , "b" }; + CPPUNIT_ASSERT( !p1.isParentOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isParentOf( p1 ) ); + CPPUNIT_ASSERT( !p1.isChildOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isChildOf( p1 ) ); +} + +void LogPathTest::testIsParentNoInvalidChild( ) +{ + T_LogPath p1{ "b" }; + T_LogPath p2{ "c" , "b\n" }; + CPPUNIT_ASSERT( !p1.isParentOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isParentOf( p1 ) ); + CPPUNIT_ASSERT( !p1.isChildOf( p2 ) ); + CPPUNIT_ASSERT( !p2.isChildOf( p1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void LogPathTest::testEquals( ) +{ + T_LogPath p1{ "a" }; + T_LogPath p2( p1 ); + T_LogPath p3{ "b" , "c" }; + T_LogPath p4{ "d" }; + CPPUNIT_ASSERT( p1 == p1 ); + CPPUNIT_ASSERT( p1 == p2 ); + CPPUNIT_ASSERT( !( p1 == p3 ) ); + CPPUNIT_ASSERT( !( p1 == p4 ) ); +} + +void LogPathTest::testNotEquals( ) +{ + T_LogPath p1{ "a" }; + T_LogPath p2( p1 ); + T_LogPath p3{ "b" , "c" }; + T_LogPath p4{ "d" }; + CPPUNIT_ASSERT( !( p1 != p1 ) ); + CPPUNIT_ASSERT( !( p1 != p2 ) ); + CPPUNIT_ASSERT( p1 != p3 ); + CPPUNIT_ASSERT( p1 != p4 ); +} + diff --git a/tests/object-table.cc b/tests/object-table.cc new file mode 100644 index 0000000..92fccbb --- /dev/null +++ b/tests/object-table.cc @@ -0,0 +1,517 @@ +#include +#include +using namespace lw; + + +class ObjectTableTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( ObjectTableTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testAdd ); + CPPUNIT_TEST( testAddMove ); + CPPUNIT_TEST( testAddDuplicate ); + CPPUNIT_TEST( testUpdate ); + CPPUNIT_TEST( testUpdateMove ); + CPPUNIT_TEST( testUpdateMissing ); + CPPUNIT_TEST( testSetNew ); + CPPUNIT_TEST( testSetExisting ); + CPPUNIT_TEST( testSetMove ); + + CPPUNIT_TEST( testRemove ); + CPPUNIT_TEST( testRemoveMissing ); + + CPPUNIT_TEST( testContains ); + CPPUNIT_TEST( testContainsMissing ); + CPPUNIT_TEST( testIndexOf ); + CPPUNIT_TEST( testIndexOfMissing ); + CPPUNIT_TEST( testGet ); + CPPUNIT_TEST( testGetMissing ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + + static constexpr uint32_t C_SIZE_ = 16; + + public: + void testEmpty( ); + + void testAdd( ); + void testAddMove( ); + void testAddDuplicate( ); + void testUpdate( ); + void testUpdateMove( ); + void testUpdateMissing( ); + void testSetNew( ); + void testSetMove( ); + void testSetExisting( ); + + void testRemove( ); + void testRemoveMissing( ); + + void testContains( ); + void testContainsMissing( ); + void testIndexOf( ); + void testIndexOfMissing( ); + void testGet( ); + void testGetMissing( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testClear( ); + void testFree( ); + + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( ObjectTableTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +class T_Test_ +{ + private: + uint32_t key_; + uint32_t value_; + + public: + T_Test_( ) : key_( 0 ) , value_( 0 ) { } + T_Test_( uint32_t k ) : key_( k ) , value_( k * 2 ) { } + T_Test_( uint32_t k , uint32_t v ) : key_( k ) , value_( v ) { } + + T_Test_( T_Test_ const& other ) : T_Test_( other.key_ , other.value_ ) { } + T_Test_( T_Test_&& other ) : T_Test_( ) { swap( *this , other ); } + + T_Test_& operator =( T_Test_ const& other ) = default; + T_Test_& operator =( T_Test_&& other ) noexcept { swap( *this , other ); return *this; } + + friend void swap( T_Test_& lhs , T_Test_& rhs ) + { + using std::swap; + swap( lhs.key_ , rhs.key_ ); + swap( lhs.value_ , rhs.value_ ); + } + + uint32_t key( ) const { return key_; } + uint32_t value( ) const { return value_; } + void value( uint32_t v ) { value_ = v; } +}; + +uint32_t KeyGetter_( T_Test_ const& test ) +{ + return test.key( ); +} + +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testEmpty( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testAdd( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + T_Test_ test( 123 ); + bool ok( table.add( test ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testAddMove( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + T_Test_ test( 123 ); + bool ok( table.add( std::move( test ) ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.key( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testAddDuplicate( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.add( T_Test_( 123 ) ); + bool ok( table.add( T_Test_( 123 , 456 ) ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testUpdate( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.add( T_Test_( 123 , 456 ) ); + T_Test_ test( 123 ); + bool ok( table.update( test ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testUpdateMove( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.add( T_Test_( 123 , 456 ) ); + T_Test_ test( 123 ); + bool ok( table.update( std::move( test ) ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , test.value( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testUpdateMissing( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + bool ok( table.update( T_Test_( 123 ) ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.values( ).size( ) ); +} + +void ObjectTableTest::testSetNew( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + T_Test_ test( 123 ); + table.set( test ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testSetMove( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + T_Test_ test( 123 ); + table.set( std::move( test ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test.key( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testSetExisting( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.add( T_Test_( 123 , 456 ) ); + table.set( T_Test_( 123 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testRemove( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.set( T_Test_( 123 ) ); + table.set( T_Test_( 456 ) ); + bool ok( table.remove( 456 ) ); + CPPUNIT_ASSERT( ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +void ObjectTableTest::testRemoveMissing( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.set( T_Test_( 123 ) ); + bool ok( table.remove( 456 ) ); + CPPUNIT_ASSERT( !ok ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.keys( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , table.values( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 123 ) , table.keys( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 246 ) , table.values( )[ 0 ].value( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testContains( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.set( T_Test_( 123 ) ); + CPPUNIT_ASSERT( table.contains( 123 ) ); +} + +void ObjectTableTest::testContainsMissing( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + table.set( T_Test_( 123 ) ); + CPPUNIT_ASSERT( !table.contains( 456 ) ); +} + +void ObjectTableTest::testIndexOf( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + + const uint32_t index( table.indexOf( 57 ) ); + CPPUNIT_ASSERT( index != T_HashIndex::INVALID_INDEX ); + CPPUNIT_ASSERT( index < table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , table[ index ].key( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table[ index ].value( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , table.values( )[ index ].value( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , table.keys( )[ index ] ); +} + +void ObjectTableTest::testIndexOfMissing( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + const uint32_t index( table.indexOf( 57 ) ); + CPPUNIT_ASSERT( index == T_HashIndex::INVALID_INDEX ); +} + +void ObjectTableTest::testGet( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + + const T_Test_* ptr( table.get( 57 ) ); + CPPUNIT_ASSERT( ptr != nullptr ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 57 ) , ptr->key( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 114 ) , ptr->value( ) ); +} + +void ObjectTableTest::testGetMissing( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + const T_Test_* ptr( table.get( 57 ) ); + CPPUNIT_ASSERT( ptr == nullptr ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testCopyCons( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + const auto sz( table.size( ) ); + + T_ObjectTable< uint32_t , T_Test_ > copy( table ); + + CPPUNIT_ASSERT_EQUAL( sz , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + auto const& k( table.keys( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + + T_Test_ const& oValue( table.values( )[ i ] ); + T_Test_ const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( oValue.key( ) , cPtr->key( ) ); + CPPUNIT_ASSERT_EQUAL( oValue.value( ) , cPtr->value( ) ); + } +} + +void ObjectTableTest::testCopyAss( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + const auto sz( table.size( ) ); + + T_ObjectTable< uint32_t , T_Test_ > copy( []( T_Test_ const& v ) { + return v.value( ); + } ); + copy = table; + + CPPUNIT_ASSERT_EQUAL( sz , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + auto const& k( table.keys( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + + T_Test_ const& oValue( table.values( )[ i ] ); + T_Test_ const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( oValue.key( ) , cPtr->key( ) ); + CPPUNIT_ASSERT_EQUAL( oValue.value( ) , cPtr->value( ) ); + } + + T_Test_ v( 543 , 210 ); + copy.set( v ); + CPPUNIT_ASSERT( copy.contains( 543 ) ); + CPPUNIT_ASSERT( !table.contains( 543 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testMoveCons( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + const auto sz( table.size( ) ); + T_Array< uint32_t > k( table.keys( ) ); + + T_ObjectTable< uint32_t , T_Test_ > copy( std::move( table ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + CPPUNIT_ASSERT( !table.contains( k[ i ] ) ); + + T_Test_ const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( k[ i ] , cPtr->key( ) ); + CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , cPtr->value( ) ); + } +} + +void ObjectTableTest::testMoveAss( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + for ( uint32_t i = 1 ; i < 124 ; i += 2 ) { + table.set( T_Test_( i ) ); + } + const auto sz( table.size( ) ); + T_Array< uint32_t > k( table.keys( ) ); + + T_ObjectTable< uint32_t , T_Test_ > copy( []( T_Test_ const& v ) { + return v.value( ); + } ); + copy = std::move( table ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , copy.size( ) ); + for ( uint32_t i = 0 ; i < sz ; i ++ ) { + CPPUNIT_ASSERT( copy.contains( k[ i ] ) ); + CPPUNIT_ASSERT( !table.contains( k[ i ] ) ); + + T_Test_ const* const cPtr( copy.get( k[ i ] ) ); + CPPUNIT_ASSERT( cPtr != nullptr ); + CPPUNIT_ASSERT_EQUAL( k[ i ] , cPtr->key( ) ); + CPPUNIT_ASSERT_EQUAL( k[ i ] * 2 , cPtr->value( ) ); + } + + copy.add( T_Test_( 456 , 789 ) ); + CPPUNIT_ASSERT( copy.contains( 456 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testClear( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i ++ ) { + table.set( T_Test_( i ) ); + } + table.clear( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); +} + +void ObjectTableTest::testFree( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 124 ; i ++ ) { + table.set( T_Test_( i ) ); + } + table.free( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , table.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void ObjectTableTest::testSwap( ) +{ + T_ObjectTable< uint32_t , T_Test_ > table( KeyGetter_ ); + T_ObjectTable< uint32_t , T_Test_ > table2( KeyGetter_ ); + for ( uint32_t i = 0 ; i < 12 ; i ++ ) { + table.set( T_Test_( i , i * 2 ) ); + table2.set( T_Test_( 20 + i , i ) ); + } + table.set( T_Test_( 123 , 456 ) ); + + swap( table , table2 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 12 ) , table.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 13 ) , table2.size( ) ); + for ( uint32_t i = 0 ; i < 12 ; i ++ ) { + CPPUNIT_ASSERT( !table.contains( i ) ); + CPPUNIT_ASSERT( table.contains( 20 + i ) ); + CPPUNIT_ASSERT_EQUAL( i , table.get( 20 + i )->value( ) ); + + CPPUNIT_ASSERT( !table2.contains( 20 + i ) ); + CPPUNIT_ASSERT( table2.contains( i ) ); + CPPUNIT_ASSERT_EQUAL( i * 2 , table2.get( i )->value( ) ); + } + CPPUNIT_ASSERT( table2.contains( 123 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 456 ) , table2.get( 123 )->value( ) ); +} diff --git a/tests/optional.cc b/tests/optional.cc new file mode 100644 index 0000000..49a1dd8 --- /dev/null +++ b/tests/optional.cc @@ -0,0 +1,287 @@ +#include +#include +using namespace lw; + + +class OptionalTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( OptionalTest ); + CPPUNIT_TEST( testDefaultCons ); + CPPUNIT_TEST( testConstructCons ); + CPPUNIT_TEST( testDestruct ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwapEE ); + CPPUNIT_TEST( testSwapVV ); + CPPUNIT_TEST( testSwapVE ); + + CPPUNIT_TEST( testValueCopyCons ); + CPPUNIT_TEST( testValueCopyAss ); + CPPUNIT_TEST( testValueMoveCons ); + CPPUNIT_TEST( testValueMoveAss ); + + CPPUNIT_TEST( testSetNew ); + CPPUNIT_TEST( testClear ); + + CPPUNIT_TEST( testCast ); + CPPUNIT_TEST( testCastException ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testDefaultCons( ); + void testConstructCons( ); + void testDestruct( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + + void testSwapEE( ); + void testSwapVV( ); + void testSwapVE( ); + + void testValueCopyCons( ); + void testValueCopyAss( ); + void testValueMoveCons( ); + void testValueMoveAss( ); + + void testSetNew( ); + void testClear( ); + + void testCast( ); + void testCastException( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( OptionalTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +int consCounter = 0; +int delCounter = 0; +int mvCounter = 0; +int cpCounter = 0; + +struct T_Counter_ +{ + int value; + + T_Counter_( ) noexcept : value( 0 ) { consCounter ++; } + explicit T_Counter_( int x ) noexcept : value( x ) { consCounter ++; } + + T_Counter_( T_Counter_ const& o ) noexcept : value( o.value ) { cpCounter ++; } + T_Counter_( T_Counter_&& o ) noexcept : value( o.value ) { mvCounter ++; } + + ~T_Counter_( ) { delCounter ++; } +}; + +#define M_COUNTS_(C,D,CP,M) \ + do { \ + CPPUNIT_ASSERT_EQUAL( int( C ) , consCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( D ) , delCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( CP ) , cpCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( M ) , mvCounter ); \ + } while ( 0 ) + +#define M_EMPTY_( O ) \ + do { \ + CPPUNIT_ASSERT( ! ( O ).present( ) ); \ + CPPUNIT_ASSERT( ( O ).target( ) == nullptr ); \ + } while ( 0 ) + +#define M_VALUE_( O , V ) \ + do { \ + CPPUNIT_ASSERT( ( O ).present( ) ); \ + CPPUNIT_ASSERT( ( O ).target( ) != nullptr ); \ + CPPUNIT_ASSERT_EQUAL( int( V ) , ( O ).target( )->value ); \ + } while ( 0 ) + +} // namespace + +void OptionalTest::tearDown( ) +{ + consCounter = delCounter = mvCounter = cpCounter = 0; +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testDefaultCons( ) +{ + const T_Optional< T_Counter_ > o; + M_EMPTY_( o ); + M_COUNTS_( 0 , 0 , 0 , 0 ); +} + +void OptionalTest::testConstructCons( ) +{ + const T_Optional< T_Counter_ > o{ Construct< T_Counter_ >( ) , 12 }; + M_VALUE_( o , 12 ); + M_COUNTS_( 1 , 0 , 0 , 0 ); +} + +void OptionalTest::testDestruct( ) +{ + { + T_Optional< T_Counter_ > o{ Construct< T_Counter_ >( ) }; + } + M_COUNTS_( 1 , 1 , 0 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testCopyCons( ) +{ + const T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + const T_Optional< T_Counter_ > d( s ); + M_COUNTS_( 1 , 0 , 1 , 0 ); + M_VALUE_( s , 12 ); + M_VALUE_( d , 12 ); +} + +void OptionalTest::testCopyAss( ) +{ + const T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + const T_Optional< T_Counter_ > empty; + T_Optional< T_Counter_ > d; + d = s; + M_COUNTS_( 1 , 0 , 1 , 0 ); + M_VALUE_( s , 12 ); + M_VALUE_( d , 12 ); + d = empty; + M_COUNTS_( 1 , 1 , 1 , 0 ); + M_EMPTY_( d ); +} + +void OptionalTest::testMoveCons( ) +{ + T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + const T_Optional< T_Counter_ > d( std::move( s ) ); + M_COUNTS_( 1 , 1 , 0 , 1 ); + M_VALUE_( d , 12 ); + M_EMPTY_( s ); +} + +void OptionalTest::testMoveAss( ) +{ + T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + T_Optional< T_Counter_ > empty; + T_Optional< T_Counter_ > d; + d = std::move( s ); + M_COUNTS_( 1 , 1 , 0 , 1 ); + M_EMPTY_( s ); + M_VALUE_( d , 12 ); + d = std::move( empty ); + M_COUNTS_( 1 , 2 , 0 , 1 ); + M_EMPTY_( d ); +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testSwapEE( ) +{ + T_Optional< T_Counter_ > o1 , o2; + swap( o1 , o2 ); + M_COUNTS_( 0 , 0 , 0 , 0 ); + M_EMPTY_( o1 ); + M_EMPTY_( o2 ); +} + +void OptionalTest::testSwapVV( ) +{ + T_Optional< T_Counter_ > o1{ Construct< T_Counter_ >( ) , 1 } , + o2{ Construct< T_Counter_ >( ) , 2 }; + swap( o1 , o2 ); + M_COUNTS_( 2 , 3 , 0 , 3 ); + M_VALUE_( o1 , 2 ); + M_VALUE_( o2 , 1 ); +} + +void OptionalTest::testSwapVE( ) +{ + T_Optional< T_Counter_ > o1{ Construct< T_Counter_ >( ) , 1 } , o2; + swap( o1 , o2 ); + M_COUNTS_( 1 , 1 , 0 , 1 ); + M_EMPTY_( o1 ); + M_VALUE_( o2 , 1 ); +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testValueCopyCons( ) +{ + const T_Counter_ c( 12 ); + T_Optional< T_Counter_ > o( c ); + M_COUNTS_( 1 , 0 , 1 , 0 ); + M_VALUE_( o , 12 ); +} + +void OptionalTest::testValueCopyAss( ) +{ + const T_Counter_ c( 12 ); + T_Optional< T_Counter_ > o; + o = c; + M_COUNTS_( 1 , 0 , 1 , 0 ); + M_VALUE_( o , 12 ); +} + +void OptionalTest::testValueMoveCons( ) +{ + T_Counter_ c( 12 ); + T_Optional< T_Counter_ > o( std::move( c ) ); + M_COUNTS_( 1 , 0 , 0 , 1 ); + M_VALUE_( o , 12 ); +} + +void OptionalTest::testValueMoveAss( ) +{ + T_Counter_ c( 12 ); + T_Optional< T_Counter_ > o; + o = std::move( c ); + M_COUNTS_( 1 , 0 , 0 , 1 ); + M_VALUE_( o , 12 ); +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testSetNew( ) +{ + T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + s.setNew( 34 ); + M_COUNTS_( 2 , 1 , 0 , 0 ); + M_VALUE_( s , 34 ); +} + +void OptionalTest::testClear( ) +{ + T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + s.clear( ); + M_COUNTS_( 1 , 1 , 0 , 0 ); + M_EMPTY_( s ); +} + +/*----------------------------------------------------------------------------*/ + +void OptionalTest::testCast( ) +{ + const T_Optional< T_Counter_ > s{ Construct< T_Counter_ >( ) , 12 }; + T_Counter_ cnt( s ); + CPPUNIT_ASSERT_EQUAL( 12 , cnt.value ); +} + +void OptionalTest::testCastException( ) +{ + const T_Optional< T_Counter_ > s; + try { + T_Counter_ cnt( s ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} diff --git a/tests/ptr-common.hh b/tests/ptr-common.hh new file mode 100644 index 0000000..1488fec --- /dev/null +++ b/tests/ptr-common.hh @@ -0,0 +1,28 @@ +// Common code for pointer tests + +namespace { + +struct T_Test +{ + static int dCalled; + int field; + + T_Test( ) : T_Test( -1 ) { } + T_Test( int f ) : field( f ) { } + virtual ~T_Test( ) { dCalled ++; } +}; + +int T_Test::dCalled = 0; + +struct T_TestChild : public T_Test +{ + T_TestChild( ) : T_Test( 256 ) { } +}; + +struct T_OtherChild : T_Test +{ + int otherField; + T_OtherChild( int x , int y ) : T_Test( x ) , otherField( y ) { } +}; + +} diff --git a/tests/ptr-owned.cc b/tests/ptr-owned.cc new file mode 100644 index 0000000..43c55b7 --- /dev/null +++ b/tests/ptr-owned.cc @@ -0,0 +1,279 @@ +#include +#include +using namespace lw; + + +class OwnPointerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( OwnPointerTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testNewOwned ); + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testMakeShared ); + + CPPUNIT_TEST( testOwnRaw ); + CPPUNIT_TEST( testOwnRawC2P ); + CPPUNIT_TEST( testOwnRawP2C ); + CPPUNIT_TEST( testOwnRawBad ); + + CPPUNIT_TEST( testMoveConsCastC2P ); + CPPUNIT_TEST( testMoveConsCastP2C ); + CPPUNIT_TEST( testMoveConsCastBad ); + + CPPUNIT_TEST( testMoveAssCastC2P ); + CPPUNIT_TEST( testMoveAssCastP2C ); + CPPUNIT_TEST( testMoveAssCastBad ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ); + + void testEmpty( ); + void testNewOwned( ); + void testClear( ); + void testSwap( ); + void testMoveCons( ); + void testMoveAss( ); + void testMakeShared( ); + + void testOwnRaw( ); + void testOwnRawC2P( ); + void testOwnRawP2C( ); + void testOwnRawBad( ); + + void testMoveConsCastC2P( ); + void testMoveConsCastP2C( ); + void testMoveConsCastBad( ); + + void testMoveAssCastC2P( ); + void testMoveAssCastP2C( ); + void testMoveAssCastBad( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( OwnPointerTest ); + +/*----------------------------------------------------------------------------*/ + +#include "ptr-common.hh" + +using Ptr = T_OwnPtr< T_Test >; + +void OwnPointerTest::tearDown( ) +{ + T_Test::dCalled = 0; +} + +/*----------------------------------------------------------------------------*/ + +void OwnPointerTest::testEmpty( ) +{ + { + Ptr p; + CPPUNIT_ASSERT( !p ); + CPPUNIT_ASSERT( p.get( ) == nullptr ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); +} + +void OwnPointerTest::testNewOwned( ) +{ + { + Ptr p( NewOwned< T_Test >( ) ); + CPPUNIT_ASSERT( bool( p ) ); + CPPUNIT_ASSERT( p.get( ) != nullptr ); + CPPUNIT_ASSERT( p->field == -1 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testClear( ) +{ + Ptr p( NewOwned< T_Test >( ) ); + p.clear( ); + CPPUNIT_ASSERT( !p ); + CPPUNIT_ASSERT( p.get( ) == nullptr ); + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testSwap( ) +{ + { + Ptr p1( NewOwned< T_Test >( 123 ) ); + Ptr p2( NewOwned< T_Test >( 456 ) ); + swap( p1 , p2 ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( bool( p1 ) ); + CPPUNIT_ASSERT( p1->field == 456 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 123 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 2 ); +} + +void OwnPointerTest::testMoveCons( ) +{ + { + Ptr p1( NewOwned< T_Test >( 123 ) ); + Ptr p2( std::move( p1 ) ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 123 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMoveAss( ) +{ + { + Ptr p1( NewOwned< T_Test >( 123 ) ); + Ptr p2; + p2 = std::move( p1 ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 123 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMakeShared( ) +{ + Ptr op( NewOwned< T_Test >( 123 ) ); + auto sp( op.makeShared( ) ); + CPPUNIT_ASSERT( !op ); + CPPUNIT_ASSERT( bool( sp ) ); + CPPUNIT_ASSERT( sp->field == 123 ); +} + +/*----------------------------------------------------------------------------*/ + +void OwnPointerTest::testOwnRaw( ) +{ + { + T_Test* obj( new T_Test( 123 ) ); + Ptr p( OwnRawPointer( obj ) ); + CPPUNIT_ASSERT( obj == nullptr ); + CPPUNIT_ASSERT( bool( p ) ); + CPPUNIT_ASSERT( p.get( ) != nullptr ); + CPPUNIT_ASSERT( p->field == 123 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testOwnRawC2P( ) +{ + { + T_TestChild* obj( new T_TestChild( ) ); + Ptr p( OwnRawPointer( obj ) ); + CPPUNIT_ASSERT( obj == nullptr ); + CPPUNIT_ASSERT( bool( p ) ); + CPPUNIT_ASSERT( p.get( ) != nullptr ); + CPPUNIT_ASSERT( p->field == 256 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testOwnRawP2C( ) +{ + T_Test* obj( new T_TestChild( ) ); + T_OwnPtr< T_TestChild > p( OwnRawPointer< T_TestChild , T_Test >( obj ) ); + CPPUNIT_ASSERT( obj == nullptr ); + CPPUNIT_ASSERT( bool( p ) ); +} + +void OwnPointerTest::testOwnRawBad( ) +{ + T_Test* obj( new T_OtherChild( 123 , 456 ) ); + try { + T_OwnPtr< T_TestChild > p( OwnRawPointer< T_TestChild , T_Test >( obj ) ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT( obj != nullptr ); + delete obj; +} + +/*----------------------------------------------------------------------------*/ + +void OwnPointerTest::testMoveConsCastC2P( ) +{ + { + T_OwnPtr< T_TestChild > p1( NewOwned< T_TestChild >( ) ); + Ptr p2( std::move( p1 ) ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 256 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMoveConsCastP2C( ) +{ + { + Ptr p1( NewOwned< T_TestChild >( ) ); + T_OwnPtr< T_TestChild > p2( std::move( p1 ) ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 256 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMoveConsCastBad( ) +{ + Ptr p1( NewOwned< T_OtherChild >( 123 , 456 ) ); + try { + T_OwnPtr< T_TestChild > p2( std::move( p1 ) ); + CPPUNIT_FAIL( "exception not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} + +/*----------------------------------------------------------------------------*/ + +void OwnPointerTest::testMoveAssCastC2P( ) +{ + { + T_OwnPtr< T_TestChild > p1( NewOwned< T_TestChild >( ) ); + Ptr p2; + p2 = std::move( p1 ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 256 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMoveAssCastP2C( ) +{ + { + Ptr p1( NewOwned< T_TestChild >( ) ); + T_OwnPtr< T_TestChild > p2; + p2 = std::move( p1 ); + CPPUNIT_ASSERT( T_Test::dCalled == 0 ); + CPPUNIT_ASSERT( !p1 ); + CPPUNIT_ASSERT( bool( p2 ) ); + CPPUNIT_ASSERT( p2->field == 256 ); + } + CPPUNIT_ASSERT( T_Test::dCalled == 1 ); +} + +void OwnPointerTest::testMoveAssCastBad( ) +{ + try { + Ptr p1( NewOwned< T_OtherChild >( 123 , 456 ) ); + T_OwnPtr< T_TestChild > p2; + p2 = std::move( p1 ); + CPPUNIT_FAIL( "exception not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} diff --git a/tests/ptr-shared.cc b/tests/ptr-shared.cc new file mode 100644 index 0000000..7afa653 --- /dev/null +++ b/tests/ptr-shared.cc @@ -0,0 +1,390 @@ +#include +#include +using namespace lw; + + +class SharedPointerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SharedPointerTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testNewShared ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testClearEmpty ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveConsCastC2P ); + CPPUNIT_TEST( testMoveConsCastP2C ); + CPPUNIT_TEST( testMoveConsCastBad ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyConsCastC2P ); + CPPUNIT_TEST( testCopyConsCastP2C ); + CPPUNIT_TEST( testCopyConsCastBad ); + + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testCopyAssCastC2P ); + CPPUNIT_TEST( testCopyAssCastP2C ); + CPPUNIT_TEST( testCopyAssCastBad ); + + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testMoveAssCastC2P ); + CPPUNIT_TEST( testMoveAssCastP2C ); + CPPUNIT_TEST( testMoveAssCastBad ); + + CPPUNIT_TEST( testMakeOwnedEmpty ); + CPPUNIT_TEST( testMakeOwnedOneRef ); + CPPUNIT_TEST( testMakeOwnedManyRefs ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ); + + void testEmpty( ); + void testNewShared( ); + void testSwap( ); + void testClear( ); + void testClearEmpty( ); + + void testMoveCons( ); + void testMoveConsCastC2P( ); + void testMoveConsCastP2C( ); + void testMoveConsCastBad( ); + + void testCopyCons( ); + void testCopyConsCastC2P( ); + void testCopyConsCastP2C( ); + void testCopyConsCastBad( ); + + void testCopyAss( ); + void testCopyAssCastC2P( ); + void testCopyAssCastP2C( ); + void testCopyAssCastBad( ); + + void testMoveAss( ); + void testMoveAssCastC2P( ); + void testMoveAssCastP2C( ); + void testMoveAssCastBad( ); + + void testMakeOwnedEmpty( ); + void testMakeOwnedOneRef( ); + void testMakeOwnedManyRefs( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SharedPointerTest ); + +/*----------------------------------------------------------------------------*/ + +#include "ptr-common.hh" + +using Ptr = T_SharedPtr< T_Test >; + +void SharedPointerTest::tearDown( ) +{ + T_Test::dCalled = 0; +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testEmpty( ) +{ + { + Ptr test; + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( false , bool( test ) ); + CPPUNIT_ASSERT_EQUAL( (T_Test *) nullptr , (T_Test *) test ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void SharedPointerTest::testNewShared( ) +{ + { + Ptr test( NewShared< T_Test >( ) ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT_EQUAL( false , !test ); + CPPUNIT_ASSERT( nullptr != (T_Test *) test ); + CPPUNIT_ASSERT_EQUAL( -1 , test->field ); + CPPUNIT_ASSERT_EQUAL( -1 , (*test).field ); + } + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); +} + +void SharedPointerTest::testSwap( ) +{ + { + Ptr test1( NewShared< T_Test >( 1 ) ); + Ptr test2( NewShared< T_Test >( 2 ) ); + swap( test1 , test2 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT_EQUAL( 1 , test2->field ); + CPPUNIT_ASSERT_EQUAL( 2 , test1->field ); + } + CPPUNIT_ASSERT_EQUAL( 2 , T_Test::dCalled ); +} + +void SharedPointerTest::testClear( ) +{ + { + Ptr test( NewShared< T_Test >( ) ); + test.clear( ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + } + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); +} + +void SharedPointerTest::testClearEmpty( ) +{ + { + Ptr test; + test.clear( ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testMoveCons( ) +{ + { + Ptr test( NewShared< T_Test >( ) ); + Ptr test2( std::move( test ) ); + CPPUNIT_ASSERT( bool( test2 ) ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + } + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); +} + +void SharedPointerTest::testMoveConsCastC2P( ) +{ + T_SharedPtr< T_TestChild > source( NewShared< T_TestChild >( ) ); + Ptr test( std::move( source ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( !source ); + CPPUNIT_ASSERT( bool( test ) ); + + T_Test const& t( *test ); + CPPUNIT_ASSERT( typeid( T_TestChild ) == typeid( t ) ); +} + +void SharedPointerTest::testMoveConsCastP2C( ) +{ + Ptr source( NewShared< T_TestChild >( ) ); + T_SharedPtr< T_TestChild > test( std::move( source ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( !source ); + CPPUNIT_ASSERT( bool( test ) ); +} + +void SharedPointerTest::testMoveConsCastBad( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + try { + T_SharedPtr< T_TestChild > test( std::move( source ) ); + CPPUNIT_ASSERT( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( source ); +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testCopyCons( ) +{ + { + Ptr test( NewShared< T_Test >( ) ); + { + Ptr test2( test ); + CPPUNIT_ASSERT( test == test2 ); + CPPUNIT_ASSERT( (T_Test *) test == (T_Test *) test2 ); + } + CPPUNIT_ASSERT( bool( test ) ); + } + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); +} + +void SharedPointerTest::testCopyConsCastC2P( ) +{ + T_SharedPtr< T_TestChild > source( NewShared< T_TestChild >( ) ); + Ptr test( source ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( source ) ); + CPPUNIT_ASSERT( bool( test ) ); + + T_Test const& t( *test ); + CPPUNIT_ASSERT( typeid( T_TestChild ) == typeid( t ) ); +} + +void SharedPointerTest::testCopyConsCastP2C( ) +{ + Ptr source( NewShared< T_TestChild >( ) ); + T_SharedPtr< T_TestChild > test( source ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( source ) ); + CPPUNIT_ASSERT( bool( test ) ); +} + +void SharedPointerTest::testCopyConsCastBad( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + try { + T_SharedPtr< T_TestChild > test( source ); + CPPUNIT_ASSERT( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( source ); +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testCopyAss( ) +{ + { + Ptr test1( NewShared< T_Test >( 1 ) ); + Ptr test2( NewShared< T_Test >( 2 ) ); + CPPUNIT_ASSERT( test1 != test2 ); + + test1 = test2; + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT_EQUAL( 2 , test1->field ); + CPPUNIT_ASSERT( test1 == test2 ); + } + CPPUNIT_ASSERT_EQUAL( 2 , T_Test::dCalled ); +} + +void SharedPointerTest::testCopyAssCastC2P( ) +{ + T_SharedPtr< T_TestChild > source( NewShared< T_TestChild >( ) ); + Ptr test( NewShared< T_Test >( 123 ) ); + CPPUNIT_ASSERT( test != source ); + + test = source; + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( source ) ); + CPPUNIT_ASSERT( bool( test ) ); + + T_Test const& t( *test ); + CPPUNIT_ASSERT( typeid( T_TestChild ) == typeid( t ) ); +} + +void SharedPointerTest::testCopyAssCastP2C( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + T_SharedPtr< T_OtherChild > test( NewShared< T_OtherChild >( 789 , 12 ) ); + CPPUNIT_ASSERT( (void*)(T_OtherChild*)test != (void*)(T_Test*)source ); + + test = source; + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( source ) ); + CPPUNIT_ASSERT( bool( test ) ); +} + +void SharedPointerTest::testCopyAssCastBad( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + T_SharedPtr< T_TestChild > test; + try { + test = source; + CPPUNIT_ASSERT( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( source ); + CPPUNIT_ASSERT( !test ); +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testMoveAss( ) +{ + { + Ptr test( NewShared< T_Test >( 1 ) ); + test = NewShared< T_Test >( 2 ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT_EQUAL( 2 , test->field ); + } + CPPUNIT_ASSERT_EQUAL( 2 , T_Test::dCalled ); +} + +void SharedPointerTest::testMoveAssCastC2P( ) +{ + T_SharedPtr< T_TestChild > source( NewShared< T_TestChild >( ) ); + Ptr test( NewShared< T_Test >( 123 ) ); + CPPUNIT_ASSERT( test != source ); + + test = std::move( source ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( !source ) ); + CPPUNIT_ASSERT( bool( test ) ); + + T_Test const& t( *test ); + CPPUNIT_ASSERT( typeid( T_TestChild ) == typeid( t ) ); +} + +void SharedPointerTest::testMoveAssCastP2C( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + T_SharedPtr< T_OtherChild > test( NewShared< T_OtherChild >( 789 , 12 ) ); + CPPUNIT_ASSERT( (void*)(T_OtherChild*)test != (void*)(T_Test*)source ); + + test = std::move( source ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT( bool( !source ) ); + CPPUNIT_ASSERT( bool( test ) ); +} + +void SharedPointerTest::testMoveAssCastBad( ) +{ + Ptr source( NewShared< T_OtherChild >( 123 , 456 ) ); + T_SharedPtr< T_TestChild > test; + try { + test = std::move( source ); + CPPUNIT_ASSERT( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( source ); + CPPUNIT_ASSERT( !test ); +} + +/*----------------------------------------------------------------------------*/ + +void SharedPointerTest::testMakeOwnedEmpty( ) +{ + Ptr sp; + T_OwnPtr< T_Test > op( sp.makeOwned( ) ); + CPPUNIT_ASSERT( !op ); +} + +void SharedPointerTest::testMakeOwnedOneRef( ) +{ + Ptr sp( NewShared< T_Test >( 123 ) ); + T_OwnPtr< T_Test > op( sp.makeOwned( ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( op ); + CPPUNIT_ASSERT_EQUAL( 123 , op->field ); + CPPUNIT_ASSERT( !sp ); +} + +void SharedPointerTest::testMakeOwnedManyRefs( ) +{ + Ptr sp( NewShared< T_Test >( 123 ) ); + Ptr sp2( sp ); + try { + sp.makeOwned( ); + CPPUNIT_FAIL( "X_TooManyReferences not thrown" ); + } catch ( X_TooManyReferences const& ) { + // OK + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( sp && sp2 ); +} diff --git a/tests/ptr-weak.cc b/tests/ptr-weak.cc new file mode 100644 index 0000000..d427867 --- /dev/null +++ b/tests/ptr-weak.cc @@ -0,0 +1,486 @@ +#include +#include +using namespace lw; + + +class WeakPointerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( WeakPointerTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testSharedCons ); + CPPUNIT_TEST( testSharedConsEmpty ); + CPPUNIT_TEST( testSharedConsCastC2P ); + CPPUNIT_TEST( testSharedConsCastP2C ); + CPPUNIT_TEST( testSharedConsCastBad ); + + CPPUNIT_TEST( testSharedAss ); + CPPUNIT_TEST( testSharedAssEmpty ); + CPPUNIT_TEST( testSharedAssCastC2P ); + CPPUNIT_TEST( testSharedAssCastP2C ); + CPPUNIT_TEST( testSharedAssCastBad ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyConsCastC2P ); + CPPUNIT_TEST( testCopyConsCastP2C ); + CPPUNIT_TEST( testCopyConsCastBad ); + + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testCopyAssCastC2P ); + CPPUNIT_TEST( testCopyAssCastP2C ); + CPPUNIT_TEST( testCopyAssCastBad ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveConsCastC2P ); + CPPUNIT_TEST( testMoveConsCastP2C ); + CPPUNIT_TEST( testMoveConsCastBad ); + + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testMoveAssCastC2P ); + CPPUNIT_TEST( testMoveAssCastP2C ); + CPPUNIT_TEST( testMoveAssCastBad ); + + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testBecomesNull ); + CPPUNIT_TEST( testMakeOwned ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ); + + void testEmpty( ); + + void testSharedCons( ); + void testSharedConsEmpty( ); + void testSharedConsCastC2P( ); + void testSharedConsCastP2C( ); + void testSharedConsCastBad( ); + + void testSharedAss( ); + void testSharedAssEmpty( ); + void testSharedAssCastC2P( ); + void testSharedAssCastP2C( ); + void testSharedAssCastBad( ); + + void testCopyCons( ); + void testCopyConsCastC2P( ); + void testCopyConsCastP2C( ); + void testCopyConsCastBad( ); + + void testCopyAss( ); + void testCopyAssCastC2P( ); + void testCopyAssCastP2C( ); + void testCopyAssCastBad( ); + + void testMoveCons( ); + void testMoveConsCastC2P( ); + void testMoveConsCastP2C( ); + void testMoveConsCastBad( ); + + void testMoveAss( ); + void testMoveAssCastC2P( ); + void testMoveAssCastP2C( ); + void testMoveAssCastBad( ); + + void testSwap( ); + void testClear( ); + void testBecomesNull( ); + void testMakeOwned( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( WeakPointerTest ); + +/*----------------------------------------------------------------------------*/ + +#include "ptr-common.hh" + +using Ptr = T_SharedPtr< T_Test >; +using WPtr = T_WeakPtr< T_Test >; + +void WeakPointerTest::tearDown( ) +{ + T_Test::dCalled = 0; +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testEmpty( ) +{ + { + WPtr test; + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( false , bool( test ) ); + CPPUNIT_ASSERT_EQUAL( (T_Test *) nullptr , (T_Test *) test ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testSharedCons( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + { + WPtr test( shared ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT_EQUAL( false , !test ); + CPPUNIT_ASSERT( (T_Test *) test != nullptr ); + + CPPUNIT_ASSERT( test == shared ); + CPPUNIT_ASSERT( shared == test ); + + CPPUNIT_ASSERT_EQUAL( -1 , test->field ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testSharedConsEmpty( ) +{ + { + Ptr shared; + WPtr test( shared ); + + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT_EQUAL( false , bool( test ) ); + CPPUNIT_ASSERT_EQUAL( (T_Test *) nullptr , (T_Test *) test ); + + CPPUNIT_ASSERT( test == shared ); + CPPUNIT_ASSERT( shared == test ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testSharedConsCastC2P( ) +{ + T_SharedPtr< T_TestChild > shared( NewShared< T_TestChild >( ) ); + WPtr test( shared ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( (T_Test *) test == (T_TestChild *) shared ); +} + +void WeakPointerTest::testSharedConsCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > test( shared ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( (T_TestChild *) test == (T_Test *) shared ); +} + +void WeakPointerTest::testSharedConsCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + try { + T_WeakPtr< T_TestChild > test( shared ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testSharedAss( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + { + WPtr test; + test = shared; + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( test == shared ); + CPPUNIT_ASSERT( shared == test ); + CPPUNIT_ASSERT_EQUAL( -1 , test->field ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testSharedAssEmpty( ) +{ + Ptr shared; + WPtr test; + test = shared; + CPPUNIT_ASSERT( !test ); + shared = NewShared< T_Test >( ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testSharedAssCastC2P( ) +{ + T_SharedPtr< T_TestChild > shared( NewShared< T_TestChild >( ) ); + WPtr test; + test = shared; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( (T_Test *) test == (T_TestChild *) shared ); +} + +void WeakPointerTest::testSharedAssCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > test; + test = shared; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( (T_TestChild *) test == (T_Test *) shared ); +} + +void WeakPointerTest::testSharedAssCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + T_WeakPtr< T_TestChild > test; + try { + test = shared; + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT( !test ); +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testCopyCons( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr wtest( shared ); + { + WPtr test( wtest ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( test == shared ); + CPPUNIT_ASSERT( shared == test ); + CPPUNIT_ASSERT( test == wtest ); + CPPUNIT_ASSERT( wtest == test ); + CPPUNIT_ASSERT_EQUAL( -1 , test->field ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testCopyConsCastC2P( ) +{ + auto shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > weak( shared ); + WPtr test( weak ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( (T_TestChild*) weak == (T_Test*) test ); + shared.clear( ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testCopyConsCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test( weak ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( (T_Test*) weak == (T_TestChild*) test ); + shared.clear( ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testCopyConsCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + WPtr weak( shared ); + try { + T_WeakPtr< T_TestChild > test( weak ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testCopyAss( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr wtest( shared ); + { + WPtr test; + test = wtest; + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( test == shared ); + CPPUNIT_ASSERT( shared == test ); + CPPUNIT_ASSERT( test == wtest ); + CPPUNIT_ASSERT( wtest == test ); + CPPUNIT_ASSERT_EQUAL( -1 , test->field ); + } + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testCopyAssCastC2P( ) +{ + auto shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > weak( shared ); + WPtr test; + test = weak; + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( (T_TestChild*) weak == (T_Test*) test ); + shared.clear( ); + CPPUNIT_ASSERT( !test ); + +} + +void WeakPointerTest::testCopyAssCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test; + + test = weak; + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( (T_Test*) weak == (T_TestChild*) test ); + shared.clear( ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testCopyAssCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test; + try { + test = weak; + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT( !test ); +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testMoveCons( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr weak( shared ); + WPtr test( std::move( weak ) ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( shared == test ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testMoveConsCastC2P( ) +{ + auto shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > weak( shared ); + WPtr test( std::move( weak ) ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( (T_TestChild*) shared == (T_Test*) test ); +} + +void WeakPointerTest::testMoveConsCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test( std::move( weak ) ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( (T_Test*) shared == (T_TestChild*) test ); +} + +void WeakPointerTest::testMoveConsCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + WPtr weak( shared ); + try { + T_WeakPtr< T_TestChild > test( std::move( weak ) ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT( weak ); +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testMoveAss( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr weak( shared ); + WPtr test; + test = std::move( weak ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( shared == test ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); +} + +void WeakPointerTest::testMoveAssCastC2P( ) +{ + auto shared( NewShared< T_TestChild >( ) ); + T_WeakPtr< T_TestChild > weak( shared ); + WPtr test; + test = std::move( weak ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( (T_TestChild*) shared == (T_Test*) test ); +} + +void WeakPointerTest::testMoveAssCastP2C( ) +{ + Ptr shared( NewShared< T_TestChild >( ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test; + + test = std::move( weak ); + CPPUNIT_ASSERT( test ); + CPPUNIT_ASSERT( !weak ); + CPPUNIT_ASSERT( (T_Test*) shared == (T_TestChild*) test ); +} + +void WeakPointerTest::testMoveAssCastBad( ) +{ + Ptr shared( NewShared< T_OtherChild >( 12 , 34 ) ); + WPtr weak( shared ); + T_WeakPtr< T_TestChild > test; + try { + test = std::move( weak ); + CPPUNIT_FAIL( "std::bad_cast not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( weak ); +} + +/*----------------------------------------------------------------------------*/ + +void WeakPointerTest::testSwap( ) +{ + Ptr stest1( NewShared< T_Test >( 1 ) ); + Ptr stest2( NewShared< T_Test >( 2 ) ); + WPtr test1( stest1 ) , test2( stest2 ); + swap( stest1 , stest2 ); + + CPPUNIT_ASSERT_EQUAL( 1 , stest2->field ); + CPPUNIT_ASSERT_EQUAL( 2 , stest1->field ); +} + +void WeakPointerTest::testClear( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr test( shared ); + test.clear( ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testBecomesNull( ) +{ + WPtr test; + { + Ptr shared( NewShared< T_Test >( ) ); + test = shared; + } + CPPUNIT_ASSERT_EQUAL( 1 , T_Test::dCalled ); + CPPUNIT_ASSERT( !test ); +} + +void WeakPointerTest::testMakeOwned( ) +{ + Ptr shared( NewShared< T_Test >( ) ); + WPtr test( shared ); + T_OwnPtr< T_Test > otest( shared.makeOwned( ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Test::dCalled ); + CPPUNIT_ASSERT( !test ); +} diff --git a/tests/ring-buffer.cc b/tests/ring-buffer.cc new file mode 100644 index 0000000..3733ad8 --- /dev/null +++ b/tests/ring-buffer.cc @@ -0,0 +1,357 @@ +#include +#include +using namespace lw; + + +class RingBufferTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( RingBufferTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testEmptyGrowth ); + + CPPUNIT_TEST( testPutCopy ); + CPPUNIT_TEST( testPutMove ); + CPPUNIT_TEST( testPutNew ); + CPPUNIT_TEST( testPutExpand ); + + CPPUNIT_TEST( testFree ); + CPPUNIT_TEST( testFreeEmpty ); + + CPPUNIT_TEST( testReadNext ); + CPPUNIT_TEST( testReadNextMultiple ); + CPPUNIT_TEST( testReadNextEmpty ); + + CPPUNIT_TEST( testReadAll ); + CPPUNIT_TEST( testReadAllEmpty ); + CPPUNIT_TEST( testReadAllAppends ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testEmpty( ); + void testEmptyGrowth( ); + + void testPutCopy( ); + void testPutMove( ); + void testPutNew( ); + void testPutExpand( ); + + void testFree( ); + void testFreeEmpty( ); + + void testReadNext( ); + void testReadNextMultiple( ); + void testReadNextEmpty( ); + + void testReadAll( ); + void testReadAllEmpty( ); + void testReadAllAppends( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( RingBufferTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { +struct T_Obj +{ + static int cons; + static int copy; + static int move; + static int destr; + + static void reset( ) { cons = copy = move = destr = 0; } + + int x; + T_Obj( ) = delete; + T_Obj( int x ) : x( x ) { cons ++; } + T_Obj( T_Obj const& o ) : x( o.x ) { copy ++; } + T_Obj( T_Obj&& o ) noexcept : x( o.x ) { move ++; } + ~T_Obj( ) { destr ++; } + + T_Obj& operator =( T_Obj const& ) = default; + T_Obj& operator =( T_Obj&& ) noexcept = default; +}; +int T_Obj::cons = 0; +int T_Obj::copy = 0; +int T_Obj::move = 0; +int T_Obj::destr = 0; +using T_Test = T_RingBuffer< T_Obj >; +} + +void RingBufferTest::tearDown( ) +{ + T_Obj::reset( ); +} + +#define M_CHECK_(N,C,M,D) \ + do { \ + CPPUNIT_ASSERT_EQUAL( int(N) , T_Obj::cons ); \ + CPPUNIT_ASSERT_EQUAL( int(C) , T_Obj::copy ); \ + CPPUNIT_ASSERT_EQUAL( int(M) , T_Obj::move ); \ + CPPUNIT_ASSERT_EQUAL( int(D) , T_Obj::destr ); \ + } while ( 0 ) + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testEmpty( ) +{ + { + T_Test rb; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 64 ) , rb.growth( ) ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void RingBufferTest::testEmptyGrowth( ) +{ + { + T_Test rb( 128 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , rb.growth( ) ); + } + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testPutCopy( ) +{ + { + T_Test rb; + T_Obj o( 12 ); + rb.put( o ); + CPPUNIT_ASSERT_EQUAL( rb.growth( ) , rb.capacity( ) ); + M_CHECK_( 1 , 1 , 0 , 0 ); + } + M_CHECK_( 1 , 1 , 0 , 2 ); +} + +void RingBufferTest::testPutMove( ) +{ + { + T_Test rb; + rb.put( T_Obj( 12 ) ); + CPPUNIT_ASSERT_EQUAL( rb.growth( ) , rb.capacity( ) ); + M_CHECK_( 1 , 0 , 1 , 1 ); + } + M_CHECK_( 1 , 0 , 1 , 2 ); +} + +void RingBufferTest::testPutNew( ) +{ + { + T_Test rb; + rb.putNew( 12 ); + CPPUNIT_ASSERT_EQUAL( rb.growth( ) , rb.capacity( ) ); + M_CHECK_( 1 , 0 , 0 , 0 ); + } + M_CHECK_( 1 , 0 , 0 , 1 ); +} + +void RingBufferTest::testPutExpand( ) +{ + T_Test rb( 4 ); + for ( int i = 0 ; i < 5 ; i ++ ) { + rb.putNew( i ); + if ( i < 4 ) { + M_CHECK_( i + 1 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( rb.growth( ) , rb.capacity( ) ); + } else { + M_CHECK_( i + 1 , 0 , i , i ); + CPPUNIT_ASSERT_EQUAL( rb.growth( ) * 2 , rb.capacity( ) ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testFree( ) +{ + T_Test rb; + rb.putNew( 1 ); + rb.free( ); + M_CHECK_( 1 , 0 , 0 , 1 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 64 ) , rb.growth( ) ); +} + +void RingBufferTest::testFreeEmpty( ) +{ + T_Test rb; + rb.free( ); + M_CHECK_( 0 , 0 , 0 , 0 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 64 ) , rb.growth( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testReadNext( ) +{ + T_Obj out( 0 ); + T_Test rb( 4 ); + rb.putNew( 123 ); + CPPUNIT_ASSERT( rb.readNext( out ) ); + CPPUNIT_ASSERT_EQUAL( 123 , out.x ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); +} + +void RingBufferTest::testReadNextMultiple( ) +{ + T_Obj out( 0 ); + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + CPPUNIT_ASSERT( rb.readNext( out ) ); + CPPUNIT_ASSERT_EQUAL( 123 , out.x ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , rb.size( ) ); +} + +void RingBufferTest::testReadNextEmpty( ) +{ + T_Obj out( 0 ); + T_Test rb( 4 ); + CPPUNIT_ASSERT( !rb.readNext( out ) ); + CPPUNIT_ASSERT_EQUAL( 0 , out.x ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testReadAll( ) +{ + T_Array< T_Obj > out; + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + CPPUNIT_ASSERT( rb.readAll( out ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , out.size( ) ); + CPPUNIT_ASSERT_EQUAL( 123 , out[ 0 ].x ); + CPPUNIT_ASSERT_EQUAL( 456 , out[ 1 ].x ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); +} + +void RingBufferTest::testReadAllEmpty( ) +{ + T_Array< T_Obj > out; + T_Test rb( 4 ); + CPPUNIT_ASSERT( !rb.readAll( out ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , out.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); +} + +void RingBufferTest::testReadAllAppends( ) +{ + T_Array< T_Obj > out; + out.addNew( 0 ); + + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + CPPUNIT_ASSERT( rb.readAll( out ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , out.size( ) ); + CPPUNIT_ASSERT_EQUAL( 0 , out[ 0 ].x ); + CPPUNIT_ASSERT_EQUAL( 123 , out[ 1 ].x ); + CPPUNIT_ASSERT_EQUAL( 456 , out[ 2 ].x ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void RingBufferTest::testCopyCons( ) +{ + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + T_Obj::reset( ); + + T_Test copy( rb ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , copy.growth( ) ); + M_CHECK_( 0 , 2 , 0 , 0 ); +} + +void RingBufferTest::testCopyAss( ) +{ + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + + T_Test copy( 128 ); + copy.putNew( 789 ); + T_Obj::reset( ); + + copy = rb; + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , copy.growth( ) ); + M_CHECK_( 0 , 2 , 0 , 1 ); +} + +void RingBufferTest::testMoveCons( ) +{ + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + T_Obj::reset( ); + + T_Test copy( std::move( rb ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , rb.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , copy.growth( ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); +} + +void RingBufferTest::testMoveAss( ) +{ + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + + T_Test copy( 128 ); + copy.putNew( 789 ); + T_Obj::reset( ); + + copy = std::move( rb ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , rb.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , copy.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , copy.growth( ) ); + M_CHECK_( 0 , 0 , 0 , 1 ); +} + +void RingBufferTest::testSwap( ) +{ + T_Test rb( 4 ); + rb.putNew( 123 ); + rb.putNew( 456 ); + + T_Test other( 128 ); + other.putNew( 789 ); + T_Obj::reset( ); + + swap( other , rb ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , rb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 128 ) , rb.growth( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , other.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , other.growth( ) ); + M_CHECK_( 0 , 0 , 0 , 0 ); +} diff --git a/tests/run-test.cc b/tests/run-test.cc new file mode 100644 index 0000000..1e01f5f --- /dev/null +++ b/tests/run-test.cc @@ -0,0 +1,13 @@ +#include +#include +#include + +int main( int argc , char * argv[] ) +{ + CppUnit::TextTestRunner runner; + runner.addTest( CppUnit::TestFactoryRegistry::getRegistry( ) + .makeTest( ) ); + runner.setOutputter( new CppUnit::CompilerOutputter( + &runner.result( ), std::cerr ) ); + return runner.run( ) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/srd-bin-reader.cc b/tests/srd-bin-reader.cc new file mode 100644 index 0000000..2062c90 --- /dev/null +++ b/tests/srd-bin-reader.cc @@ -0,0 +1,559 @@ +#include +#include +#include +using namespace lw; + + +#define V1PROLOGUE_ \ + 0xce , 0xa1 , 0x7c , 0xea , \ + 0x00 , 0xee , 0xff , 0xc0 + +class SRDBinReaderTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDBinReaderTest ); + CPPUNIT_TEST( testEmptyInput ); + CPPUNIT_TEST( testIncompleteMagic ); + CPPUNIT_TEST( testBadMagic ); + + CPPUNIT_TEST( testMissingVersion ); + CPPUNIT_TEST( testIncompleteVersion ); + CPPUNIT_TEST( testBadVersion ); + + CPPUNIT_TEST( testPrologueOnly ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testList ); + CPPUNIT_TEST( testIncompleteList ); + + CPPUNIT_TEST( testString ); + CPPUNIT_TEST( testComment ); + + CPPUNIT_TEST( testWordUnknown ); + CPPUNIT_TEST( testWordEmpty ); + CPPUNIT_TEST( testWordInvalid ); + CPPUNIT_TEST( testWordDup ); + CPPUNIT_TEST( testWordKnown ); + CPPUNIT_TEST( testWordBadID ); + + CPPUNIT_TEST( testSpuriousVar ); + CPPUNIT_TEST( testVarUnknownWord ); + CPPUNIT_TEST( testVarKnownWord ); + + CPPUNIT_TEST( testInt32 ); + CPPUNIT_TEST( testInt64 ); + CPPUNIT_TEST( testFloat ); + + CPPUNIT_TEST( testBinaryEmpty ); + CPPUNIT_TEST( testBinarySizeOnly ); + CPPUNIT_TEST( testBinaryData ); + CPPUNIT_TEST_SUITE_END( ); + + T_String location = T_String( "test" ); + +public: + void testEmptyInput( ); + void testIncompleteMagic( ); + void testBadMagic( ); + + void testMissingVersion( ); + void testIncompleteVersion( ); + void testBadVersion( ); + + void testPrologueOnly( ); + void testEmpty( ); + + void testList( ); + void testIncompleteList( ); + + void testString( ); + void testComment( ); + + void testWordUnknown( ); + void testWordEmpty( ); + void testWordInvalid( ); + void testWordDup( ); + void testWordKnown( ); + void testWordBadID( ); + + void testSpuriousVar( ); + void testVarUnknownWord( ); + void testVarKnownWord( ); + + void testInt32( ); + void testInt64( ); + void testFloat( ); + + void testBinaryEmpty( ); + void testBinarySizeOnly( ); + void testBinaryData( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDBinReaderTest ); + +/*----------------------------------------------------------------------------*/ +// Helper macros + +#define M_CKLS_(SIZE) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( SIZE ) , list.size( ) ) + +#define M_CKTOK_(ITEM,TYPE,POS) \ + do { \ + CPPUNIT_ASSERT( E_SRDTokenType::TYPE == \ + list[ ITEM ].type( ) ); \ + CPPUNIT_ASSERT( list[ ITEM ].hasLocation( ) ); \ + CPPUNIT_ASSERT( location == \ + list[ ITEM ].location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \ + list[ ITEM ].location( ).byte( ) ); \ + } while ( 0 ) + +#define M_CKERRS_( x ) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( x ) , e.errors.size( ) ) + +#define M_CKERR_(IDX,STR,POS) \ + do { \ + auto const& _e( e.errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT( location == _e.location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \ + _e.location( ).byte( ) ); \ + } while ( 0 ) + +#define M_PRINTERR_(IDX) \ + do { \ + char err[ e.errors[ IDX ].error( ).size( ) + 1 ]; \ + err[ sizeof( err ) - 1 ] = 0; \ + memcpy( err , e.errors[ IDX ].error( ).data( ) , \ + sizeof( err ) - 1 ); \ + printf( "ERR %s @ %d\n" , err , int( \ + e.errors[ IDX ].location( ).byte( ) ) ); \ + } while ( 0 ) + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testEmptyInput( ) +{ + T_MemoryInputStream stream( nullptr , 0 ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "missing magic" , 0 ); + } +} + +void SRDBinReaderTest::testIncompleteMagic( ) +{ + const uint8_t data[] = { + 0xce , 0xa1 , 0x7c + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "missing magic" , 3 ); + } +} + +void SRDBinReaderTest::testBadMagic( ) +{ + const uint8_t data[] = { + 0xce , 0xa1 , 0x7c , 0xfa + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid magic number" , 0 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testMissingVersion( ) +{ + const uint8_t data[] = { + 0xce , 0xa1 , 0x7c , 0xea + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "missing version ID" , 4 ); + } +} + +void SRDBinReaderTest::testIncompleteVersion( ) +{ + const uint8_t data[] = { + 0xce , 0xa1 , 0x7c , 0xea , + 0x00 , 0xee , 0xff + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "missing version ID" , 7 ); + } +} + +void SRDBinReaderTest::testBadVersion( ) +{ + const uint8_t data[] = { + 0xce , 0xa1 , 0x7c , 0xea , + 0xc0 , 0xff , 0xee , 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid version ID" , 4 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testPrologueOnly( ) +{ + const uint8_t data[] = { V1PROLOGUE_ }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unexpected end of file" , 8 ); + } +} + +void SRDBinReaderTest::testEmpty( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , 0x00 }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testList( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x01 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 2 ); + M_CKTOK_( 0 , START , 8 ); + M_CKTOK_( 1 , END , 9 ); +} + +void SRDBinReaderTest::testIncompleteList( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x01 , 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unexpected end of file" , 10 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testString( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x05 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 8 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +void SRDBinReaderTest::testComment( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x09 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 8 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testWordUnknown( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , WORD , 8 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +void SRDBinReaderTest::testWordEmpty( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , + 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "empty word" , 9 ); + } +} + +void SRDBinReaderTest::testWordInvalid( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , ' ' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid word" , 9 ); + } +} + +void SRDBinReaderTest::testWordDup( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x02 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "duplicate word" , 18 ); + } +} + +void SRDBinReaderTest::testWordKnown( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x03 , + 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 2 ); + M_CKTOK_( 1 , WORD , 17 ); + CPPUNIT_ASSERT( list[ 1 ].stringValue( ) == T_String( "test" ) ); +} + +void SRDBinReaderTest::testWordBadID( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x03 , + 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unregistered word" , 9 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testSpuriousVar( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x04 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + try { + SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "spurious variable tag" , 8 ); + } +} + +void SRDBinReaderTest::testVarUnknownWord( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x04 , + 0x02 , 0x04 , 0x00 , 0x00 , 0x00 , 't' , 'e' , 's' , 't' , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , VAR , 8 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +void SRDBinReaderTest::testVarKnownWord( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x02 , 0x04 , 0x00 , 0x00 , 0x00 , 't' , 'e' , 's' , 't' , + 0x04 , + 0x03 , 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 2 ); + M_CKTOK_( 1 , VAR , 17 ); + CPPUNIT_ASSERT( list[ 1 ].stringValue( ) == T_String( "test" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testInt32( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x06 , 0x04 , 0x03 , 0x02 , 0x01 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , INT , 8 ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0x01020304 ) , list[ 0 ].longValue( ) ); +} + +void SRDBinReaderTest::testInt64( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x07 , 0x04 , 0x03 , 0x02 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , LONG , 8 ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0x01020304 ) , list[ 0 ].longValue( ) ); +} + +void SRDBinReaderTest::testFloat( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x08 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xf0 , 0x3f , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , FLOAT , 8 ); + CPPUNIT_ASSERT_EQUAL( 1.0 , list[ 0 ].floatValue( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDBinReaderTest::testBinaryEmpty( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x0a , 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 8 ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , list[ 0 ].binary( ).size( ) ); +} + +void SRDBinReaderTest::testBinarySizeOnly( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x0a , 0x08 , 0x00 , 0x00 , 0x00 , + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list; + try { + list = SRDBinaryReadFrom( location , stream , false ); + CPPUNIT_FAIL( "X_SRDErrors exception expected" ); + } catch ( X_SRDErrors & e ) { + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unexpected end of file" , 13 ); + } +} + +void SRDBinReaderTest::testBinaryData( ) +{ + const uint8_t data[] = { V1PROLOGUE_ , + 0x0a , 0x08 , 0x00 , 0x00 , 0x00 , + 0x01 , 0x02 , 0x03 , 0x04 , + 0x05 , 0x06 , 0x07 , 0x08 , + 0x00 + }; + T_MemoryInputStream stream( data , sizeof( data ) ); + T_SRDList list( SRDBinaryReadFrom( location , stream , false ) ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 8 ); + CPPUNIT_ASSERT_EQUAL( size_t( 8 ) , list[ 0 ].binary( ).size( ) ); + for ( uint8_t i = 0 ; i < 8 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( uint8_t( i + 1 ) , + list[ 0 ].binary( )[ i ] ); + } +} diff --git a/tests/srd-bin-writer.cc b/tests/srd-bin-writer.cc new file mode 100644 index 0000000..60759a1 --- /dev/null +++ b/tests/srd-bin-writer.cc @@ -0,0 +1,245 @@ +#include +#include +#include +using namespace lw; + + +class SRDBinWriterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDBinWriterTest ); + CPPUNIT_TEST( testEmptyList ); + CPPUNIT_TEST( testList ); + CPPUNIT_TEST( testString ); + CPPUNIT_TEST( testComment ); + CPPUNIT_TEST( testCommentCoalesce ); + CPPUNIT_TEST( testWordUnknown ); + CPPUNIT_TEST( testWordKnown ); + CPPUNIT_TEST( testVarUnknown ); + CPPUNIT_TEST( testVarKnown ); + CPPUNIT_TEST( testInt32 ); + CPPUNIT_TEST( testInt64 ); + CPPUNIT_TEST( testFloat ); + CPPUNIT_TEST( testBinary ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmptyList( ); + void testList( ); + void testString( ); + void testComment( ); + void testCommentCoalesce( ); + void testWordUnknown( ); + void testWordKnown( ); + void testVarUnknown( ); + void testVarKnown( ); + void testInt32( ); + void testInt64( ); + void testFloat( ); + void testBinary( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDBinWriterTest ); + +/*----------------------------------------------------------------------------*/ + +#define V1PROLOGUE_ \ + 0xce , 0xa1 , 0x7c , 0xea , \ + 0x00 , 0xee , 0xff , 0xc0 + +#define M_RUN_( ) \ + { \ + T_Buffer< uint8_t > buffer; \ + T_MemoryOutputStream stream( buffer ); \ + SRDBinaryWriteTo( stream , list ); \ + CPPUNIT_ASSERT_EQUAL( size_t( sizeof( expected ) ) , \ + buffer.size( ) ); \ + for ( size_t i = 0 ; i < sizeof( expected ) ; i ++ ) { \ + CPPUNIT_ASSERT_EQUAL( int( expected[ i ] ) , \ + int( buffer[ i ] ) ); \ + } \ + } + +/*----------------------------------------------------------------------------*/ + +void SRDBinWriterTest::testEmptyList( ) +{ + T_SRDList list; + const uint8_t expected[] = { V1PROLOGUE_ , 0x00 }; + M_RUN_( ); +} + +void SRDBinWriterTest::testList( ) +{ + T_SRDList list; + list << T_SRDToken::List( T_SRDList( ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x01 , 0x00 , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testString( ) +{ + T_SRDList list; + list << T_SRDToken::String( T_String( "test" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x05 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testComment( ) +{ + { + T_SRDList list; + list << T_SRDToken::Comment( T_String( "test" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x09 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + M_RUN_( ); + } + { + T_SRDList list; + list << T_SRDToken::List( T_SRDList( ) << T_SRDToken::Comment( + T_String( "test" ) ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x01 , + 0x09 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 , 0x00 + }; + M_RUN_( ); + } +} + +void SRDBinWriterTest::testCommentCoalesce( ) +{ + T_SRDList list; + list << T_SRDToken::Comment( T_String( "te" ) ) + << T_SRDToken::Comment( T_String( "st" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x09 , + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testWordUnknown( ) +{ + T_SRDList list; + list << T_SRDToken::Word( T_String( "test" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x02 , // WORD_NEW + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testWordKnown( ) +{ + T_SRDToken word( T_SRDToken::Word( T_String( "test" ) ) ); + T_SRDList list( T_SRDList() << word << word ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x02 , // WORD_NEW + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x03 , // WORD_KNOWN + 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testVarUnknown( ) +{ + T_SRDList list; + list << T_SRDToken::Variable( T_String( "test" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x04 , 0x02 , // VAR_WORD WORD_NEW + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testVarKnown( ) +{ + T_SRDList list; + list << T_SRDToken::Word( T_String( "test" ) ) + << T_SRDToken::Variable( T_String( "test" ) ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x02 , // WORD_NEW + 0x04 , 0x00 , 0x00 , 0x00 , + 't' , 'e' , 's' , 't' , + 0x04 , 0x03 , // VAR_WORD WORD_KNOWN + 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testInt32( ) +{ + T_SRDList list; + list << T_SRDToken::Integer( 0x01020304 ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x06 , // INT + 0x04 , 0x03 , 0x02 , 0x01 , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testInt64( ) +{ + T_SRDList list; + list << T_SRDToken::Long( 0x01020304L ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x07 , // LONG + 0x04 , 0x03 , 0x02 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testFloat( ) +{ + T_SRDList list; + list << T_SRDToken::Float( 1.0 ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x08 , // FLOAT + 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0xf0 , 0x3f , + 0x00 + }; + M_RUN_( ); +} + +void SRDBinWriterTest::testBinary( ) +{ + T_Buffer< uint8_t > buf( 4 ); + buf[ 0 ] = 0xde; + buf[ 1 ] = 0xad; + buf[ 2 ] = 0xbe; + buf[ 3 ] = 0xef; + T_SRDList list; + list << T_SRDToken::Binary( buf ); + const uint8_t expected[] = { V1PROLOGUE_ , + 0x0a , // BINARY + 0x04 , 0x00 , 0x00 , 0x00 , + 0xde , 0xad , 0xbe , 0xef , + 0x00 + }; + M_RUN_( ); +} diff --git a/tests/srd-lexer.cc b/tests/srd-lexer.cc new file mode 100644 index 0000000..4b956c6 --- /dev/null +++ b/tests/srd-lexer.cc @@ -0,0 +1,687 @@ +#include +#include +using namespace lw; + + +class SRDLexerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDLexerTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testList ); + + CPPUNIT_TEST( testString ); + CPPUNIT_TEST( testStringUnterminatedEOL ); + CPPUNIT_TEST( testStringUnterminatedEscEOL ); + CPPUNIT_TEST( testStringUnterminatedEOI ); + CPPUNIT_TEST( testStringUnterminatedEscEOI ); + CPPUNIT_TEST( testStringUTF8Short ); + CPPUNIT_TEST( testStringUTF8Long ); + CPPUNIT_TEST( testStringUTF8BadCodepoint ); + CPPUNIT_TEST( testStringUTF8BadHexa ); + CPPUNIT_TEST( testStringUTF8BadSequence ); + CPPUNIT_TEST( testStringUTF8ControlInSequence ); + CPPUNIT_TEST( testStringUTF8EOSInSequence ); + CPPUNIT_TEST( testStringUTF8EOLInSequence ); + CPPUNIT_TEST( testStringUTF8EOIInSequence ); + + CPPUNIT_TEST( testCommentSLEmptyEOL ); + CPPUNIT_TEST( testCommentSLEmptyEOI ); + CPPUNIT_TEST( testCommentSLEOL ); + CPPUNIT_TEST( testCommentSLEOI ); + + CPPUNIT_TEST( testCommentMLEmpty ); + CPPUNIT_TEST( testCommentMLEmptyEOI ); + CPPUNIT_TEST( testCommentMLEmptyUnterminated ); + CPPUNIT_TEST( testCommentML ); + CPPUNIT_TEST( testCommentMLEOI ); + CPPUNIT_TEST( testCommentMLUnterminated ); + CPPUNIT_TEST( testCommentMLNested ); + CPPUNIT_TEST( testCommentMLNestedUnterminated ); + + CPPUNIT_TEST( testIntegers ); + CPPUNIT_TEST( testFloatValid ); + CPPUNIT_TEST( testFloatInvalid ); + + CPPUNIT_TEST( testWordValid ); + CPPUNIT_TEST( testWordInvalid ); + CPPUNIT_TEST( testVarValid ); + CPPUNIT_TEST( testVarInvalid ); + + CPPUNIT_TEST( testBinaryEmpty ); + CPPUNIT_TEST( testBinaryValid ); + CPPUNIT_TEST( testBinaryBadDigit1 ); + CPPUNIT_TEST( testBinaryBadDigit2 ); + CPPUNIT_TEST( testBinaryBadDigit3 ); + CPPUNIT_TEST( testBinaryUnterminated ); + CPPUNIT_TEST_SUITE_END( ); + + T_String location = T_String( "test" ); + +public: + void testEmpty( ); + + void testList( ); + + void testString( ); + void testStringUnterminatedEOL( ); + void testStringUnterminatedEscEOL( ); + void testStringUnterminatedEOI( ); + void testStringUnterminatedEscEOI( ); + void testStringUTF8Short( ); + void testStringUTF8Long( ); + void testStringUTF8BadCodepoint( ); + void testStringUTF8BadHexa( ); + void testStringUTF8BadSequence( ); + void testStringUTF8ControlInSequence( ); + void testStringUTF8EOSInSequence( ); + void testStringUTF8EOLInSequence( ); + void testStringUTF8EOIInSequence( ); + + void testCommentSLEmptyEOL( ); + void testCommentSLEmptyEOI( ); + void testCommentSLEOL( ); + void testCommentSLEOI( ); + + void testCommentMLEmpty( ); + void testCommentMLEmptyEOI( ); + void testCommentMLEmptyUnterminated( ); + void testCommentML( ); + void testCommentMLEOI( ); + void testCommentMLUnterminated( ); + void testCommentMLNested( ); + void testCommentMLNestedUnterminated( ); + + void testIntegers( ); + void testFloatValid( ); + void testFloatInvalid( ); + + void testWordValid( ); + void testWordInvalid( ); + void testVarValid( ); + void testVarInvalid( ); + + void testBinaryEmpty( ); + void testBinaryValid( ); + void testBinaryBadDigit1( ); + void testBinaryBadDigit2( ); + void testBinaryBadDigit3( ); + void testBinaryUnterminated( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDLexerTest ); + +/*----------------------------------------------------------------------------*/ + +#define M_TEST_(INPUT) \ + T_SRDErrors errors; \ + T_SRDList list( ([&]( ) \ + { \ + T_SRDMemoryTarget mt( false ); \ + mt.start( errors ); \ + T_SRDLexer lexer( location , errors , mt ); \ + char const* ptr = INPUT; \ + while ( *ptr != 0 ) lexer.processCharacter( *ptr ++ ); \ + lexer.processEnd( ); \ + return mt.list( ); \ + } )( ) ); + +#define M_CKLS_(SIZE) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( SIZE ) , list.size( ) ) + +#define M_CKTOK_(ITEM,TYPE,L,C) \ + do { \ + CPPUNIT_ASSERT( E_SRDTokenType::TYPE == \ + list[ ITEM ].type( ) ); \ + CPPUNIT_ASSERT( list[ ITEM ].hasLocation( ) ); \ + CPPUNIT_ASSERT( location == \ + list[ ITEM ].location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ + list[ ITEM ].location( ).line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ + list[ ITEM ].location( ).character( ) ); \ + } while ( 0 ) + +#define M_CKERRS_( x ) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( x ) , errors.size( ) ) + +#define M_CKERR_(IDX,STR,L,C) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT( location == _e.location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ + _e.location( ).line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +#define M_PRINTERR_(IDX) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + char err[ _e.error( ).size( ) + 1 ]; \ + err[ sizeof( err ) - 1 ] = 0; \ + memcpy( err , _e.error( ).data( ) , \ + sizeof( err ) - 1 ); \ + printf( "ERR %s l. %u c. %lu\n" , err , \ + _e.location( ).line( ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testEmpty( ) +{ + M_TEST_( "" ); + M_CKLS_( 0 ); + M_CKERRS_( 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testList( ) +{ + M_TEST_( "( )" ); + M_CKERRS_( 0 ); + M_CKLS_( 2 ); + M_CKTOK_( 0 , START , 1 , 1 ); + M_CKTOK_( 1 , END , 1 , 3 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testString( ) +{ + M_TEST_( "\"test\\\\\\\"\\t\\n\\r\"" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "test\\\"\t\n\r" ) ); +} + +void SRDLexerTest::testStringUnterminatedEOL( ) +{ + M_TEST_( "\"test\\\\\\\"\\t\\n\\r\n" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 16 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "test\\\"\t\n\r" ) ); +} + +void SRDLexerTest::testStringUnterminatedEscEOL( ) +{ + M_TEST_( "\"test\\\\\\\"\\t\\n\\\n" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 15 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "test\\\"\t\n" ) ); +} + +void SRDLexerTest::testStringUnterminatedEOI( ) +{ + M_TEST_( "\"test\\\\\\\"\\t\\n\\r" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 16 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "test\\\"\t\n\r" ) ); +} + +void SRDLexerTest::testStringUnterminatedEscEOI( ) +{ + M_TEST_( "\"test\\\\\\\"\\t\\n\\" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 15 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "test\\\"\t\n" ) ); +} + +void SRDLexerTest::testStringUTF8Short( ) +{ + M_TEST_( "\"\\u20ac\"" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "\xe2\x82\xac" ) ); +} + +void SRDLexerTest::testStringUTF8Long( ) +{ + M_TEST_( "\"\\U000020ac\"" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "\xe2\x82\xac" ) ); +} + +void SRDLexerTest::testStringUTF8BadCodepoint( ) +{ + M_TEST_( "\"\\Uffffffff\"" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid UTF-8 codepoint" , 1 , 11 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( ) ); +} + +void SRDLexerTest::testStringUTF8BadHexa( ) +{ + M_TEST_( "\"\\u20aw\"" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid UTF-8 sequence" , 1 , 7 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( ) ); +} + +void SRDLexerTest::testStringUTF8BadSequence( ) +{ + M_TEST_( "\"\\u20-w\"" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid UTF-8 sequence" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "w" ) ); +} + +void SRDLexerTest::testStringUTF8ControlInSequence( ) +{ + M_TEST_( "\"\\u20\x01w\"" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "control character in UTF-8 sequence" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "w" ) ); +} + +void SRDLexerTest::testStringUTF8EOSInSequence( ) +{ + M_TEST_( "\"\\u20\"" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "incomplete UTF-8 sequence" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( ) ); +} + +void SRDLexerTest::testStringUTF8EOLInSequence( ) +{ + M_TEST_( "\"\\u20\n" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( ) ); +} + +void SRDLexerTest::testStringUTF8EOIInSequence( ) +{ + M_TEST_( "\"\\u20" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated string" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , STRING , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testCommentSLEmptyEOL( ) +{ + M_TEST_( "# \n" ); + M_CKERRS_( 0 ); + M_CKLS_( 0 ); +} + +void SRDLexerTest::testCommentSLEmptyEOI( ) +{ + M_TEST_( "# " ); + M_CKERRS_( 0 ); + M_CKLS_( 0 ); +} + +void SRDLexerTest::testCommentSLEOL( ) +{ + M_TEST_( "# test\n" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +void SRDLexerTest::testCommentSLEOI( ) +{ + M_TEST_( "# test" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) == T_String( "test" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testCommentMLEmpty( ) +{ + M_TEST_( "{ \n \n }\n" ); + M_CKERRS_( 0 ); + M_CKLS_( 0 ); +} + +void SRDLexerTest::testCommentMLEmptyEOI( ) +{ + M_TEST_( "{ \n \n }" ); + M_CKERRS_( 0 ); + M_CKLS_( 0 ); +} + +void SRDLexerTest::testCommentMLEmptyUnterminated( ) +{ + M_TEST_( "{ \n \n " ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated multi-line comment" , 3 , 2 ); + M_CKLS_( 0 ); +} + +void SRDLexerTest::testCommentML( ) +{ + M_TEST_( "{ this is a\n test }\n" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "this is a\n test " ) ); +} + +void SRDLexerTest::testCommentMLEOI( ) +{ + M_TEST_( "{ this is a\n test }" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "this is a\n test " ) ); +} + +void SRDLexerTest::testCommentMLUnterminated( ) +{ + M_TEST_( "{ this is a\n test" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated multi-line comment" , 2 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "this is a\n test" ) ); +} + +void SRDLexerTest::testCommentMLNested( ) +{ + M_TEST_( "{ this { is a }\n test }" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "this { is a }\n test " ) ); +} + +void SRDLexerTest::testCommentMLNestedUnterminated( ) +{ + M_TEST_( "{ this { is a }\n test" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "unterminated multi-line comment" , 2 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , COMMENT , 1 , 1 ); + CPPUNIT_ASSERT( list[ 0 ].stringValue( ) + == T_String( "this { is a }\n test" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testIntegers( ) +{ + M_TEST_( + "0 000 -0 1 -1 123 -123 5000000000 -5000000000 " + "1000000000000000000000000000000000000000000000 123" + ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid integer value" , 1 , 93 ); + M_CKLS_( 11 ); + + M_CKTOK_( 0 , INT , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( 0l , list[ 0 ].longValue( ) ); + + M_CKTOK_( 1 , INT , 1 , 3 ); + CPPUNIT_ASSERT_EQUAL( 0l , list[ 1 ].longValue( ) ); + + M_CKTOK_( 2 , INT , 1 , 7 ); + CPPUNIT_ASSERT_EQUAL( 0l , list[ 2 ].longValue( ) ); + + M_CKTOK_( 3 , INT , 1 , 10 ); + CPPUNIT_ASSERT_EQUAL( 1l , list[ 3 ].longValue( ) ); + + M_CKTOK_( 4 , INT , 1 , 12 ); + CPPUNIT_ASSERT_EQUAL( -1l , list[ 4 ].longValue( ) ); + + M_CKTOK_( 5 , INT , 1 , 15 ); + CPPUNIT_ASSERT_EQUAL( 123l , list[ 5 ].longValue( ) ); + + M_CKTOK_( 6 , INT , 1 , 19 ); + CPPUNIT_ASSERT_EQUAL( -123l , list[ 6 ].longValue( ) ); + + M_CKTOK_( 7 , LONG , 1 , 24 ); + CPPUNIT_ASSERT_EQUAL( 5000000000l , list[ 7 ].longValue( ) ); + + M_CKTOK_( 8 , LONG , 1 , 35 ); + CPPUNIT_ASSERT_EQUAL( -5000000000l , list[ 8 ].longValue( ) ); + + M_CKTOK_( 9 , INT , 1 , 47 ); + CPPUNIT_ASSERT_EQUAL( 0l , list[ 9 ].longValue( ) ); + + M_CKTOK_( 10 , INT , 1 , 94 ); + CPPUNIT_ASSERT_EQUAL( 123l , list[ 10 ].longValue( ) ); +} + +void SRDLexerTest::testFloatValid( ) +{ + M_TEST_( + "0.\n.0\n-0.\n-.0\n1.0\n-1.0\n1e0\n1e-0\n1e+0\n1.e0\n1.e+0\n" + "1.e-0\n1.0e0\n1.0e+0\n1.0e-0\n.1e1\n.1e+1\n.1e-1\n11.25e2\n" + "112500e-2\n11.25e+2" + ); + M_CKERRS_( 0 ); + M_CKLS_( 21 ); + + for ( int i = 0 ; i < 21 ; i ++ ) { + M_CKTOK_( i , FLOAT , i + 1 , 1 ); + } + CPPUNIT_ASSERT_EQUAL( 0.0 , list[ 0 ].floatValue( ) ); + CPPUNIT_ASSERT_EQUAL( 0.0 , list[ 1 ].floatValue( ) ); + CPPUNIT_ASSERT_EQUAL( 0.0 , list[ 2 ].floatValue( ) ); + CPPUNIT_ASSERT_EQUAL( 0.0 , list[ 3 ].floatValue( ) ); + CPPUNIT_ASSERT_EQUAL( 1.0 , list[ 4 ].floatValue( ) ); + CPPUNIT_ASSERT_EQUAL( -1.0 , list[ 5 ].floatValue( ) ); + for ( int i = 6 ; i < 17 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( 1.0 , list[ i ].floatValue( ) ); + } + CPPUNIT_ASSERT_EQUAL( .01 , list[ 17 ].floatValue( ) ); + for ( int i = 18 ; i < 21 ; i ++ ) { + CPPUNIT_ASSERT_EQUAL( 1125.0 , list[ i ].floatValue( ) ); + } +} + +void SRDLexerTest::testFloatInvalid( ) +{ + M_TEST_( + ".\n-.\n1e\n1e+\n1e-" + ); + M_CKERRS_( 5 ); + M_CKERR_( 0 , "fractional part expected" , 1 , 2 ); + M_CKERR_( 1 , "fractional part expected" , 2 , 3 ); + M_CKERR_( 2 , "exponent expected" , 3 , 3 ); + M_CKERR_( 3 , "exponent expected" , 4 , 4 ); + M_CKERR_( 4 , "exponent expected" , 5 , 4 ); + + M_CKLS_( 5 ); + + for ( int i = 0 ; i < 5 ; i ++ ) { + M_CKTOK_( i , FLOAT , i + 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( i < 2 ? 0.0 : 1.0 , + list[ i ].floatValue( ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testWordValid( ) +{ + const char WORDS[5][7] = { + "word" , "-word" , "w12d" , "w-ord" , "-w-ord" + }; + for ( int i = 0 ; i < 5 ; i ++ ) { + for ( int j = 0 ; j < 1 ; j ++ ) { + char buffer[ 12 ]; + strcpy( buffer , WORDS[ i ] ); + if ( j ) strcat( buffer , "\n" ); + + M_TEST_( buffer ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , WORD , 1 , 1 ); + CPPUNIT_ASSERT( T_String::Pooled( WORDS[ i ] ) + == list[ 0 ].stringValue( ) ); + } + } +} + +void SRDLexerTest::testWordInvalid( ) +{ + const char WORDS[4][8] = { + "12word" , "word.0" , "-word-" , "word\"\"" + }; + for ( int i = 0 ; i < 4 ; i ++ ) { + for ( int j = 0 ; j < 1 ; j ++ ) { + char buffer[ 12 ]; + strcpy( buffer , WORDS[ i ] ); + if ( j ) strcat( buffer , "\n" ); + + M_TEST_( buffer ); + M_CKERRS_( 1 ); + } + } +} + +void SRDLexerTest::testVarValid( ) +{ + const char WORDS[3][7] = { + "word" , "w12d" , "w-ord" + }; + for ( int i = 0 ; i < 3 ; i ++ ) { + for ( int j = 0 ; j < 1 ; j ++ ) { + char buffer[ 12 ] = { '$' , 0 }; + strcat( buffer , WORDS[ i ] ); + if ( j ) strcat( buffer , "\n" ); + + M_TEST_( buffer ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , VAR , 1 , 1 ); + CPPUNIT_ASSERT( T_String::Pooled( WORDS[ i ] ) + == list[ 0 ].stringValue( ) ); + } + } +} + +void SRDLexerTest::testVarInvalid( ) +{ + const char WORDS[5][8] = { + "-word" , "12word" , "word.0" , "-word-" , "word\"\"" + }; + for ( int i = 0 ; i < 5 ; i ++ ) { + for ( int j = 0 ; j < 1 ; j ++ ) { + char buffer[ 12 ] = { '$' , 0 }; + strcat( buffer , WORDS[ i ] ); + if ( j ) strcat( buffer , "\n" ); + + M_TEST_( buffer ); + M_CKERRS_( 1 ); + } + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDLexerTest::testBinaryEmpty( ) +{ + M_TEST_( "[]" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , list[ 0 ].binary( ).size( ) ); +} + +void SRDLexerTest::testBinaryValid( ) +{ + M_TEST_( "[ 12 34 ab CD ef56 ]" ); + M_CKERRS_( 0 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 6 ) , list[ 0 ].binary( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , list[ 0 ].binary( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x34 ) , list[ 0 ].binary( )[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0xab ) , list[ 0 ].binary( )[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0xcd ) , list[ 0 ].binary( )[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0xef ) , list[ 0 ].binary( )[ 4 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x56 ) , list[ 0 ].binary( )[ 5 ] ); +} + +void SRDLexerTest::testBinaryBadDigit1( ) +{ + M_TEST_( "[ nope 12 ]" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid binary digit" , 1 , 3 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 1 ) , list[ 0 ].binary( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , list[ 0 ].binary( )[ 0 ] ); +} + +void SRDLexerTest::testBinaryBadDigit2( ) +{ + M_TEST_( "[ 1x 12 ]" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid binary digit" , 1 , 4 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 2 ) , list[ 0 ].binary( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x10 ) , list[ 0 ].binary( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , list[ 0 ].binary( )[ 1 ] ); +} + +void SRDLexerTest::testBinaryBadDigit3( ) +{ + M_TEST_( "[ 12lolnopethiswontwork 34 ]" ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "invalid binary digit" , 1 , 5 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 2 ) , list[ 0 ].binary( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , list[ 0 ].binary( )[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x34 ) , list[ 0 ].binary( )[ 1 ] ); +} + +void SRDLexerTest::testBinaryUnterminated( ) +{ + M_TEST_( "[ 12 " ); + M_CKERRS_( 1 ); + M_CKERR_( 0 , "incomplete binary data" , 1 , 6 ); + M_CKLS_( 1 ); + M_CKTOK_( 0 , BINARY , 1 , 1 ); + CPPUNIT_ASSERT_EQUAL( size_t( 1 ) , list[ 0 ].binary( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint8_t( 0x12 ) , list[ 0 ].binary( )[ 0 ] ); +} diff --git a/tests/srd-mem-target.cc b/tests/srd-mem-target.cc new file mode 100644 index 0000000..9a144b7 --- /dev/null +++ b/tests/srd-mem-target.cc @@ -0,0 +1,195 @@ +#include +#include +using namespace lw; + +/* - SRDMemTargetTest ---------------------------------------------------{{{-*/ +class SRDMemTargetTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDMemTargetTest ); + CPPUNIT_TEST( testStartEnd ); + CPPUNIT_TEST( testPushUnstructured ); + CPPUNIT_TEST( testPushUnstructuredExtraEnd ); + CPPUNIT_TEST( testPushUnstructuredUnterminated ); + CPPUNIT_TEST( testPushStructured ); + CPPUNIT_TEST( testPushStructuredExtraEnd ); + CPPUNIT_TEST( testPushStructuredUnterminated ); + CPPUNIT_TEST_SUITE_END( ); + + size_t counter; + T_String location = T_String( "test" ); + + T_SRDToken && mkToken( T_SRDToken && t ) + { + t.location( location , counter ++ ); + return std::move( t ); + } + +public: + void setUp( ) override + { counter = 0; } + + void testStartEnd( ); + void testPushUnstructured( ); + void testPushUnstructuredExtraEnd( ); + void testPushUnstructuredUnterminated( ); + void testPushStructured( ); + void testPushStructuredExtraEnd( ); + void testPushStructuredUnterminated( ); +}; + + +#define M_CKTOK_(ITEM,TYPE,POS) \ + do { \ + CPPUNIT_ASSERT( E_SRDTokenType::TYPE == (ITEM).type( ) ); \ + CPPUNIT_ASSERT( (ITEM).hasLocation( ) ); \ + CPPUNIT_ASSERT( location == (ITEM).location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \ + (ITEM).location( ).byte( ) ); \ + } while ( 0 ) + +#define M_CKERR_(IDX,STR,POS) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT( location == _e.location( ).source( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( POS ) , \ + _e.location( ).byte( ) ); \ + } while ( 0 ) + + +CPPUNIT_TEST_SUITE_REGISTRATION( SRDMemTargetTest ); + +void SRDMemTargetTest::testStartEnd( ) // {{{ +{ + T_SRDErrors errors; + { + T_SRDMemoryTarget mt( false ); + mt.start( errors ); + mt.end( errors ); + } + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); +} + +// }}} +void SRDMemTargetTest::testPushUnstructured( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( false ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + } + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , mt.list( ).size( ) ); + M_CKTOK_( mt.list( )[ 0 ] , START , 0 ); + M_CKTOK_( mt.list( )[ 1 ] , INT , 1 ); + M_CKTOK_( mt.list( )[ 2 ] , END , 2 ); +} + +// }}} +void SRDMemTargetTest::testPushUnstructuredExtraEnd( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( false ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + } + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unexpected ')'" , 3 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , mt.list( ).size( ) ); + M_CKTOK_( mt.list( )[ 0 ] , START , 0 ); + M_CKTOK_( mt.list( )[ 1 ] , INT , 1 ); + M_CKTOK_( mt.list( )[ 2 ] , END , 2 ); +} + +// }}} +void SRDMemTargetTest::testPushUnstructuredUnterminated( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( false ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + } + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 0 ); + M_CKERR_( 1 , "unterminated list" , 2 ); +} + +// }}} +void SRDMemTargetTest::testPushStructured( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( true ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + } + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , mt.list( ).size( ) ); + M_CKTOK_( mt.list( )[ 0 ] , LIST , 0 ); + + auto const& list( mt.list( )[ 0 ].list( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , list.size( ) ); + M_CKTOK_( list[ 0 ] , INT , 1 ); +} + +// }}} +void SRDMemTargetTest::testPushStructuredExtraEnd( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( true ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListEnd( ) ) ); + } + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unexpected ')'" , 3 ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , mt.list( ).size( ) ); + M_CKTOK_( mt.list( )[ 0 ] , LIST , 0 ); + + auto const& list( mt.list( )[ 0 ].list( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , list.size( ) ); + M_CKTOK_( list[ 0 ] , INT , 1 ); +} + +// }}} +void SRDMemTargetTest::testPushStructuredUnterminated( ) // {{{ +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( true ); + { + T_SRDReaderTargetHelper rth( mt , errors ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + mt.push( errors , mkToken( T_SRDToken::Integer( 123 ) ) ); + mt.push( errors , mkToken( T_SRDToken::ListStart( ) ) ); + } + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 0 ); + M_CKERR_( 1 , "unterminated list" , 2 ); +} + +// }}} + +/*------------------------------------------------------------------------}}}-*/ diff --git a/tests/srd-parser-cfg.cc b/tests/srd-parser-cfg.cc new file mode 100644 index 0000000..81a8d1a --- /dev/null +++ b/tests/srd-parser-cfg.cc @@ -0,0 +1,784 @@ +#include +#include +using namespace lw; + + +class SRDParserConfigTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDParserConfigTest ); + CPPUNIT_TEST( testWordIndex ); + + CPPUNIT_TEST( testRuleSimple ); + CPPUNIT_TEST( testRuleABStarABB ); + CPPUNIT_TEST( testMergedRules ); + CPPUNIT_TEST( testRulesMultiContexts ); + + CPPUNIT_TEST( testContextIDs ); + CPPUNIT_TEST( testEnumIDs ); + + CPPUNIT_TEST( testUnusedEnums ); + CPPUNIT_TEST( testUnusedContexts ); + + CPPUNIT_TEST( testContextInheritance ); + + CPPUNIT_TEST( testErrorMissingCtxRule ); + CPPUNIT_TEST( testErrorMissingCtxParent ); + CPPUNIT_TEST( testErrorMissingEnum ); + CPPUNIT_TEST( testErrorEmptyContext ); + CPPUNIT_TEST( testErrorEmptyRule ); + CPPUNIT_TEST( testErrorEmptyAlt ); + CPPUNIT_TEST( testErrorEmptyRepeat ); + CPPUNIT_TEST( testErrorRecursiveInheritance ); + CPPUNIT_TEST( testErrorAmbiguousRules ); + + CPPUNIT_TEST( testErrorUBRSimpleOK ); + CPPUNIT_TEST( testErrorUBRSimpleUnbalancedStart ); + CPPUNIT_TEST( testErrorUBRSimpleUnbalancedEnd ); + CPPUNIT_TEST( testErrorUBRAltsOKContained ); + CPPUNIT_TEST( testErrorUBRAltsOKBalancing ); + CPPUNIT_TEST( testErrorUBRAltsDifferent ); + CPPUNIT_TEST( testErrorUBRAltsUnbalanced ); + CPPUNIT_TEST( testErrorUBRRepsOK ); + CPPUNIT_TEST( testErrorUBRRepsUnbalanced ); + + CPPUNIT_TEST( testTransition ); + CPPUNIT_TEST_SUITE_END( ); + + void checkMergedRules( T_SRDParserConfig const& c ); + +public: + void testWordIndex( ); + + void testRuleSimple( ); + void testRuleABStarABB( ); + void testMergedRules( ); + void testRulesMultiContexts( ); + + void testContextIDs( ); + void testEnumIDs( ); + + void testUnusedEnums( ); + void testUnusedContexts( ); + + void testContextInheritance( ); + + void testErrorMissingCtxRule( ); + void testErrorMissingCtxParent( ); + void testErrorMissingEnum( ); + void testErrorEmptyContext( ); + void testErrorEmptyRule( ); + void testErrorEmptyAlt( ); + void testErrorEmptyRepeat( ); + void testErrorRecursiveInheritance( ); + void testErrorAmbiguousRules( ); + + void testErrorUBRSimpleOK( ); + void testErrorUBRSimpleUnbalancedStart( ); + void testErrorUBRSimpleUnbalancedEnd( ); + void testErrorUBRAltsOKContained( ); + void testErrorUBRAltsOKBalancing( ); + void testErrorUBRAltsDifferent( ); + void testErrorUBRAltsUnbalanced( ); + void testErrorUBRRepsOK( ); + void testErrorUBRRepsUnbalanced( ); + + void testTransition( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDParserConfigTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::checkMergedRules( T_SRDParserConfig const& c ) +{ + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.startStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.startStates[ 0 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , c.endStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.endStates[ 4 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.endStates[ 5 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.endStates[ 6 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.endStates[ 7 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , c.transitions.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 3 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 4 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 5 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 6 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 7 ) ); + + // 0: "a" -> 1 + { + auto const& t( c.transitions.get( 0 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + + // 1: "a" -> 2 + { + auto const& t( c.transitions.get( 1 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , t.next ); + } + // 1: "b" -> 3 + { + auto const& t( c.transitions.get( 1 , 1 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , t.next ); + } + + // 2: "a" -> 4 + { + auto const& t( c.transitions.get( 2 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , t.next ); + } + + // 3: "a" -> 5 + { + auto const& t( c.transitions.get( 3 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 5 ) , t.next ); + } + + // 4: "a" -> 6 + { + auto const& t( c.transitions.get( 4 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 6 ) , t.next ); + } + + // 6: "a" -> 7 + { + auto const& t( c.transitions.get( 6 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 7 ) , t.next ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testWordIndex( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" << "and" << "test" ); + d.context( T_String( "unused" ) ) + << ( Rule( ) << "not" << "in" << "the" << "index" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.words.size( ) ); + CPPUNIT_ASSERT( c.words[ 0 ] == T_String( "test" ) ); + CPPUNIT_ASSERT( c.words[ 1 ] == T_String( "and" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testRuleSimple( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.startStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.startStates[ 0 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.endStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 1 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 1 ) ); + + auto const& t( c.transitions.get( 0 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); +} + +void SRDParserConfigTest::testRuleABStarABB( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) + << ( AtLeast( 0 ) << ( Alt( ) << "a" << "b" ) ) + << "a" << "b" << "b" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.startStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.startStates[ 0 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , c.endStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 3 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , c.transitions.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.sizeOf( 3 ) ); + + // 0: "a" -> 1 + { + auto const& t( c.transitions.get( 0 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + // 0: "b" -> 0 + { + auto const& t( c.transitions.get( 0 , 1 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.next ); + } + + // 1: "a" -> 1 + { + auto const& t( c.transitions.get( 1 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + // 1: "b" -> 2 + { + auto const& t( c.transitions.get( 1 , 1 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , t.next ); + } + + // 2: "a" -> 1 + { + auto const& t( c.transitions.get( 2 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + // 2: "b" -> 3 + { + auto const& t( c.transitions.get( 2 , 1 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , t.next ); + } + + // 3: "a" -> 1 + { + auto const& t( c.transitions.get( 3 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + // 3: "b" -> 0 + { + auto const& t( c.transitions.get( 3 , 1 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.next ); + } +} + +void SRDParserConfigTest::testMergedRules( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "a" ) + << ( Rule( ) << "a" << "b" << "a" ) + << ( Rule( ) << ( Between( 3 , 5 ) << "a" ) ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + checkMergedRules( c ); +} + +void SRDParserConfigTest::testRulesMultiContexts( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" << EnterContext( "other" ) ); + d.context( T_String( "other" ) ) + << ( Rule( ) << "test" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.startStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.startStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.startStates[ 1 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , c.endStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 3 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , c.transitions.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 2 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 3 ) ); + + { + auto const& t( c.transitions.get( 0 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); + } + { + auto const& t( c.transitions.get( 2 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::WORD == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , t.next ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testContextIDs( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" << EnterContext( "other" ) ) + << ( Rule( ) << "test" << "number" << "two" + << EnterContext( "other" ) ) + << ( Rule( ) << "yet" << "another" << "test" + << EnterContext( "other" ) ) + << ( Rule( ) << "moar" << EnterContext( "moar" ) ) + ; + d.context( T_String( "other" ) ) + << ( Rule( ) << "test" ); + + d.context( T_String( "moar" ) ) + << ( Rule( ) << "test" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , c.ruleContexts.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , c.ruleContexts.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.ruleContexts.sizeOf( 1 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.ruleContexts.sizeOf( 2 ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.ruleContexts[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.ruleContexts[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.ruleContexts[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.ruleContexts[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.ruleContexts[ 4 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.ruleContexts[ 5 ] ); +} + +void SRDParserConfigTest::testEnumIDs( ) +{ + T_String n( T_String::Pooled( "test" ) ); + T_SRDParserDefs d( n ); + { + using namespace SRD; + d.enumeration( n ) << "a" << "b"; + d.context( n ) + << ( Rule( ) << Enum( "test" ) ); + } + + T_SRDParserConfig c( d ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.enums.size( ) ); + CPPUNIT_ASSERT( c.enums[ 0 ].name( ) == n ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.startStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.startStates[ 0 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.endStates.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDParserConfig::INVALID_END ) , c.endStates[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.endStates[ 1 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , c.transitions.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.transitions.sizeOf( 0 ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.transitions.sizeOf( 1 ) ); + + auto const& t( c.transitions.get( 0 , 0 ) ); + CPPUNIT_ASSERT( E_SRDTransitionType::ENUM == t.type ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , t.index ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , t.next ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testUnusedEnums( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" ); + d.enumeration( T_String( "Blah" ) ) + << "a" << "b"; + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , c.enums.size( ) ); +} + +void SRDParserConfigTest::testUnusedContexts( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "test" ); + d.context( T_String( "other" ) ) + << ( Rule( ) << "test" ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT( c.contexts[ 0 ].name( ) == T_String( "test" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testContextInheritance( ) +{ + const T_String n0( T_String::Pooled( "test" ) ); + const T_String n1( T_String::Pooled( "child" ) ); + + T_SRDParserDefs d( n0 ); + { + using namespace SRD; + d.context( n0 ) + << ( Rule( ) << "a" ); + d.context( n1 , n0 ) + << ( Rule( ) << "a" << "b" << "a" ) + << ( Rule( ) << ( Between( 3 , 5 ) << "a" ) ); + d.defaultContext( n1 ); + } + + T_SRDParserConfig c( d ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , c.contexts.size( ) ); + CPPUNIT_ASSERT( c.contexts[ 0 ].name( ) == n1 ); + checkMergedRules( c ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testErrorMissingCtxRule( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "a" << EnterContext( "nope" ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorMissingCtxParent( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "fail" ) , T_String( "parent" ) ) + << ( Rule( ) << "a" ); + d.defaultContext( T_String( "fail" ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorMissingEnum( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << Enum( "nope" ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorEmptyContext( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorEmptyRule( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) << Rule( ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorEmptyAlt( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) << ( Rule( ) << Alt( ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorEmptyRepeat( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) << ( Rule( ) << Opt( ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorRecursiveInheritance( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "fail" ) , T_String( "parent" ) ) + << ( Rule( ) << "a" ); + d.context( T_String( "parent" ) , T_String( "fail" ) ) + << ( Rule( ) << "b" ); + d.defaultContext( T_String( "fail" ) ); + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorAmbiguousRules( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << ( AtLeast( 1 ) << "a" ) ) + << ( Rule( ) << "a" ) + ; + } + CPPUNIT_ASSERT_THROW( NewOwned< T_SRDParserConfig >( d ) , X_SRDParserConfig ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testErrorUBRSimpleOK( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << LStart( ) << "a" << LEnd( ) ) + ; + } + T_SRDParserConfig{ d }; +} + +void SRDParserConfigTest::testErrorUBRSimpleUnbalancedStart( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << LStart( ) << "a" ) + ; + } + CPPUNIT_ASSERT_THROW( T_SRDParserConfig{ d } , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorUBRSimpleUnbalancedEnd( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) << "a" << LEnd( ) ) + ; + } + CPPUNIT_ASSERT_THROW( T_SRDParserConfig{ d } , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorUBRAltsOKContained( ) +{ + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.context( T_String( "test" ) ) + << ( Rule( ) + << ( Alt( ) + << ( Sub( ) << LStart( ) << "a" << LEnd( ) ) + << ( Sub( ) << LStart( ) << "b" << LEnd( ) ) + << "c" ) ) + ; + } + T_SRDParserConfig{ d }; +} + +void SRDParserConfigTest::testErrorUBRAltsOKBalancing( ) +{ + T_SRDParserDefs d( "test" ); + { + using namespace SRD; + d.context( "test" ) + << ( Rule( ) + << ( Alt( ) + << ( Sub( ) << LStart( ) << "a" ) + << ( Sub( ) << LStart( ) << "b" ) ) + << "c" + << ( Alt( ) + << ( Sub( ) << "d" << LEnd( ) ) + << ( Sub( ) << "e" << LEnd( ) ) ) ) + ; + } + T_SRDParserConfig{ d }; +} + +void SRDParserConfigTest::testErrorUBRAltsDifferent( ) +{ + T_SRDParserDefs d( "test" ); + { + using namespace SRD; + d.context( "test" ) + << ( Rule( ) + << ( Alt( ) << LStart( ) << LEnd( ) ) ); + } + CPPUNIT_ASSERT_THROW( T_SRDParserConfig{ d } , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorUBRAltsUnbalanced( ) +{ + T_SRDParserDefs d( "test" ); + { + using namespace SRD; + d.context( "test" ) + << ( Rule( ) + << ( Alt( ) + << ( Sub( ) << LStart( ) << "a" ) + << ( Sub( ) << LStart( ) << "b" ) ) ); + } + CPPUNIT_ASSERT_THROW( T_SRDParserConfig{ d } , X_SRDParserConfig ); +} + +void SRDParserConfigTest::testErrorUBRRepsOK( ) +{ + T_SRDParserDefs d( "test" ); + { + using namespace SRD; + d.context( "test" ) + << ( Rule( ) + << ( Times( 5 ) << LStart( ) ) + << ( Times( 3 ) << LEnd( ) ) + << LEnd( ) << LEnd( ) ); + } + T_SRDParserConfig{ d }; +} + +void SRDParserConfigTest::testErrorUBRRepsUnbalanced( ) +{ + T_SRDParserDefs d( "test" ); + { + using namespace SRD; + d.context( "test" ) + << ( Rule( ) + << ( Between( 1 , 5 ) << LStart( ) ) + << ( Between( 1 , 5 ) << LEnd( ) ) ); + } + CPPUNIT_ASSERT_THROW( T_SRDParserConfig{ d } , X_SRDParserConfig ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserConfigTest::testTransition( ) +{ +#define M_DO_TRANS( STATE , TOK ) \ + uint32_t state( STATE ); \ + bool result = c.transition( state , TOK ) + +#define M_CHECK_TRANS_OK( STATE , TOK , OUT ) \ + do { \ + M_DO_TRANS( STATE , TOK ); \ + CPPUNIT_ASSERT( result ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( OUT ) , state ); \ + } while ( 0 ) + +#define M_CHECK_TRANS_FAIL( STATE , TOK ) \ + do { \ + M_DO_TRANS( STATE , TOK ); \ + CPPUNIT_ASSERT( !result ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( STATE ) , state ); \ + } while ( 0 ) + + T_SRDParserDefs d( T_String( "test" ) ); + { + using namespace SRD; + d.enumeration( T_String( "test" ) ) + << "a" << "b"; + d.context( T_String( "test" ) ) + << ( Rule( ) << Int32( ) << "a" << Enum( "test" ) << Word( ) + << ( Alt( ) + << ( Sub( ) << "a" << "a" ) + << ( Sub( ) << Enum( "test" ) << "b" ) + << ( Sub( ) << Word( ) << "c" ) ) ) + ; + } + + T_SRDParserConfig c( d ); + T_SRDToken tInt( T_SRDToken::Integer( 1 ) ) , + tWordA( T_SRDToken::Word( T_String( "a" ) ) ) , + tWordB( T_SRDToken::Word( T_String( "b" ) ) ) , + tWordC( T_SRDToken::Word( T_String( "c" ) ) ); + + M_CHECK_TRANS_OK( 0 , tInt , 1 ); + M_CHECK_TRANS_FAIL( 0 , tWordA ); + + M_CHECK_TRANS_FAIL( 1 , tInt ); + M_CHECK_TRANS_OK( 1 , tWordA , 2 ); + M_CHECK_TRANS_FAIL( 1 , tWordB ); + + M_CHECK_TRANS_FAIL( 2 , tInt ); + M_CHECK_TRANS_OK( 2 , tWordA , 3 ); + M_CHECK_TRANS_OK( 2 , tWordB , 3 ); + M_CHECK_TRANS_FAIL( 2 , tWordC ); + + M_CHECK_TRANS_FAIL( 3 , tInt ); + M_CHECK_TRANS_OK( 3 , tWordA , 4 ); + M_CHECK_TRANS_OK( 3 , tWordB , 4 ); + M_CHECK_TRANS_OK( 3 , tWordC , 4 ); + + M_CHECK_TRANS_FAIL( 4 , tInt ); + M_CHECK_TRANS_OK( 4 , tWordA , 6 ); + M_CHECK_TRANS_OK( 4 , tWordB , 7 ); + M_CHECK_TRANS_OK( 4 , tWordC , 5 ); +} diff --git a/tests/srd-parser-defs.cc b/tests/srd-parser-defs.cc new file mode 100644 index 0000000..708a3a8 --- /dev/null +++ b/tests/srd-parser-defs.cc @@ -0,0 +1,880 @@ +#include +#include +using namespace lw; + + +class SRDInputItemTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDInputItemTest ); + CPPUNIT_TEST( testWordFromTString ); + CPPUNIT_TEST( testWordFromCString ); + + CPPUNIT_TEST( testEnumFromTString ); + CPPUNIT_TEST( testEnumFromCString ); + + CPPUNIT_TEST( testWordToken ); + CPPUNIT_TEST( testStringToken ); + CPPUNIT_TEST( testStartToken ); + CPPUNIT_TEST( testEndToken ); + CPPUNIT_TEST( testIntToken ); + CPPUNIT_TEST( testLongToken ); + CPPUNIT_TEST( testFloatToken ); + CPPUNIT_TEST( testBinaryToken ); + + CPPUNIT_TEST( testAlt ); + CPPUNIT_TEST( testInteger ); + CPPUNIT_TEST( testNumeric ); + CPPUNIT_TEST( testText ); + + CPPUNIT_TEST( testOptNoArg ); + CPPUNIT_TEST( testOptArg ); + + CPPUNIT_TEST( testAtLeast0 ); + CPPUNIT_TEST( testAtLeast1 ); + CPPUNIT_TEST( testAtLeast2 ); + + CPPUNIT_TEST( testAtMost1 ); + CPPUNIT_TEST( testAtMost2 ); + + CPPUNIT_TEST( testTimes1 ); + CPPUNIT_TEST( testTimes2 ); + + CPPUNIT_TEST( testBetween ); + CPPUNIT_TEST( testBetweenSame ); + CPPUNIT_TEST( testBetweenInverted ); + + CPPUNIT_TEST( testRepeatSequence ); + + CPPUNIT_TEST( testList ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testWordFromTString( ); + void testWordFromCString( ); + + void testEnumFromTString( ); + void testEnumFromCString( ); + + void testWordToken( ); + void testStringToken( ); + void testStartToken( ); + void testEndToken( ); + void testIntToken( ); + void testLongToken( ); + void testFloatToken( ); + void testBinaryToken( ); + + void testAlt( ); + void testInteger( ); + void testNumeric( ); + void testText( ); + + void testOptNoArg( ); + void testOptArg( ); + + void testAtLeast0( ); + void testAtLeast1( ); + void testAtLeast2( ); + + void testAtMost1( ); + void testAtMost2( ); + + void testTimes1( ); + void testTimes2( ); + + void testBetween( ); + void testBetweenSame( ); + void testBetweenInverted( ); + + void testRepeatSequence( ); + + void testList( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDInputItemTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testWordFromTString( ) +{ + T_String const word( "some-word" ); + + auto item( SRD::Word( word ) ); + CPPUNIT_ASSERT( E_SRDInputItem::WORD == item.type( ) ); + CPPUNIT_ASSERT( word == item.word( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( word == sb ); +} + +void SRDInputItemTest::testWordFromCString( ) +{ + char const* const word = "some-word"; + auto item( SRD::Word( word ) ); + CPPUNIT_ASSERT( E_SRDInputItem::WORD == item.type( ) ); + CPPUNIT_ASSERT( item.word( ) == T_String( word ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( word ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testEnumFromTString( ) +{ + T_String const word( "EnumType" ); + + auto item( SRD::Enum( word ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ENUM == item.type( ) ); + CPPUNIT_ASSERT( word == item.word( ) ); + + T_StringBuilder sb, sb2; + sb << item; + sb2 << '[' << word << "::*]"; + CPPUNIT_ASSERT( sb2 == sb ); +} + +void SRDInputItemTest::testEnumFromCString( ) +{ + char const* const word = "EnumType"; + auto item( SRD::Enum( word ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ENUM == item.type( ) ); + CPPUNIT_ASSERT( item.word( ) == T_String( word ) ); + + T_StringBuilder sb, sb2; + sb << item; + sb2 << '[' << word << "::*]"; + CPPUNIT_ASSERT( sb == sb2 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testWordToken( ) +{ + auto item( SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::WORD == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD" ) ); +} + +void SRDInputItemTest::testStringToken( ) +{ + auto item( SRD::String( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::STRING == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "STRING" ) ); +} + +void SRDInputItemTest::testStartToken( ) +{ + auto item( SRD::LStart( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::START == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "(" ) ); +} + +void SRDInputItemTest::testEndToken( ) +{ + auto item( SRD::LEnd( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::END == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( ")" ) ); +} + +void SRDInputItemTest::testIntToken( ) +{ + auto item( SRD::Int32( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::INT == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "INT" ) ); +} + +void SRDInputItemTest::testLongToken( ) +{ + auto item( SRD::Int64( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::LONG == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "LONG" ) ); +} + +void SRDInputItemTest::testFloatToken( ) +{ + auto item( SRD::Float( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::FLOAT == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "FLOAT" ) ); +} + +void SRDInputItemTest::testBinaryToken( ) +{ + auto item( SRD::Binary( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::BINARY == item.token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "BINARY" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testAlt( ) +{ + auto item( SRD::Alt( ) << SRD::Int32( ) << SRD::Int64( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ALTERNATIVE == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.items( ).size( ) ); + + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::INT == item.items( )[ 0 ].token( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 1 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::LONG == item.items( )[ 1 ].token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ INT | LONG ]" ) ); +} + +void SRDInputItemTest::testInteger( ) +{ + auto item( SRD::Integer( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ALTERNATIVE == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.items( ).size( ) ); + + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::INT == item.items( )[ 0 ].token( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 1 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::LONG == item.items( )[ 1 ].token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ INT | LONG ]" ) ); +} + +void SRDInputItemTest::testNumeric( ) +{ + auto item( SRD::Numeric( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ALTERNATIVE == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , item.items( ).size( ) ); + + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::INT == item.items( )[ 0 ].token( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 1 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::LONG == item.items( )[ 1 ].token( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 2 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::FLOAT == item.items( )[ 2 ].token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ INT | LONG | FLOAT ]" ) ); +} + +void SRDInputItemTest::testText( ) +{ + auto item( SRD::Text( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::ALTERNATIVE == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.items( ).size( ) ); + + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::STRING == item.items( )[ 0 ].token( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 1 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::WORD == item.items( )[ 1 ].token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ STRING | WORD ]" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testOptNoArg( ) +{ + auto item( SRD::Opt( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ ]?" ) ); +} + +void SRDInputItemTest::testOptArg( ) +{ + auto item( SRD::Opt( SRD::Word( ) ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.max( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == item.items( )[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::WORD == item.items( )[ 0 ].token( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD?" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testAtLeast0( ) +{ + auto item( SRD::AtLeast( 0 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( UINT32_MAX ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD*" ) ); +} + +void SRDInputItemTest::testAtLeast1( ) +{ + auto item( SRD::AtLeast( 1 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( UINT32_MAX ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD+" ) ); +} + +void SRDInputItemTest::testAtLeast2( ) +{ + auto item( SRD::AtLeast( 2 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( UINT32_MAX ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{2+}" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testAtMost1( ) +{ + auto item( SRD::AtMost( 1 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD?" ) ); +} + +void SRDInputItemTest::testAtMost2( ) +{ + auto item( SRD::AtMost( 2 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{0,2}" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testTimes1( ) +{ + auto item( SRD::Times( 1 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD" ) ); +} + +void SRDInputItemTest::testTimes2( ) +{ + auto item( SRD::Times( 2 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{2}" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testBetween( ) +{ + auto item( SRD::Between( 2 , 4 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{2,4}" ) ); +} + +void SRDInputItemTest::testBetweenSame( ) +{ + auto item( SRD::Between( 2 , 2 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{2}" ) ); +} + +void SRDInputItemTest::testBetweenInverted( ) +{ + auto item( SRD::Between( 4 , 2 ) << SRD::Word( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::REPETITION == item.type( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , item.items( ).size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , item.min( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , item.max( ) ); + + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "WORD{2,4}" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testRepeatSequence( ) +{ + auto item( SRD::AtLeast( 0 ) << SRD::Word( ) << SRD::Word( ) ); + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "[ WORD WORD ]*" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDInputItemTest::testList( ) +{ + auto item( SRD::Sub( ) << ( SRD::List( ) << SRD::Word( ) ) ); + T_StringBuilder sb; + sb << item; + CPPUNIT_ASSERT( sb == T_String( "( WORD )" ) ); +} + + +/*============================================================================*/ + +class SRDInputRuleTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDInputRuleTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testAddItem ); + CPPUNIT_TEST( testAddList ); + CPPUNIT_TEST( testSetExecutor ); + CPPUNIT_TEST( testSetContext ); + CPPUNIT_TEST( testSetOnEnter ); + CPPUNIT_TEST( testSetOnExit ); + CPPUNIT_TEST( testStringNoContext ); + CPPUNIT_TEST( testStringContext ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testAddItem( ); + void testAddList( ); + void testSetExecutor( ); + void testSetContext( ); + void testSetOnEnter( ); + void testSetOnExit( ); + void testStringNoContext( ); + void testStringContext( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDInputRuleTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDInputRuleTest::testEmpty( ) +{ + auto rule( SRD::Rule( ) ); + CPPUNIT_ASSERT( !rule.handler( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , rule.rule( ).size( ) ); + CPPUNIT_ASSERT( !rule.context( ) ); + CPPUNIT_ASSERT( !rule.onEnter( ) ); + CPPUNIT_ASSERT( !rule.onExit( ) ); +} + +void SRDInputRuleTest::testAddItem( ) +{ + auto rule( SRD::Rule( ) << SRD::Word( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , rule.rule( ).size( ) ); +} + +void SRDInputRuleTest::testAddList( ) +{ + auto rule( SRD::Rule( ) << ( SRD::List( ) << SRD::Word( ) ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , rule.rule( ).size( ) ); +} + +void SRDInputRuleTest::testSetExecutor( ) +{ + bool check = false; + F_SRDHandler exec = [&check]( auto const& ) { + check = true; + return false; + }; + + auto rule( SRD::Rule( ) << exec ); + T_SRDParserData * x = nullptr; + ( rule.handler( ) )( *x ); + + CPPUNIT_ASSERT( check ); +} + +void SRDInputRuleTest::testSetContext( ) +{ + auto rule( SRD::Rule( ) << SRD::EnterContext( "ctx" ) ); + CPPUNIT_ASSERT( rule.context( ) == T_String( "ctx" ) ); +} + +void SRDInputRuleTest::testSetOnEnter( ) +{ + bool check = false; + F_SRDHandler exec = [&check]( auto const& ) { + check = true; + return false; + }; + + auto rule( SRD::Rule( ) << SRD::OnEnter( exec ) ); + T_SRDParserData * x = nullptr; + ( rule.onEnter( ) )( *x ); + + CPPUNIT_ASSERT( check ); +} + +void SRDInputRuleTest::testSetOnExit( ) +{ + bool check = false; + F_SRDHandler exec = [&check]( auto const& ) { + check = true; + return false; + }; + + auto rule( SRD::Rule( ) << SRD::OnExit( exec ) ); + T_SRDParserData * x = nullptr; + ( rule.onExit( ) )( *x ); + + CPPUNIT_ASSERT( check ); +} + +void SRDInputRuleTest::testStringNoContext( ) +{ + auto rule( SRD::Rule( ) << SRD::Word( ) << SRD::Integer( ) ); + T_StringBuilder sb; + sb << rule; + CPPUNIT_ASSERT( sb == T_String( "( WORD [ INT | LONG ] )" ) ); +} + +void SRDInputRuleTest::testStringContext( ) +{ + auto rule( SRD::Rule( ) << SRD::Word( ) << SRD::Integer( ) + << SRD::EnterContext( "ctx" ) ); + T_StringBuilder sb; + sb << rule; + CPPUNIT_ASSERT( sb == T_String( "( WORD [ INT | LONG ] ...ctx... )" ) ); +} + + +/*----------------------------------------------------------------------------*/ + +class SRDEnumTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDEnumTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testItems ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testItems( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDEnumTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDEnumTest::testEmpty( ) +{ + auto e( SRD::CreateEnum( "test" ) ); + CPPUNIT_ASSERT( e.name( ) == T_String( "test" ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e.size( ) ); +} + +void SRDEnumTest::testItems( ) +{ + auto e( SRD::CreateEnum( "test" ) << "a" << "b" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , e.size( ) ); + + CPPUNIT_ASSERT( e.contains( "a" ) ); + CPPUNIT_ASSERT( e.contains( "b" ) ); + CPPUNIT_ASSERT( !e.contains( "c" ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e[ "a" ] ); + CPPUNIT_ASSERT( T_String( "a" ) == e[ 0 ] ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , e[ "b" ] ); + CPPUNIT_ASSERT( T_String( "b" ) == e[ 1 ] ); +} + + +/*============================================================================*/ + +class SRDContextTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDContextTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testParent ); + CPPUNIT_TEST( testRules ); + CPPUNIT_TEST( testDump ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testParent( ); + void testRules( ); + void testDump( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDContextTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDContextTest::testEmpty( ) +{ + auto e( SRD::Context( "test" ) ); + CPPUNIT_ASSERT( e.name( ) == T_String( "test" ) ); + CPPUNIT_ASSERT( !e.parent( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e.rules( ).size( ) ); +} + +void SRDContextTest::testParent( ) +{ + auto e( SRD::Context( "test" , "parent" ) ); + CPPUNIT_ASSERT( e.name( ) == T_String( "test" ) ); + CPPUNIT_ASSERT( e.parent( ) == T_String( "parent" ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e.rules( ).size( ) ); +} + +void SRDContextTest::testRules( ) +{ + using namespace SRD; + auto e( Context( "test" ) + << ( Rule( ) << Word( ) ) + << ( Rule( ) << Int32( ) ) ); + + auto const& rules( e.rules( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , rules.size( ) ); + + auto const& r0( rules[ 0 ].rule( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , r0.size( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == r0[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::WORD == r0[ 0 ].token( ) ); + + auto const& r1( rules[ 1 ].rule( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , r1.size( ) ); + CPPUNIT_ASSERT( E_SRDInputItem::TOKEN == r1[ 0 ].type( ) ); + CPPUNIT_ASSERT( E_SRDTokenType::INT == r1[ 0 ].token( ) ); +} + +void SRDContextTest::testDump( ) +{ + using namespace SRD; + auto e( Context( "test" ) + << ( Rule( ) << Word( ) ) + << ( Rule( ) << Int32( ) ) ); + + T_StringBuilder sb; + e.dump( sb , T_String::Pooled( "\n" ) ); + + CPPUNIT_ASSERT( sb == T_String( "( WORD )\n( INT )" ) ); +} + + +/*============================================================================*/ + +class SRDParserDefsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDParserDefsTest ); + CPPUNIT_TEST( testDefault ); + + CPPUNIT_TEST( testGetContextParentOK ); + CPPUNIT_TEST( testGetContextBadParent ); + CPPUNIT_TEST( testGetContextNew ); + CPPUNIT_TEST( testGetContextNewWithParent ); + + CPPUNIT_TEST( testGetEnum ); + + CPPUNIT_TEST( testSetDefaultContextSame ); + CPPUNIT_TEST( testSetDefaultContextOther ); + CPPUNIT_TEST( testSetDefaultContextMissing ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testDefault( ); + + void testGetContextParentOK( ); + void testGetContextBadParent( ); + void testGetContextNew( ); + void testGetContextNewWithParent( ); + + void testGetEnum( ); + + void testSetDefaultContextSame( ); + void testSetDefaultContextOther( ); + void testSetDefaultContextMissing( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDParserDefsTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDParserDefsTest::testDefault( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + + T_SRDParserDefs e( dc ); + CPPUNIT_ASSERT( e.defaultContext( ) == dc ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , e.contexts( ) ); + CPPUNIT_ASSERT( e[ 0 ].name( ) == dc ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e.contextId( dc ) ); + CPPUNIT_ASSERT( &e[ 0 ] == &e.context( dc ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , e.enums( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserDefsTest::testGetContextParentOK( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + + T_SRDParserDefs e( dc ); + auto const& c( e.context( dc , T_String( ) ) ); + CPPUNIT_ASSERT( c.name( ) == dc ); + CPPUNIT_ASSERT( &e[ 0 ] == &c ); +} + +void SRDParserDefsTest::testGetContextBadParent( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + + T_SRDParserDefs e( dc ); + CPPUNIT_ASSERT_THROW( e.context( dc , T_String( "nope" ) ) , + std::invalid_argument ); +} + +void SRDParserDefsTest::testGetContextNew( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + T_String cn2( T_String::Pooled( "other" ) ); + + T_SRDParserDefs e( dc ); + auto const& c( e.context( cn2 ) ); + CPPUNIT_ASSERT( c.name( ) == cn2 ); + CPPUNIT_ASSERT( !c.parent( ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , e.contexts( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , e.contextId( cn2 ) ); + CPPUNIT_ASSERT( &e[ 1 ] == &c ); + CPPUNIT_ASSERT( &e.context( cn2 ) == &c ); +} + +void SRDParserDefsTest::testGetContextNewWithParent( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + T_String cn2( T_String::Pooled( "other" ) ); + + T_SRDParserDefs e( dc ); + auto const& c( e.context( cn2 , dc ) ); + CPPUNIT_ASSERT( c.name( ) == cn2 ); + CPPUNIT_ASSERT( c.parent( ) == dc ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , e.contexts( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , e.contextId( cn2 ) ); + CPPUNIT_ASSERT( &e[ 1 ] == &c ); + CPPUNIT_ASSERT( &e.context( cn2 ) == &c ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserDefsTest::testGetEnum( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + + T_SRDParserDefs d( dc ); + auto const& e( d.enumeration( dc ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , d.enums( ) ); + CPPUNIT_ASSERT( e.name( ) == dc ); + CPPUNIT_ASSERT( d.hasEnum( dc ) ); + CPPUNIT_ASSERT( &d.enumeration( dc ) == &e ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserDefsTest::testSetDefaultContextSame( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + + T_SRDParserDefs e( dc ); + e.defaultContext( dc ); + CPPUNIT_ASSERT( e.defaultContext( ) == dc ); +} + +void SRDParserDefsTest::testSetDefaultContextOther( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + T_String dc2( T_String::Pooled( "test2" ) ); + + T_SRDParserDefs e( dc ); + e.context( dc2 ); + e.defaultContext( dc2 ); + CPPUNIT_ASSERT( e.defaultContext( ) == dc2 ); +} + +void SRDParserDefsTest::testSetDefaultContextMissing( ) +{ + T_String dc( T_String::Pooled( "test" ) ); + T_String dc2( T_String::Pooled( "test2" ) ); + + T_SRDParserDefs e( dc ); + CPPUNIT_ASSERT_THROW( e.defaultContext( dc2 ) , + std::invalid_argument ); +} diff --git a/tests/srd-parser.cc b/tests/srd-parser.cc new file mode 100644 index 0000000..b078e39 --- /dev/null +++ b/tests/srd-parser.cc @@ -0,0 +1,606 @@ +#include +#include +#include +using namespace lw; + + +class SRDParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDParserTest ); + CPPUNIT_TEST( testTokenTypes ); + CPPUNIT_TEST( testParse ); + + CPPUNIT_TEST( testExecStartRuleFinish ); + CPPUNIT_TEST( testExecContext ); + CPPUNIT_TEST( testExecContextAndRule ); + CPPUNIT_TEST( testExecInterruptTop ); + CPPUNIT_TEST( testExecInterruptContext ); + CPPUNIT_TEST( testExecLoneEnter ); + CPPUNIT_TEST( testExecLoneExit ); + CPPUNIT_TEST( testExecNoContextHandler ); + + CPPUNIT_TEST( testParseOLBCList ); + CPPUNIT_TEST( testParseOLBCContext ); + CPPUNIT_TEST( testParseOLBCListThenContext ); + CPPUNIT_TEST( testParseOLBCListOverrides ); + CPPUNIT_TEST( testParseOLBCContextOverrides ); + CPPUNIT_TEST( testParseOLBCFailure ); + + CPPUNIT_TEST( testErrorTopLevelJunk ); + CPPUNIT_TEST( testErrorMismatchInRule ); + CPPUNIT_TEST( testErrorContextJunk ); + CPPUNIT_TEST( testErrorUnterminatedLists ); + + CPPUNIT_TEST( testErrorNoExec ); + CPPUNIT_TEST( testErrorInHandlerTop ); + CPPUNIT_TEST( testErrorInHandlerContext ); + CPPUNIT_TEST_SUITE_END( ); + + static T_SRDParserDefs makeParseOnlyDefs( ); + static T_SRDParserDefs makeExecDefs( ); + static T_SRDParserDefs makeOLBCDefs( ); + +public: + void testTokenTypes( ); + void testParse( ); + + void testExecStartRuleFinish( ); + void testExecContext( ); + void testExecContextAndRule( ); + void testExecInterruptTop( ); + void testExecInterruptContext( ); + void testExecLoneEnter( ); + void testExecLoneExit( ); + void testExecNoContextHandler( ); + + // Parse with Optional List Before Context + // * Clear choice towards the list + void testParseOLBCList( ); + // * Clear choice towards the context + void testParseOLBCContext( ); + // * List then context, both unambiguous + void testParseOLBCListThenContext( ); + // * Ambiguity between list and context resolved by using the list + void testParseOLBCListOverrides( ); + // * Initial ambiguity between list and context resolved by using the context + void testParseOLBCContextOverrides( ); + // * Neither the list nor the context match the input + void testParseOLBCFailure( ); + + void testErrorTopLevelJunk( ); + void testErrorMismatchInRule( ); + void testErrorContextJunk( ); + void testErrorUnterminatedLists( ); + + void testErrorNoExec( ); + void testErrorInHandlerTop( ); + void testErrorInHandlerContext( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDParserTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +// parse( input , IN/OUT parser , OUT errors ) +void parse( char const* input , T_SRDParser & parser , T_SRDErrors & errors ) +{ + T_SRDLexer lexer( T_String( "test" ) , errors , parser ); + parser.start( errors ); + char const* ptr = input; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); +} + +// parse( input , defs , OUT errors ) - Helper function for the tests +void parse( char const* input , T_SRDParserDefs const& defs , T_SRDErrors & errors ) +{ + T_SRDParserConfig cfg( defs ); + T_SRDParser parser( cfg ); + parse( input , parser , errors ); +} + +} // namespace + +// M_PRINTERR_( index ) - Print an error message +#define M_PRINTERR_(IDX) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + char err[ _e.error( ).size( ) + 1 ]; \ + err[ sizeof( err ) - 1 ] = 0; \ + memcpy( err , _e.error( ).data( ) , \ + sizeof( err ) - 1 ); \ + printf( "ERR %s l. %u c. %lu\n" , err , \ + _e.location( ).line( ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +// M_CKERR_( index , string , line , character ) - Check an error +#define M_CKERR_(IDX,STR,L,C) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ + _e.location( ).line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +// M_PARSE_( defs , input ) - Run the parser using definitions provided by +// SRDParserTest::make{defs}Defs() on the specified input. +#define M_PARSE_( DEFS , INPUT ) \ + T_SRDErrors errors; \ + T_SRDParserConfig cfg( make ##DEFS## Defs( ) ); \ + T_SRDParser parser( cfg ); \ + parse( INPUT , parser , errors ) + + +/*----------------------------------------------------------------------------*/ + +T_SRDParserDefs SRDParserTest::makeParseOnlyDefs( ) +{ + using namespace SRD; + const T_String name( T_String::Pooled( "base" ) ); + const T_String sub( T_String::Pooled( "sub" ) ); + + T_SRDParserDefs d( name ); + d.enumeration( T_String( "enum" ) ) + << "this" << "that"; + d.context( name ) + << ( Rule( ) << "simple" << "test" ) + << ( Rule( ) << Enum( "enum" ) << "works" << ( Opt( ) << "too" ) + << EnterContext( sub ) ) + << ( Rule( ) << Int32( ) << "little" << "piggies" ) + << ( Rule( ) << "too" << "sexy" << "for" << "the" + << ( AtLeast( 0 ) << "wabbit" << "too" << "sexy" << "for" << "the" ) + << "wabbiiiiit" ) + ; + d.context( sub ) + << ( Rule( ) << "sometimes" ); + return d; +} + +T_SRDParserDefs SRDParserTest::makeExecDefs( ) +{ + using namespace SRD; + const T_String name( T_String::Pooled( "base" ) ); + const T_String sub( T_String::Pooled( "sub" ) ); + static const F_SRDHandler start( + []( T_SRDParserData const& d ) -> bool { + *( d.currentData ) = T_StringBuilder( ); + d.currentData->value< T_StringBuilder >( ) << "START"; + return true; + } ); + static const F_SRDHandler finish( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) << " FINISH"; + return true; + } ); + static const F_SRDHandler op( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " OP(" + << (* d.input)[ 1 ].longValue( ) + << ')'; + return true; + } ); + static const F_SRDHandler enter( + []( T_SRDParserData const& d ) -> bool { + *( d.targetData ) = T_StringBuilder( ); + d.targetData->value< T_StringBuilder >( ) + << " ENTER(" << d.targetContext << ')'; + return true; + } ); + static const F_SRDHandler exit( + []( T_SRDParserData const& d ) -> bool { + d.targetData->value< T_StringBuilder >( ) + << d.currentData->value< T_StringBuilder >( ) + << " EXIT(" << d.currentContext << ')'; + return true; + } ); + static const F_SRDHandler ctxExec( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " CTXEXEC(" + << (* d.input)[ 2 ].longValue( ) + << ')'; + return true; + } ); + static const F_SRDHandler failError( + []( T_SRDParserData const& d ) -> bool { + d.errors.add( "Failure on demand!" , (* d.input)[ 0 ] ); + return true; + } ); + static const F_SRDHandler failHandler( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " FAIL"; + return false; + } ); + static const F_SRDHandler exitAlone( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " LONE-EXIT(" << d.currentContext << ')'; + return true; + } ); + static const F_SRDHandler enterAlone( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " LONE-ENTER(" << d.targetContext << ')'; + return true; + } ); + + T_SRDParserDefs d( sub ); + d << OnStart( start ) << OnFinish( finish ); + + d.context( sub ) + << ( Rule( ) << "do-op" << Int32( ) << op ) + << ( Rule( ) << "fail" << "error" << failError ) + << ( Rule( ) << "fail" << "handler" << failHandler ); + + d.context( name , sub ) + << ( Rule( ) << "enter" << EnterContext( sub ) + << OnEnter( enter ) << OnExit( exit ) ) + << ( Rule( ) << "enter" << "exec" << Int32( ) + << ctxExec << EnterContext( sub ) + << OnEnter( enter ) << OnExit( exit ) ) + << ( Rule( ) << "handle" << "nothing" << EnterContext( sub ) ) + << ( Rule( ) << "handle" << "only" << "exit" << EnterContext( sub ) + << OnExit( exitAlone ) ) + << ( Rule( ) << "handle" << "only" << "entrance" << EnterContext( sub ) + << OnEnter( enterAlone ) ); + + d.defaultContext( name ); + return d; +} + +T_SRDParserDefs SRDParserTest::makeOLBCDefs( ) +{ + using namespace SRD; + T_SRDParserDefs d( "base" ); + d << OnStart( + []( T_SRDParserData const& d ) -> bool { + *( d.currentData ) = T_StringBuilder( ); + d.currentData->value< T_StringBuilder >( ) << "START"; + return true; + } ) + << OnFinish( + []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) << " FINISH"; + return true; + } ); + + d.context( "base" ) + << ( Rule( ) << "olbc" + << ( AtLeast( 0 ) << ( List( ) << ( Alt( ) + << "non-context" + << "context" ) ) ) + << EnterContext( "sub" ) + << OnEnter( []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) << " SUB"; + return true; + } ) + << []( T_SRDParserData const& d ) -> bool { + d.currentData->value< T_StringBuilder >( ) + << " EXEC(" + << d.input->size( ) + << ')'; + return true; + } ) + ; + d.context( "sub" ) + << ( Rule( ) << "context" << Opt( Word( "too" ) ) ) + << ( Rule( ) << "in" << "context" ) + ; + return d; +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserTest::testTokenTypes( ) +{ + using F_InitRule = std::function< void( T_SRDContext& ) >; + using namespace lw::SRD; + const F_InitRule rules[] = { + []( T_SRDContext& c ) { c << ( Rule() << Word( ) ); } , + []( T_SRDContext& c ) { c << ( Rule() << String( ) ); } , + []( T_SRDContext& c ) { c << ( Rule() << Integer( ) ); } , + []( T_SRDContext& c ) { c << ( Rule() << Float( ) ); } , + []( T_SRDContext& c ) { c << ( Rule() << Binary( ) ); } , + }; + char const* const inputs[] = { + "(word)" , "(\"string\")" , "(123)" , "(123.0)" , "([])" + }; + const uint32_t nTests( sizeof( inputs ) / sizeof( inputs[ 0 ] ) ); + static_assert( nTests == sizeof( rules ) / sizeof( rules[ 0 ] ) , + "bad tests" ); + + for ( uint32_t i = 0 ; i < nTests ; i ++ ) { + T_SRDParserDefs defs( "default" ); + rules[ i ]( defs.context( "default" ) ); + for ( uint32_t j = 0 ; j < nTests ; j ++ ) { + T_SRDErrors errors; + parse( inputs[ j ] , defs , errors ); + if ( i == j ) { + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + } else { + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + } + } + } +} + +void SRDParserTest::testParse( ) +{ + static const char input[] = + "( simple test )\n" + "( this works )\n" + "( that works too )\n" + "( this works ( sometimes ) )\n" + "( 3 little piggies )\n" + "( too sexy for the wabbit too sexy for the wabbit too sexy for the wabbiiiiit )" + ; + + T_SRDErrors errors; + parse( input , makeParseOnlyDefs( ) , errors ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserTest::testExecStartRuleFinish( ) +{ + M_PARSE_( Exec , "( do-op 2 )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START OP(2) FINISH" ); +} + +void SRDParserTest::testExecContext( ) +{ + M_PARSE_( Exec , "( enter ( do-op 2 ) ( do-op 3 ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( + "START " + "ENTER(sub) " + "OP(2) " + "OP(3) " + "EXIT(sub) " + "FINISH" ) ); +} + +void SRDParserTest::testExecContextAndRule( ) +{ + M_PARSE_( Exec , "( enter exec 4 ( do-op 2 ) ( do-op 3 ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( + "START " + "ENTER(sub) " + "OP(2) " + "OP(3) " + "EXIT(sub) " + "CTXEXEC(4) " + "FINISH" ) ); +} + +void SRDParserTest::testExecInterruptTop( ) +{ + M_PARSE_( Exec , "( do-op 2 ) ( fail handler )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( + "START " + "OP(2) " + "FAIL" ) ); +} + +void SRDParserTest::testExecInterruptContext( ) +{ + M_PARSE_( Exec , "( enter ( do-op 2 ) ( fail handler ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); +} + +void SRDParserTest::testExecLoneEnter( ) +{ + M_PARSE_( Exec , "( handle only entrance ( do-op 2 ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( + "START " + "LONE-ENTER(sub) " + "OP(2) " + "FINISH" ) ); +} + +void SRDParserTest::testExecLoneExit( ) +{ + M_PARSE_( Exec , "( handle only exit ( do-op 2 ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( + "START " + "OP(2) " + "LONE-EXIT(sub) " + "FINISH" ) ); +} + +void SRDParserTest::testExecNoContextHandler( ) +{ + M_PARSE_( Exec , "( handle nothing ( do-op 2 ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START OP(2) FINISH" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserTest::testParseOLBCList( ) +{ + M_PARSE_( OLBC , "( olbc ( non-context ) ( non-context ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START EXEC(3) FINISH" ); +} + +void SRDParserTest::testParseOLBCContext( ) +{ + M_PARSE_( OLBC , "( olbc ( in context ) ( in context ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" ); +} + +void SRDParserTest::testParseOLBCListThenContext( ) +{ + M_PARSE_( OLBC , "( olbc ( non-context ) ( in context ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START SUB EXEC(2) FINISH" ); +} + +void SRDParserTest::testParseOLBCListOverrides( ) +{ + M_PARSE_( OLBC , "( olbc ( context ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START EXEC(2) FINISH" ); +} + +void SRDParserTest::testParseOLBCContextOverrides( ) +{ + M_PARSE_( OLBC , "( olbc ( context too ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( sb == "START SUB EXEC(1) FINISH" ); +} + +void SRDParserTest::testParseOLBCFailure( ) +{ + M_PARSE_( OLBC , "( olbc ( incorrect ) )" ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "'context' or 'in' expected, 'incorrect' found instead." , 1 , 10 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserTest::testErrorTopLevelJunk( ) +{ + static const char input[] = + ")\n" + "( simple test )\n" + "junk\n" + "( this works )"; + ; + + T_SRDErrors errors; + parse( input , makeParseOnlyDefs( ) , errors ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "'(' expected, ')' found instead." , 1 , 1 ); + M_CKERR_( 1 , "'(' expected, 'junk' found instead." , 3 , 1 ); +} + +void SRDParserTest::testErrorMismatchInRule( ) +{ + static const char input[] = + "( simple piggies )\n" + "( tests suck )\n" + "( 3 little piggies ate a farmer )\n" + "( this works when you punch it in the balls )\n" + "( this works ( sort of ) )\n" + ; + + T_SRDErrors errors; + parse( input , makeParseOnlyDefs( ) , errors ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 5 ) , errors.size( ) ); + M_CKERR_( 0 , "'test' expected, 'piggies' found instead." , 1 , 10 ); + M_CKERR_( 1 , "'simple', 'too', 'enum' enum value or INT token expected, 'tests' found instead." , 2 , 3 ); + M_CKERR_( 2 , "')' expected, 'ate' found instead." , 3 , 20 ); + M_CKERR_( 3 , "'(', ')' or 'too' expected, 'when' found instead." , 4 , 14 ); + M_CKERR_( 4 , "'sometimes' expected, 'sort' found instead." , 5 , 16 ); +} + +void SRDParserTest::testErrorContextJunk( ) +{ + static const char input[] = + "( this works (sometimes) when you punch it (sometimes) forcefully )"; + ; + + T_SRDErrors errors; + parse( input , makeParseOnlyDefs( ) , errors ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "'(' or ')' expected, 'when' found instead." , 1 , 26 ); + // Second error because finding a valid rule resets error recovery + M_CKERR_( 1 , "'(' or ')' expected, 'forcefully' found instead." , 1 , 56 ); +} + +void SRDParserTest::testErrorUnterminatedLists( ) +{ + static const char input[] = + "( this works\n" + "( sometimes" + ; + + T_SRDErrors errors; + parse( input , makeParseOnlyDefs( ) , errors ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "Unterminated list." , 1 , 1 ); + M_CKERR_( 1 , "Unterminated list." , 2 , 1 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDParserTest::testErrorNoExec( ) +{ + static const char input[] = + "( enter ( do-op 2 ) ( invalid ) )" + ; + + T_SRDErrors errors; + T_SRDParserConfig cfg( makeExecDefs( ) ); + T_SRDParser parser( cfg ); + parse( input , parser , errors ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + CPPUNIT_ASSERT_THROW( parser.getData< T_StringBuilder >( ) , + std::bad_cast ); +} + +void SRDParserTest::testErrorInHandlerTop( ) +{ + static const char input[] = + "( fail error )" + ; + + T_SRDErrors errors; + T_SRDParserConfig cfg( makeExecDefs( ) ); + T_SRDParser parser( cfg ); + parse( input , parser , errors ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "Failure on demand!" , 1 , 3 ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); +} + +void SRDParserTest::testErrorInHandlerContext( ) +{ + static const char input[] = + "( enter\n( fail error ) )" + ; + + T_SRDErrors errors; + T_SRDParserConfig cfg( makeExecDefs( ) ); + T_SRDParser parser( cfg ); + parse( input , parser , errors ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "Failure on demand!" , 2 , 3 ); + T_StringBuilder sb( parser.getData< T_StringBuilder >( ) ); + CPPUNIT_ASSERT( T_String( sb ) == T_String( "START" ) ); +} diff --git a/tests/srd-preproc-cmd-arithmetic.cc b/tests/srd-preproc-cmd-arithmetic.cc new file mode 100644 index 0000000..8624203 --- /dev/null +++ b/tests/srd-preproc-cmd-arithmetic.cc @@ -0,0 +1,877 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdArithmeticTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdArithmeticTest ); + + CPPUNIT_TEST( testAddNoArgs ); + CPPUNIT_TEST( testAddOneArg ); + CPPUNIT_TEST( testAddIntInt ); + CPPUNIT_TEST( testAddIntLong ); + CPPUNIT_TEST( testAddLongInt ); + CPPUNIT_TEST( testAddLongLong ); + CPPUNIT_TEST( testAddIntFloat ); + CPPUNIT_TEST( testAddLongFloat ); + CPPUNIT_TEST( testAddFloatInt ); + CPPUNIT_TEST( testAddFloatLong ); + CPPUNIT_TEST( testAddFloatFloat ); + CPPUNIT_TEST( testAddManyInt ); + CPPUNIT_TEST( testAddManyFloat ); + CPPUNIT_TEST( testAddManyWithError ); + + CPPUNIT_TEST( testSubNoArgs ); + CPPUNIT_TEST( testSubOneArg ); + CPPUNIT_TEST( testSubIntInt ); + CPPUNIT_TEST( testSubIntLong ); + CPPUNIT_TEST( testSubLongInt ); + CPPUNIT_TEST( testSubLongLong ); + CPPUNIT_TEST( testSubIntFloat ); + CPPUNIT_TEST( testSubLongFloat ); + CPPUNIT_TEST( testSubFloatInt ); + CPPUNIT_TEST( testSubFloatLong ); + CPPUNIT_TEST( testSubFloatFloat ); + CPPUNIT_TEST( testSubManyInt ); + CPPUNIT_TEST( testSubManyFloat ); + CPPUNIT_TEST( testSubManyWithError ); + + CPPUNIT_TEST( testMulNoArgs ); + CPPUNIT_TEST( testMulOneArg ); + CPPUNIT_TEST( testMulIntInt ); + CPPUNIT_TEST( testMulIntLong ); + CPPUNIT_TEST( testMulLongInt ); + CPPUNIT_TEST( testMulLongLong ); + CPPUNIT_TEST( testMulIntFloat ); + CPPUNIT_TEST( testMulLongFloat ); + CPPUNIT_TEST( testMulFloatInt ); + CPPUNIT_TEST( testMulFloatLong ); + CPPUNIT_TEST( testMulFloatFloat ); + CPPUNIT_TEST( testMulManyInt ); + CPPUNIT_TEST( testMulManyFloat ); + CPPUNIT_TEST( testMulManyWithError ); + + CPPUNIT_TEST( testDivNoArgs ); + CPPUNIT_TEST( testDivOneArg ); + CPPUNIT_TEST( testDivIntInt ); + CPPUNIT_TEST( testDivIntLong ); + CPPUNIT_TEST( testDivLongInt ); + CPPUNIT_TEST( testDivLongLong ); + CPPUNIT_TEST( testDivIntFloat ); + CPPUNIT_TEST( testDivLongFloat ); + CPPUNIT_TEST( testDivFloatInt ); + CPPUNIT_TEST( testDivFloatLong ); + CPPUNIT_TEST( testDivFloatFloat ); + CPPUNIT_TEST( testDivIntZero ); + CPPUNIT_TEST( testDivFloatZero ); + CPPUNIT_TEST( testDivManyInt ); + CPPUNIT_TEST( testDivManyFloat ); + CPPUNIT_TEST( testDivManyWithError ); + + CPPUNIT_TEST( testModNoArgs ); + CPPUNIT_TEST( testModOneArg ); + CPPUNIT_TEST( testModIntInt ); + CPPUNIT_TEST( testModIntLong ); + CPPUNIT_TEST( testModLongInt ); + CPPUNIT_TEST( testModLongLong ); + CPPUNIT_TEST( testModIntFloat ); + CPPUNIT_TEST( testModLongFloat ); + CPPUNIT_TEST( testModFloatInt ); + CPPUNIT_TEST( testModFloatLong ); + CPPUNIT_TEST( testModFloatFloat ); + CPPUNIT_TEST( testModIntZero ); + CPPUNIT_TEST( testModFloatZero ); + CPPUNIT_TEST( testModManyInt ); + CPPUNIT_TEST( testModManyFloat ); + CPPUNIT_TEST( testModManyWithError ); + + CPPUNIT_TEST( testNegNoArgs ); + CPPUNIT_TEST( testNegTooManyArgs ); + CPPUNIT_TEST( testNegBadArg ); + CPPUNIT_TEST( testNegInt ); + CPPUNIT_TEST( testNegLong ); + CPPUNIT_TEST( testNegReal ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testAddNoArgs( ); + void testAddOneArg( ); + void testAddIntInt( ); + void testAddIntLong( ); + void testAddLongInt( ); + void testAddLongLong( ); + void testAddIntFloat( ); + void testAddLongFloat( ); + void testAddFloatInt( ); + void testAddFloatLong( ); + void testAddFloatFloat( ); + void testAddManyInt( ); + void testAddManyFloat( ); + void testAddManyWithError( ); + + void testSubNoArgs( ); + void testSubOneArg( ); + void testSubIntInt( ); + void testSubIntLong( ); + void testSubLongInt( ); + void testSubLongLong( ); + void testSubIntFloat( ); + void testSubLongFloat( ); + void testSubFloatInt( ); + void testSubFloatLong( ); + void testSubFloatFloat( ); + void testSubManyInt( ); + void testSubManyFloat( ); + void testSubManyWithError( ); + + void testMulNoArgs( ); + void testMulOneArg( ); + void testMulIntInt( ); + void testMulIntLong( ); + void testMulLongInt( ); + void testMulLongLong( ); + void testMulIntFloat( ); + void testMulLongFloat( ); + void testMulFloatInt( ); + void testMulFloatLong( ); + void testMulFloatFloat( ); + void testMulManyInt( ); + void testMulManyFloat( ); + void testMulManyWithError( ); + + void testDivNoArgs( ); + void testDivOneArg( ); + void testDivIntInt( ); + void testDivIntLong( ); + void testDivLongInt( ); + void testDivLongLong( ); + void testDivIntFloat( ); + void testDivLongFloat( ); + void testDivFloatInt( ); + void testDivFloatLong( ); + void testDivFloatFloat( ); + void testDivIntZero( ); + void testDivFloatZero( ); + void testDivManyInt( ); + void testDivManyFloat( ); + void testDivManyWithError( ); + + void testModNoArgs( ); + void testModOneArg( ); + void testModIntInt( ); + void testModIntLong( ); + void testModLongInt( ); + void testModLongLong( ); + void testModIntFloat( ); + void testModLongFloat( ); + void testModFloatInt( ); + void testModFloatLong( ); + void testModFloatFloat( ); + void testModIntZero( ); + void testModFloatZero( ); + void testModManyInt( ); + void testModManyFloat( ); + void testModManyWithError( ); + + void testNegNoArgs( ); + void testNegTooManyArgs( ); + void testNegBadArg( ); + void testNegInt( ); + void testNegLong( ); + void testNegReal( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdArithmeticTest ); + +/*----------------------------------------------------------------------------*/ + +#define M_MKTEST_( OP , A1 , A2 ) \ + "( -set out ( -" OP " " A1 " " A2 " ) )\n" \ + "( -type-of $out )\n" \ + "( -if ( -eq ( -type-of $out ) long ) ( (-to-best-integer $out) ) ($out) )" + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testAddNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -add )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddOneArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -add 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "1" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 2" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddIntLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "1" , "12123456789" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12123456790" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "12123456789" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12123456790" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "12123456789" , "( -to-long 1 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12123456790" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddIntFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "1" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddLongFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , "( -to-long 1 )" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddFloatInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , ".5" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddFloatLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , ".5" , "( -to-long 1 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddFloatFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "add" , ".5" , "1.5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 2.0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddManyInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -add 1 2 3 4 5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "15" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddManyFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -add 1 .5 .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1.75" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testAddManyWithError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -add 1 ( ) .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real number expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "1.25" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testSubNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -sub )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubOneArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -sub 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "1" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubIntLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "1" , "12123456789" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long -12123456788" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "12123456789" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12123456788" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "12123456789" , "( -to-long 1 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12123456788" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubIntFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "1" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubLongFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , "( -to-long 1 )" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubFloatInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , ".5" , "1" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real -.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubFloatLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , ".5" , "( -to-long 1 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real -.5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubFloatFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "sub" , ".5" , "1.5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real -1.0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubManyInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -sub 20 1 2 3 4 5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubManyFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -sub 1 .5 .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( ".25" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testSubManyWithError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -sub 1 ( ) .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real number expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( ".75" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testMulNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mul )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulOneArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mul 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "2" , "3" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 6" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulIntLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "2" , "6000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "6000000000" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "6000000000" , "( -to-long 2 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 12000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulIntFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "2" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulLongFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , "( -to-long 2 )" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulFloatInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , ".5" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulFloatLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , ".5" , "( -to-long 2 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulFloatFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mul" , ".5" , "1.5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .75" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulManyInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mul 2 3 4 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "24" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulManyFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mul 2. .5 .1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( ".1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testMulManyWithError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mul 2 ( ) .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real number expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( ".5" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testDivNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivOneArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "4" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 2" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivIntLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "200" , "( -to-long 10 )" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 20" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "12000000000" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 6000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "12000000000" , "6000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 2" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivIntFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "2" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 4." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivLongFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "6000000000" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 12e9" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivFloatInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , ".5" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .25" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivFloatLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "12e9" , "6000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 2." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivFloatFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "div" , "1.5" , ".5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 3." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivIntZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 10 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "non-zero argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "10" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivFloatZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 2. 0. )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "non-zero argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "2." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivManyInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 20 2 5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "2" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivManyFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 2. .5 4. )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testDivManyWithError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -div 2 ( ) .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real number expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "8." , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testModNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModOneArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "4" , "3" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModIntLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "20" , "6000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 20" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "6000000000" , "7" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int 1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "12000000000" , "7000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long 5000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModIntFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "2" , "1.5" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .5" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModLongFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "6000000000" , "1e10" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 6e9" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModFloatInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "3.0" , "2" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 1." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModFloatLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "12e9" , "7000000000" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real 5e9" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModFloatFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( M_MKTEST_( "mod" , "1.5" , ".6" ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real .3" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModIntZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 10 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "non-zero argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "10" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModFloatZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 2. 0. )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "non-zero argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "2." , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModManyInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 23 5 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModManyFloat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 2. .6 .15 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( ".05" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testModManyWithError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -mod 2 ( ) .25 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real number expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "0." , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdArithmeticTest::testNegNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg )" , errors ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); +} + +void SRDPreprocCmdArithmeticTest::testNegTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg 1 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "-1" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testNegBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer or real argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testNegInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-12" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testNegLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg 12000000000 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-12000000000" , output ) ); +} + +void SRDPreprocCmdArithmeticTest::testNegReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -neg -1.2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1.2" , output ) ); +} + diff --git a/tests/srd-preproc-cmd-casts.cc b/tests/srd-preproc-cmd-casts.cc new file mode 100644 index 0000000..dc02135 --- /dev/null +++ b/tests/srd-preproc-cmd-casts.cc @@ -0,0 +1,1100 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdCastsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdCastsTest ); + + CPPUNIT_TEST( testToStringEmpty ); + CPPUNIT_TEST( testToStringString ); + CPPUNIT_TEST( testToStringWord ); + CPPUNIT_TEST( testToStringInt ); + CPPUNIT_TEST( testToStringLong ); + CPPUNIT_TEST( testToStringReal ); + CPPUNIT_TEST( testToStringVar ); + CPPUNIT_TEST( testToStringList ); + CPPUNIT_TEST( testToStringBinary ); + CPPUNIT_TEST( testToStringBinaryInvalid ); + CPPUNIT_TEST( testToStringMultiple ); + + CPPUNIT_TEST( testToWordEmpty ); + CPPUNIT_TEST( testToWordStringOk ); + CPPUNIT_TEST( testToWordStringBad ); + CPPUNIT_TEST( testToWordWord ); + CPPUNIT_TEST( testToWordInt ); + CPPUNIT_TEST( testToWordLong ); + CPPUNIT_TEST( testToWordReal ); + CPPUNIT_TEST( testToWordVar ); + CPPUNIT_TEST( testToWordList ); + CPPUNIT_TEST( testToWordMultiple ); + + CPPUNIT_TEST( testToIntEmpty ); + CPPUNIT_TEST( testToIntStringOk ); + CPPUNIT_TEST( testToIntStringBad ); + CPPUNIT_TEST( testToIntWord ); + CPPUNIT_TEST( testToIntInt ); + CPPUNIT_TEST( testToIntLongOk ); + CPPUNIT_TEST( testToIntLongBad ); + CPPUNIT_TEST( testToIntReal ); + CPPUNIT_TEST( testToIntVar ); + CPPUNIT_TEST( testToIntList ); + CPPUNIT_TEST( testToIntMultiple ); + + CPPUNIT_TEST( testToLongEmpty ); + CPPUNIT_TEST( testToLongStringOk ); + CPPUNIT_TEST( testToLongStringBad ); + CPPUNIT_TEST( testToLongWord ); + CPPUNIT_TEST( testToLongInt ); + CPPUNIT_TEST( testToLongLong ); + CPPUNIT_TEST( testToLongReal ); + CPPUNIT_TEST( testToLongVar ); + CPPUNIT_TEST( testToLongList ); + CPPUNIT_TEST( testToLongMultiple ); + + CPPUNIT_TEST( testToBestIntEmpty ); + CPPUNIT_TEST( testToBestIntString32 ); + CPPUNIT_TEST( testToBestIntString64 ); + CPPUNIT_TEST( testToBestIntStringBad ); + CPPUNIT_TEST( testToBestIntWord ); + CPPUNIT_TEST( testToBestIntInt ); + CPPUNIT_TEST( testToBestIntLong32 ); + CPPUNIT_TEST( testToBestIntLong64 ); + CPPUNIT_TEST( testToBestIntReal32 ); + CPPUNIT_TEST( testToBestIntReal64 ); + CPPUNIT_TEST( testToBestIntVar ); + CPPUNIT_TEST( testToBestIntList ); + CPPUNIT_TEST( testToBestIntMultiple ); + + CPPUNIT_TEST( testToRealEmpty ); + CPPUNIT_TEST( testToRealStringOk ); + CPPUNIT_TEST( testToRealStringBad ); + CPPUNIT_TEST( testToRealWord ); + CPPUNIT_TEST( testToRealInt ); + CPPUNIT_TEST( testToRealLong ); + CPPUNIT_TEST( testToRealReal ); + CPPUNIT_TEST( testToRealVar ); + CPPUNIT_TEST( testToRealList ); + CPPUNIT_TEST( testToRealMultiple ); + + CPPUNIT_TEST( testToVarEmpty ); + CPPUNIT_TEST( testToVarStringOk ); + CPPUNIT_TEST( testToVarStringOkWithDollar ); + CPPUNIT_TEST( testToVarStringBadWord ); + CPPUNIT_TEST( testToVarStringBadVarName ); + CPPUNIT_TEST( testToVarWordOk ); + CPPUNIT_TEST( testToVarWordBad ); + CPPUNIT_TEST( testToVarInt ); + CPPUNIT_TEST( testToVarLong ); + CPPUNIT_TEST( testToVarReal ); + CPPUNIT_TEST( testToVarVar ); + CPPUNIT_TEST( testToVarList ); + CPPUNIT_TEST( testToVarMultiple ); + + CPPUNIT_TEST( testToListEmpty ); + CPPUNIT_TEST( testToListString ); + CPPUNIT_TEST( testToListStringEmpty ); + CPPUNIT_TEST( testToListWord ); + CPPUNIT_TEST( testToListBinary ); + CPPUNIT_TEST( testToListBinaryEmpty ); + CPPUNIT_TEST( testToListList ); + CPPUNIT_TEST( testToListListEmpty ); + CPPUNIT_TEST( testToListOtherTypes ); + + CPPUNIT_TEST( testToSourceEmpty ); + CPPUNIT_TEST( testToSourceWord ); + CPPUNIT_TEST( testToSourceString ); + CPPUNIT_TEST( testToSourceInt ); + CPPUNIT_TEST( testToSourceLong ); + CPPUNIT_TEST( testToSourceReal ); + CPPUNIT_TEST( testToSourceVar ); + CPPUNIT_TEST( testToSourceList ); + CPPUNIT_TEST( testToSourceMultiple ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testToStringEmpty( ); + void testToStringString( ); + void testToStringWord( ); + void testToStringInt( ); + void testToStringLong( ); + void testToStringReal( ); + void testToStringVar( ); + void testToStringList( ); + void testToStringBinary( ); + void testToStringBinaryInvalid( ); + void testToStringMultiple( ); + + void testToWordEmpty( ); + void testToWordStringOk( ); + void testToWordStringBad( ); + void testToWordWord( ); + void testToWordInt( ); + void testToWordLong( ); + void testToWordReal( ); + void testToWordVar( ); + void testToWordList( ); + void testToWordMultiple( ); + + void testToIntEmpty( ); + void testToIntStringOk( ); + void testToIntStringBad( ); + void testToIntWord( ); + void testToIntInt( ); + void testToIntLongOk( ); + void testToIntLongBad( ); + void testToIntReal( ); + void testToIntVar( ); + void testToIntList( ); + void testToIntMultiple( ); + + void testToLongEmpty( ); + void testToLongStringOk( ); + void testToLongStringBad( ); + void testToLongWord( ); + void testToLongInt( ); + void testToLongLong( ); + void testToLongReal( ); + void testToLongVar( ); + void testToLongList( ); + void testToLongMultiple( ); + + void testToBestIntEmpty( ); + void testToBestIntString32( ); + void testToBestIntString64( ); + void testToBestIntStringBad( ); + void testToBestIntWord( ); + void testToBestIntInt( ); + void testToBestIntLong32( ); + void testToBestIntLong64( ); + void testToBestIntReal32( ); + void testToBestIntReal64( ); + void testToBestIntVar( ); + void testToBestIntList( ); + void testToBestIntMultiple( ); + + void testToRealEmpty( ); + void testToRealStringOk( ); + void testToRealStringBad( ); + void testToRealWord( ); + void testToRealInt( ); + void testToRealLong( ); + void testToRealReal( ); + void testToRealVar( ); + void testToRealList( ); + void testToRealMultiple( ); + + void testToVarEmpty( ); + void testToVarStringOk( ); + void testToVarStringOkWithDollar( ); + void testToVarStringBadWord( ); + void testToVarStringBadVarName( ); + void testToVarWordOk( ); + void testToVarWordBad( ); + void testToVarInt( ); + void testToVarLong( ); + void testToVarReal( ); + void testToVarVar( ); + void testToVarList( ); + void testToVarMultiple( ); + + void testToListEmpty( ); + void testToListString( ); + void testToListStringEmpty( ); + void testToListWord( ); + void testToListBinary( ); + void testToListBinaryEmpty( ); + void testToListList( ); + void testToListListEmpty( ); + void testToListOtherTypes( ); + + void testToSourceEmpty( ); + void testToSourceWord( ); + void testToSourceString( ); + void testToSourceInt( ); + void testToSourceLong( ); + void testToSourceReal( ); + void testToSourceVar( ); + void testToSourceList( ); + void testToSourceMultiple( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdCastsTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToStringEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string \"a string\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"a string\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"word\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"1\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string 12123456789 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"12123456789\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string 0.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"0.5\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string ( -raw $a ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"$a\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string [ 4c 4f 4c ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"LOL\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringBinaryInvalid( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string [ 80 4c 4f 4c e5 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid UTF-8 data" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToStringMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-string \"a\" b ( ) ( -raw $c ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "\"a\" \"b\" \"$c\"" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToWordEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordStringOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word \"a-word\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "a-word" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word \"not a word, lol\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid word" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "word" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word 12123456789 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word 0.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToWordMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-word ( ) \"a\" ( -raw $b ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "invalid a b" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToIntEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntStringOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer \"123\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer \"123nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid integer value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer 123 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntLongOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer ( -to-long 123 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntLongBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer 12345678912 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "value out of range" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "2147483647" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer 0.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 22 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToIntMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-integer ( ) 1 \"2\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0 1 2" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToLongEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToLongStringOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long \"12345678912\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12345678912" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToLongStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long \"123nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid integer value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , output[ 0 ].longValue( ) ); +} + +void SRDPreprocCmdCastsTest::testToLongWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , output[ 0 ].longValue( ) ); +} + +void SRDPreprocCmdCastsTest::testToLongInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long 123 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , output[ 0 ].longValue( ) ); +} + +void SRDPreprocCmdCastsTest::testToLongLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long 12345678912 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12345678912" , output ) ); +} + + +void SRDPreprocCmdCastsTest::testToLongReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long 1e10 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "10000000000" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToLongVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 19 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , output[ 0 ].longValue( ) ); +} + +void SRDPreprocCmdCastsTest::testToLongList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , output[ 0 ].longValue( ) ); +} + +void SRDPreprocCmdCastsTest::testToLongMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-long ( ) 12345678912 \"12123456789\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT_EQUAL( 3u , output.size( ) ); + CPPUNIT_ASSERT( output[ 0 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , output[ 0 ].longValue( ) ); + CPPUNIT_ASSERT( output[ 1 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 12345678912L ) , output[ 1 ].longValue( ) ); + CPPUNIT_ASSERT( output[ 2 ].type( ) == E_SRDTokenType::LONG ); + CPPUNIT_ASSERT_EQUAL( int64_t( 12123456789L ) , output[ 2 ].longValue( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToBestIntEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntString32( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer \"123456\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123456" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntString64( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer \"12345678912\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12345678912" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer \"123nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid integer value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer 123 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntLong32( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer ( -to-long 123 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "123" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntLong64( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer 12345678912 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12345678912" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntReal32( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer 12.3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntReal64( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer 1e10 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "10000000000" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 27 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToBestIntMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-best-integer ( ) \"123\" 456 78912345678 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0 123 456 78912345678" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToRealEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealStringOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real \"-12e-3\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-0.012" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real \"nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid real value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "0." , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "0." , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12.0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real 12345678912 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "12345678912.0" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real 1234567891.2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1234567891.2" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 19 ); + CPPUNIT_ASSERT( check( "0." , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "0." , output ) ); +} + +void SRDPreprocCmdCastsTest::testToRealMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-real ( ) 123 456.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "0. 123.0 456.5" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToVarEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarStringOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable \"name\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "$name" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarStringOkWithDollar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable \"$name\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "$name" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarStringBadWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable \"not a word\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid variable name" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarStringBadVarName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable \"-bad\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid variable name" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarWordOk( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable name )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "$name" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarWordBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable -bad )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid variable name" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable 12345678912 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable 1.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "$x" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToVarMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-variable ( ) \"$x\" y )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unsupported type conversion" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "$invalid $x $y" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToListEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"a\" \"b\" \"c\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListStringEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list \"\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list abc )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"a\" \"b\" \"c\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list [ deadbeef ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "222 173 190 239" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListBinaryEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list [ ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list ( ab c ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "ab c" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListListEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToListOtherTypes( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-list 10 1.2 (-raw $x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "10 1.2 $x" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCastsTest::testToSourceEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source word )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"word\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source \"some\\nstring\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\\\"some\\\\nstring\\\"\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source 123 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"123\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source 5000000000 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"5000000000\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source 1.2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"1.2\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -to-source (-raw $x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"$x\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-to-source ( word \"string\" 123 5000000000 1.2 (-raw $x) ))" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"( word \\\"string\\\" 123 5000000000 1.2 $x )\"" , output ) ); +} + +void SRDPreprocCmdCastsTest::testToSourceMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-to-source word \"string\" 123 5000000000 1.2 (-raw $x) ( some list ))" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"word \\\"string\\\" 123 5000000000 1.2 $x ( some list )\"" , output ) ); +} diff --git a/tests/srd-preproc-cmd-common.hh b/tests/srd-preproc-cmd-common.hh new file mode 100644 index 0000000..a6990d7 --- /dev/null +++ b/tests/srd-preproc-cmd-common.hh @@ -0,0 +1,108 @@ +#ifndef TESTS_SRDPREPROCCMDCOMMON_H_ +#define TESTS_SRDPREPROCCMDCOMMON_H_ + +#include +#include +#include +#include +#include +using namespace lw; + +// M_PRINTERR_( index ) - Print an error message +#define M_PRINTERR_( IDX ) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + char err[ _e.error( ).size( ) + 1 ]; \ + err[ sizeof( err ) - 1 ] = 0; \ + memcpy( err , _e.error( ).data( ) , \ + sizeof( err ) - 1 ); \ + printf( "ERR %s l. %u c. %lu\n" , err , \ + _e.location( ).line( ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +// M_CKERR_( index , string , line , character ) - Check an error +#define M_CKERR_( IDX , STR , L , C ) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ + _e.location( ).line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +namespace { + +void process( char const* input , T_SRDPreprocessor& preproc , T_SRDErrors& errors ) +{ + T_SRDLexer lexer( T_String( "test" ) , errors , preproc ); + preproc.start( errors ); + char const* ptr = input; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); +} + +T_SRDList process( char const* input , T_SRDErrors& errors ) +{ + T_SRDMemoryTarget mt( false ); + mt.clearFlushToken( true ); + mt.start( errors ); + + T_SRDPreprocessorConfig emptyConfig; + emptyConfig.addBuiltinCommands( ); + T_SRDPreprocessor pp( emptyConfig , mt ); + process( input , pp , errors ); + mt.end( errors ); + + return mt.list( ); +} + +bool checkMatch( T_SRDList const& expected , T_SRDList const& actual ) +{ + const size_t nExpected( expected.size( ) ); + const size_t nActual( actual.size( ) ); + + bool ok( nExpected == nActual ); + const size_t nCheck( std::min( nExpected , nActual ) ); + for ( size_t i = 0 ; i < nCheck ; i ++ ) { + T_SRDToken const& tExpected( expected[ i ] ); + T_SRDToken const& tActual( actual[ i ] ); + if ( tExpected.type( ) != tActual.type( ) ) { + ok = false; + } else if ( tExpected.stringValue( ) != tActual.stringValue( ) ) { + ok = false; + } + } + return ok; +} + +bool check( char const* expected , T_SRDList const& actual ) +{ + T_SRDMemoryTarget mt( false ); + T_SRDErrors errors; + T_SRDLexer lexer( T_String( "expected" ) , errors , mt ); + mt.start( errors ); + char const* ptr = expected; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); + + const bool ok( checkMatch( mt.list( ) , actual ) ); + if ( ok ) { + return true; + } + T_Buffer< char > output; + T_MemoryOutputStream stream{ output }; + lw::SRDWriteAsText( stream , actual ); + stream.write( "" , 1 ); + std::cerr << "\nExpected:\n\t" << expected << "\nActual:\n" << output.data( ) << '\n'; + return false; +} + +} + +#endif // TESTS_SRDPREPROCCMDCOMMON_H_ diff --git a/tests/srd-preproc-cmd-compare.cc b/tests/srd-preproc-cmd-compare.cc new file mode 100644 index 0000000..8d924e7 --- /dev/null +++ b/tests/srd-preproc-cmd-compare.cc @@ -0,0 +1,340 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdCompareTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdCompareTest ); + + CPPUNIT_TEST( testCmpMissingArgs ); + CPPUNIT_TEST( testCmpExtraArgs ); + CPPUNIT_TEST( testCmp ); + + CPPUNIT_TEST( testEqMissingArgs ); + CPPUNIT_TEST( testEqExtraArgs ); + CPPUNIT_TEST( testEq ); + + CPPUNIT_TEST( testNeMissingArgs ); + CPPUNIT_TEST( testNeExtraArgs ); + CPPUNIT_TEST( testNe ); + + CPPUNIT_TEST( testLtMissingArgs ); + CPPUNIT_TEST( testLtExtraArgs ); + CPPUNIT_TEST( testLt ); + + CPPUNIT_TEST( testGtMissingArgs ); + CPPUNIT_TEST( testGtExtraArgs ); + CPPUNIT_TEST( testGt ); + + CPPUNIT_TEST( testLeMissingArgs ); + CPPUNIT_TEST( testLeExtraArgs ); + CPPUNIT_TEST( testLe ); + + CPPUNIT_TEST( testGeMissingArgs ); + CPPUNIT_TEST( testGeExtraArgs ); + CPPUNIT_TEST( testGe ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testCmpMissingArgs( ); + void testCmpExtraArgs( ); + void testCmp( ); + + void testEqMissingArgs( ); + void testEqExtraArgs( ); + void testEq( ); + + void testNeMissingArgs( ); + void testNeExtraArgs( ); + void testNe( ); + + void testLtMissingArgs( ); + void testLtExtraArgs( ); + void testLt( ); + + void testGtMissingArgs( ); + void testGtExtraArgs( ); + void testGt( ); + + void testLeMissingArgs( ); + void testLeExtraArgs( ); + void testLe( ); + + void testGeMissingArgs( ); + void testGeExtraArgs( ); + void testGe( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdCompareTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testCmpMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -cmp )\n( -cmp a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "0 0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testCmpExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -cmp a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "-1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testCmp( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -cmp a a )\n" + "( -cmp a b )\n" + "( -cmp ((a)) b )\n" + "( -cmp a ((b)) )\n" + "( -cmp ((a)) ((b)) )\n" + "( -cmp ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0 -1 -1 1 -1 0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testEqMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -eq )\n( -eq a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "1 1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testEqExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -eq a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testEq( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -eq a a )\n" + "( -eq a b )\n" + "( -eq ((a)) b )\n" + "( -eq a ((b)) )\n" + "( -eq ((a)) ((b)) )\n" + "( -eq ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 0 0 0 0 1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testNeMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ne )\n( -ne a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "0 0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testNeExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ne a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testNe( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -ne a a )\n" + "( -ne a b )\n" + "( -ne ((a)) b )\n" + "( -ne a ((b)) )\n" + "( -ne ((a)) ((b)) )\n" + "( -ne ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0 1 1 1 1 0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testLtMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -lt )\n( -lt a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "0 0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testLtExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -lt a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testLt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -lt a a )\n" + "( -lt a b )\n" + "( -lt ((a)) b )\n" + "( -lt a ((b)) )\n" + "( -lt ((a)) ((b)) )\n" + "( -lt ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0 1 1 0 1 0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testGtMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -gt )\n( -gt a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "0 0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testGtExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -gt a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testGt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -gt a a )\n" + "( -gt a b )\n" + "( -gt ((a)) b )\n" + "( -gt a ((b)) )\n" + "( -gt ((a)) ((b)) )\n" + "( -gt ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0 0 0 1 0 0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testLeMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -le )\n( -le a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "1 1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testLeExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -le a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testLe( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -le a a )\n" + "( -le a b )\n" + "( -le ((a)) b )\n" + "( -le a ((b)) )\n" + "( -le ((a)) ((b)) )\n" + "( -le ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 1 1 0 1 1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCompareTest::testGeMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ge )\n( -ge a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + M_CKERR_( 2 , "not enough arguments" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "1 1" , output ) ); +} + +void SRDPreprocCmdCompareTest::testGeExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ge a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdCompareTest::testGe( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -ge a a )\n" + "( -ge a b )\n" + "( -ge ((a)) b )\n" + "( -ge a ((b)) )\n" + "( -ge ((a)) ((b)) )\n" + "( -ge ((a)) ((a)) )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 0 0 1 0 1" , output ) ); +} diff --git a/tests/srd-preproc-cmd-core.cc b/tests/srd-preproc-cmd-core.cc new file mode 100644 index 0000000..de8826f --- /dev/null +++ b/tests/srd-preproc-cmd-core.cc @@ -0,0 +1,1303 @@ +#include "srd-preproc-cmd-common.hh" +#include "srd-preproc-location.hh" + + +class SRDPreprocCmdCoreTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdCoreTest ); + + CPPUNIT_TEST( testRaw ); + CPPUNIT_TEST( testRawEmpty ); + CPPUNIT_TEST( testRawSkipComments ); + CPPUNIT_TEST( testRawUnterminated ); + + CPPUNIT_TEST( testIfMissingArgs ); + CPPUNIT_TEST( testIfExtraArgs ); + CPPUNIT_TEST( testIfBadCondition ); + CPPUNIT_TEST( testIfBadThenBlock ); + CPPUNIT_TEST( testIfBadElseBlock ); + CPPUNIT_TEST( testIfTrueThen ); + CPPUNIT_TEST( testIfFalseThen ); + CPPUNIT_TEST( testIfTrueThenElse ); + CPPUNIT_TEST( testIfFalseThenElse ); + CPPUNIT_TEST( testIfArgSubst ); + CPPUNIT_TEST( testIfThenInnerList ); + CPPUNIT_TEST( testIfElseInnerList ); + + CPPUNIT_TEST( testEvalEmpty ); + CPPUNIT_TEST( testEval ); + CPPUNIT_TEST( testEvalErrors ); + CPPUNIT_TEST( testEvalSkipComments ); + CPPUNIT_TEST( testEvalUnterminated ); + CPPUNIT_TEST( testEvalInnerList ); + CPPUNIT_TEST( testEvalCalls ); + + CPPUNIT_TEST( testScope ); + CPPUNIT_TEST( testScopeValues ); + CPPUNIT_TEST( testScopeInnerList ); + + CPPUNIT_TEST( testClearScopeInstruction ); + CPPUNIT_TEST( testClearScopeBlock ); + CPPUNIT_TEST( testClearScopeInnerList ); + + CPPUNIT_TEST( testTryEmpty ); + CPPUNIT_TEST( testTryNoErrors ); + CPPUNIT_TEST( testTryErrors ); + CPPUNIT_TEST( testTryErrorsChained ); + CPPUNIT_TEST( testTryUnterminated ); + CPPUNIT_TEST( testTryTooManyErrors ); + CPPUNIT_TEST( testTryInnerList ); + + CPPUNIT_TEST( testOutput ); + CPPUNIT_TEST( testOutputErrors ); + + CPPUNIT_TEST( testErrorEmpty ); + CPPUNIT_TEST( testErrorWord ); + CPPUNIT_TEST( testErrorString ); + CPPUNIT_TEST( testErrorVar ); + CPPUNIT_TEST( testErrorInt ); + CPPUNIT_TEST( testErrorLong ); + CPPUNIT_TEST( testErrorReal ); + CPPUNIT_TEST( testErrorList ); + CPPUNIT_TEST( testErrorConcat ); + + CPPUNIT_TEST( testBreakMain ); + CPPUNIT_TEST( testBreakMainList ); + CPPUNIT_TEST( testBreakEvalInput ); + CPPUNIT_TEST( testBreakEvalOutput ); + CPPUNIT_TEST( testBreakFunction ); + CPPUNIT_TEST( testBreakRecursiveFunction ); + CPPUNIT_TEST( testBreakMacro ); + CPPUNIT_TEST( testBreakArgs ); + + CPPUNIT_TEST( testRethrowNothing ); + CPPUNIT_TEST( testRethrowList ); + CPPUNIT_TEST( testRethrowEmptyList ); + CPPUNIT_TEST( testRethrowUnwrapped ); + CPPUNIT_TEST( testRethrowErrorWord ); + CPPUNIT_TEST( testRethrowSourceWord ); + CPPUNIT_TEST( testRethrowDetails ); + CPPUNIT_TEST( testRethrowChained ); + CPPUNIT_TEST( testRethrowChainedDepth ); + + CPPUNIT_TEST( testRethrowNoList ); + CPPUNIT_TEST( testRethrowBadMessage ); + CPPUNIT_TEST( testRethrowNoLocation ); + CPPUNIT_TEST( testRethrowBadLocation ); + CPPUNIT_TEST( testRethrowLocationBadSource ); + CPPUNIT_TEST( testRethrowLocationBadLine ); + CPPUNIT_TEST( testRethrowLocationNegativeLine ); + CPPUNIT_TEST( testRethrowLocationLineTooHigh ); + CPPUNIT_TEST( testRethrowLocationBadChar ); + CPPUNIT_TEST( testRethrowLocationNegativeChar ); + CPPUNIT_TEST( testRethrowChainedBad ); + CPPUNIT_TEST( testRethrowChainedInvalid ); + CPPUNIT_TEST( testRethrowChainedNoLocation ); + CPPUNIT_TEST( testRethrowChainedBadLocation ); + CPPUNIT_TEST( testRethrowChainedNegativeDepth ); + CPPUNIT_TEST( testRethrowChainedDepthTooHigh ); + CPPUNIT_TEST( testRethrowChainedDepthNoLocation ); + CPPUNIT_TEST( testRethrowChainedDepthBadLocation ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testRaw( ); + void testRawEmpty( ); + void testRawSkipComments( ); + void testRawUnterminated( ); + + void testIfMissingArgs( ); + void testIfExtraArgs( ); + void testIfBadCondition( ); + void testIfBadThenBlock( ); + void testIfBadElseBlock( ); + void testIfTrueThen( ); + void testIfFalseThen( ); + void testIfTrueThenElse( ); + void testIfFalseThenElse( ); + void testIfArgSubst( ); + void testIfThenInnerList( ); + void testIfElseInnerList( ); + + void testEvalEmpty( ); + void testEval( ); + void testEvalErrors( ); + void testEvalSkipComments( ); + void testEvalUnterminated( ); + void testEvalInnerList( ); + void testEvalCalls( ); + + void testScope( ); + void testScopeValues( ); + void testScopeInnerList( ); + + void testClearScopeInstruction( ); + void testClearScopeBlock( ); + void testClearScopeInnerList( ); + + void testTryEmpty( ); + void testTryNoErrors( ); + void testTryErrors( ); + void testTryErrorsChained( ); + void testTryUnterminated( ); + void testTryTooManyErrors( ); + void testTryInnerList( ); + + void testOutput( ); + void testOutputErrors( ); + + void testErrorEmpty( ); + void testErrorWord( ); + void testErrorString( ); + void testErrorVar( ); + void testErrorInt( ); + void testErrorLong( ); + void testErrorReal( ); + void testErrorList( ); + void testErrorConcat( ); + + void testBreakMain( ); + void testBreakMainList( ); + void testBreakEvalInput( ); + void testBreakEvalOutput( ); + void testBreakFunction( ); + void testBreakRecursiveFunction( ); + void testBreakMacro( ); + void testBreakArgs( ); + + void testRethrowNothing( ); + void testRethrowList( ); + void testRethrowEmptyList( ); + void testRethrowUnwrapped( ); + void testRethrowErrorWord( ); + void testRethrowSourceWord( ); + void testRethrowDetails( ); + void testRethrowChained( ); + void testRethrowChainedDepth( ); + + void testRethrowNoList( ); + void testRethrowBadMessage( ); + void testRethrowNoLocation( ); + void testRethrowBadLocation( ); + void testRethrowLocationBadSource( ); + void testRethrowLocationBadLine( ); + void testRethrowLocationNegativeLine( ); + void testRethrowLocationLineTooHigh( ); + void testRethrowLocationBadChar( ); + void testRethrowLocationNegativeChar( ); + void testRethrowChainedBad( ); + void testRethrowChainedInvalid( ); + void testRethrowChainedNoLocation( ); + void testRethrowChainedBadLocation( ); + void testRethrowChainedNegativeDepth( ); + void testRethrowChainedDepthTooHigh( ); + void testRethrowChainedDepthNoLocation( ); + void testRethrowChainedDepthBadLocation( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdCoreTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testRaw( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -raw ok $x ( -raw ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "ok $x ( -raw )" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 8 , 0 ); + M_CKTOKFULL_( 1 , "test" , 1 , 11 , 0 ); + M_CKTOKFULL_( 2 , "test" , 1 , 14 , 0 ); + M_CKTOKFULL_( 3 , "test" , 1 , 16 , 0 ); + M_CKTOKFULL_( 4 , "test" , 1 , 21 , 0 ); +} + +void SRDPreprocCmdCoreTest::testRawEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -raw )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRawSkipComments( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -raw { a comment } x # another comment!\n)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 22 , 0 ); +} + +void SRDPreprocCmdCoreTest::testRawUnterminated( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -raw\n(\n(" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERRFULL_( 0 , "unterminated list" , false , "test" , 1 , 1 , 0 ); + M_CKERRFULL_( 1 , "unterminated list" , false , "test" , 2 , 1 , 0 ); + M_CKERRFULL_( 2 , "unterminated list" , false , "test" , 3 , 1 , 0 ); + CPPUNIT_ASSERT( check( "(())" , output ) ); + M_CKTOKFULL_( 0 , "test" , 2 , 1 , 0 ); + M_CKTOKFULL_( 1 , "test" , 3 , 1 , 0 ); + M_CKTOKFULL_( 2 , "*unexpected end of file*" , 0 , 2 , 0 ); + M_CKTOKFULL_( 3 , "*unexpected end of file*" , 0 , 1 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testIfMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if )\n( -if 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERRFULL_( 0 , "not enough arguments" , false , "test" , 1 , 3 , 0 ); + M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 7 , 0 ); + M_CKERRFULL_( 2 , "not enough arguments" , false , "test" , 2 , 3 , 0 ); + M_CKERRFULL_( 3 , "previous error cause" , true , "test" , 2 , 9 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testIfExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 0 (a) (b) (blah()) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "too many arguments" , false , "test" , 1 , 3 , 0 ); + M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 17 , 0 ); + CPPUNIT_ASSERT( check( "b" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 14 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfBadCondition( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if nope () )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "numeric value expected" , false , "test" , 1 , 3 , 0 ); + M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 7 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testIfBadThenBlock( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 1 nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "list expected" , false , "test" , 1 , 3 , 0 ); + M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 9 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testIfBadElseBlock( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 1 (a) nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "list expected" , false , "test" , 1 , 3 , 0 ); + M_CKERRFULL_( 1 , "previous error cause" , true , "test" , 1 , 13 , 0 ); + CPPUNIT_ASSERT( check( "a" , output ) ); +} + +void SRDPreprocCmdCoreTest::testIfTrueThen( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 1 (x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 10 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfFalseThen( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 0 (x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testIfTrueThenElse( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 1 (x) (y) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 10 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfFalseThenElse( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -if 0 (x) (y) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "y" , output ) ); + M_CKTOKFULL_( 0 , "test" , 1 , 14 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfArgSubst( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x 1 )\n" + "( -if $x (x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); + M_CKTOKFULL_( 0 , "test" , 2 , 11 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfThenInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -if 1 (-not-a-command) )\n" + "( -if 1 ($x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); + M_CKTOKFULL_( 0 , "test" , 3 , 10 , 0 ); + M_CKTOKFULL_( 1 , "$x" , 0 , 0 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 ); + M_CKTOKFULL_( 2 , "$x" , 0 , 1 , 1 ); + M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 ); + M_CKTOKFULL_( 3 , "$x" , 0 , 2 , 1 ); + M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 ); + M_CKTOKFULL_( 4 , "$x" , 0 , 3 , 1 ); + M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 10 , 0 ); +} + +void SRDPreprocCmdCoreTest::testIfElseInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -if 0 () (-not-a-command) )\n" + "( -if 0 () ($x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); + M_CKTOKFULL_( 0 , "test" , 3 , 13 , 0 ); + M_CKTOKFULL_( 1 , "$x" , 0 , 0 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 ); + M_CKTOKFULL_( 2 , "$x" , 0 , 1 , 1 ); + M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 ); + M_CKTOKFULL_( 3 , "$x" , 0 , 2 , 1 ); + M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 ); + M_CKTOKFULL_( 4 , "$x" , 0 , 3 , 1 ); + M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 13 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testEvalEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -eval )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set (x y)\n" + "( -raw $y )\n" + "this-is-y\n" + ")\n" + "eval ( -eval $x ) actual $x" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "eval this-is-y actual $y" , output ) ); + M_CKTOKFULL_( 0 , "test" , 5 , 1 , 0 ); + M_CKTOKFULL_( 1 , "$y" , 0 , 0 , 2 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "$x" , 0 , 0 , 0 ); + M_CKTOKCHAIN_( 1 , 1 , EVALUATED , "test" , 5 , 8 , 0 ); + M_CKTOKFULL_( 2 , "test" , 5 , 19 , 0 ); + M_CKTOKFULL_( 3 , "$x" , 0 , 0 , 1 ); + M_CKTOKCHAIN_( 3 , 0 , SUBSTITUTED , "test" , 5 , 26 , 0 ); +} + +void SRDPreprocCmdCoreTest::testEvalErrors( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x ( -raw $does-not-exist ) )\n" + "( -eval $x )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "unknown variable" , false , "$x" , 0 , 0 , 1 ); + M_CKERRCHAIN_( 0 , 0 , EVALUATED , "test" , 2 , 3 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testEvalSkipComments( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -eval (-from-source \"{ a comment } x # another comment!\")\n)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); + M_CKTOKFULL_( 0 , "-from-source" , 1 , 15 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 10 , 0 ); +} + +void SRDPreprocCmdCoreTest::testEvalUnterminated( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -eval\n(\n(" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 1 , 1 ); + M_CKERR_( 1 , "unterminated list" , 2 , 1 ); + M_CKERR_( 2 , "unterminated list" , 3 , 1 ); + CPPUNIT_ASSERT( check( "(())" , output ) ); + M_CKTOKFULL_( 0 , "test" , 2 , 1 , 0 ); + M_CKTOKFULL_( 1 , "test" , 3 , 1 , 0 ); + M_CKTOKFULL_( 2 , "*unexpected end of file*" , 0 , 2 , 0 ); + M_CKTOKFULL_( 3 , "*unexpected end of file*" , 0 , 1 , 0 ); +} + +void SRDPreprocCmdCoreTest::testEvalInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -eval -not-a-command )\n" + "( -eval $x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); +} + +void SRDPreprocCmdCoreTest::testEvalCalls( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set (x y) (-raw (() f1 (-raw $y) f2)) x)\n" + "(-bless x)\n" + "( -eval a $y b ($x) c ($x) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "a x b f1 x f2 c f1 x f2" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testScope( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro dump ( ( ) ( -raw ( ( -raw\n" + "( -is-set x )\n" + "( -is-blessed f )\n" + "( -is-set y )\n" + "( -is-macro m )\n" + "( -is-macro n )\n" + ") ) ) ) )\n" + "( -set x 1 )\n" + "( -set f (()) )\n" + "( -set-macro m ( ( ) ) )\n" + "( -scope\n" + "( -unset x )\n" + "( -bless f )\n" + "( -set y 0 )\n" + "( -unset-macro m )\n" + "( -set-macro n ( ( ) ) )\n" + "(dump)\n" + ")\n" + "(dump)\n" + "" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "( 0 1 1 0 1 ) ( 1 0 0 1 0 )" , output ) ); +} + +void SRDPreprocCmdCoreTest::testScopeValues( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x 1 )\n" + "$x\n" + "( -scope\n" + "( -set x 2 )\n" + "$x\n" + ")\n" + "$x\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 2 1" , output ) ); +} + +void SRDPreprocCmdCoreTest::testScopeInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -scope -not-a-command )\n" + "( -scope $x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testClearScopeInstruction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x )\n" + "( -set-macro y ( ( ) ) )\n" + "( -is-set x )\n" + "( -is-macro y )\n" + "( -clear-scope )\n" + "( -is-set x )\n" + "( -is-macro y )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 1 0 0" , output ) ); +} + +void SRDPreprocCmdCoreTest::testClearScopeBlock( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x )\n" + "( -set-macro y ( ( ) ) )\n" + "( -is-set x )\n" + "( -is-macro y )\n" + "( -clear-scope\n" + "( -is-set x )\n" + "( -is-macro y )\n" + ")\n" + "( -is-set x )\n" + "( -is-macro y )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 1 0 0 1 1" , output ) ); +} + +void SRDPreprocCmdCoreTest::testClearScopeInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -clear-scope -not-a-command )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testTryEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-try )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "() ()" , output ) ); +} + +void SRDPreprocCmdCoreTest::testTryNoErrors( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-try 1 2 3)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(1 2 3) ()" , output ) ); +} + +void SRDPreprocCmdCoreTest::testTryErrors( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -try $x 2 $y )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "( 2 ) ( " + "( \"unknown variable\" ( \"test\" 1 8 ) )" + "( \"unknown variable\" ( \"test\" 1 13 ) )" + ")" , output ) ); +} + +void SRDPreprocCmdCoreTest::testTryErrorsChained( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set (f1 f2) (-raw\n" + "(() f1 $x)\n" + "((a)\n" + "f2 $a\n" + "(-if (-gt $a 0) (\n" + "($f2 (-sub $a 1))\n" + ")(\n" + "($f1)\n" + "))\n" + ")\n" + "))\n" + "(-bless f1 f2)\n" + "( -try ($f1) ($f2 5))" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( + "( f1 f2 5 f2 4 f2 3 f2 2 f2 1 f2 0 f1 ) ( " + "( \"unknown variable\"" + "( \"test\" 2 8 )" + "called ( \"test\" 13 9 )" + ")" + "( \"unknown variable\"" + "( \"test\" 2 8 )" + "called ( \"test\" 8 2 )" + "called 4 ( \"test\" 6 2 )" + "called ( \"test\" 13 15 )" + ")" + ")" , output ) ); +} + +void SRDPreprocCmdCoreTest::testTryUnterminated( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -try\n(\n(" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 1 , 1 ); + M_CKERR_( 1 , "unterminated list" , 2 , 1 ); + M_CKERR_( 2 , "unterminated list" , 3 , 1 ); + CPPUNIT_ASSERT( check( "((()))()" , output ) ); +} + +void SRDPreprocCmdCoreTest::testTryTooManyErrors( ) +{ + T_StringBuilder sb; + sb << "(-set (output errors) (-try (\n"; + for ( uint32_t i = 0 ; i < T_SRDErrors::MAX_ERRORS + 1 ; i ++ ) { + sb << "$missing\n"; + } + sb << ") ) )\n" + << "(-set last-error ( ((list)) (-raw\n" + << "(-set (a b) $list)\n" + << "(-if (-eq ($b) ()) ($a) (($last-error $b)))\n" + << ")))\n" + << "(-bless last-error)\n" + << "$output (-length $errors) ($last-error (-unwrap $errors))\n" + << '\0'; + + T_SRDErrors errors; + T_SRDList output( process( sb.data( ) , errors ) ); + + sb.clear( ); + sb << "(()) 41 ( \"too many errors\" ( \"test\" " << ( T_SRDErrors::MAX_ERRORS + 1 ) << " 1 ) )" << '\0'; + CPPUNIT_ASSERT( check( sb.data( ) , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); +} + +void SRDPreprocCmdCoreTest::testTryInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -try -not-a-command )\n" + "( -try $x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(-not-a-command) () ((())) ()" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +namespace { + +class T_OutputErrorGen_ : public A_SRDReaderTarget +{ +private: + A_SRDReaderTarget& next_; + +public: + explicit T_OutputErrorGen_( A_SRDReaderTarget& next ) + : A_SRDReaderTarget( ) , next_( next ) + {} + + void start( T_SRDErrors & errors ) override + { + next_.start( errors ); + } + + void push( T_SRDErrors & errors , T_SRDToken && token ) override + { + if ( token.type( ) == E_SRDTokenType::WORD && token.stringValue( ) == "fail" ) { + errors.add( "DO NOT WANT" , token ); + } + next_.push( errors , std::move( token ) ); + } + + void end( T_SRDErrors & errors ) override + { + next_.end( errors ); + } +}; + +} + +void SRDPreprocCmdCoreTest::testOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(5 (-output (3 (-output (1 2)) 4)) 6)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(1 2) (3 4) (5 6)" , output ) ); +} + +void SRDPreprocCmdCoreTest::testOutputErrors( ) +{ + T_SRDErrors errors; + T_SRDMemoryTarget mt( false ); + mt.clearFlushToken( true ); + T_OutputErrorGen_ eg( mt ); + + T_SRDPreprocessorConfig emptyConfig; + emptyConfig.addBuiltinCommands( ); + T_SRDPreprocessor pp( emptyConfig , eg ); + T_SRDLexer lexer( T_String( "test" ) , errors , pp ); + pp.start( errors ); + char const* ptr = "(-try (-output fail))"; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); + mt.end( errors ); + + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "fail () ( ( \"DO NOT WANT\" ( \"test\" 1 16 ) ) )" , + mt.list( ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testErrorEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "user error" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error fail )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "fail" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error \"out of cheese\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "out of cheese" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "$x" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error 123 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "123" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error 5000000000 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "5000000000" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error 1.5 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "1.5" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error ( a list ( 123 \"wut\" ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "( a list ( 123 wut ) )" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testErrorConcat( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -error And this is the real thing \"(or is it?)\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERR_( 0 , "And this is the real thing (or is it?)" , 1 , 3 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testBreakMain( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "a (-break) b" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "a" , output ) ); +} + +void SRDPreprocCmdCoreTest::testBreakMainList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(a ((-break)) b)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(a())" , output ) ); +} + +void SRDPreprocCmdCoreTest::testBreakEvalInput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "a (-eval (b (-break)) c) d" , errors ) ); + for ( uint32_t i = 0 ; i < errors.size( ) ; i ++ ) { + M_PRINTERR_( i ); + } + CPPUNIT_ASSERT( check( "a (b) d" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); +} + +void SRDPreprocCmdCoreTest::testBreakEvalOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "a (-eval b ((-raw ((-break)))) c) d" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "a b (())" , output ) ); +} + +void SRDPreprocCmdCoreTest::testBreakFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set f (-raw ( (a b) $a (-break) $b ) ))\n" + "(-bless f)\n" + "a ($f b c) d" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "a b d" , output ) ); +} + +void SRDPreprocCmdCoreTest::testBreakRecursiveFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set f (-raw ( (a b)\n" + "(\n" + "(-if (-lt $b $a ) (\n" + "(-break)\n" + "))\n" + "$a\n" + "($f (-add $a 1) $b)\n" + ")\n" + ")))\n" + "(-bless f)\n" + "a ($f 1 3) b" , errors ) ); + for ( auto i = 0u ; i < errors.size( ) ; i ++ ) { + M_PRINTERR_( i ); + } + CPPUNIT_ASSERT( check( "a (1(2(3()))) b" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); +} + +void SRDPreprocCmdCoreTest::testBreakMacro( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( (a b) ($a (-break) $b) ) ))\n" + "a (m b c) d" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "a (b) d" , output ) ); +} + +void SRDPreprocCmdCoreTest::testBreakArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(a (-break even) b)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 5 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "(a)" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +#define M_CKRT_TEXT_( INDEX , TEXT ) \ + CPPUNIT_ASSERT( errors[ INDEX ].error( ) == (TEXT) ) +#define M_CKRT_SOURCE_( INDEX , EID , NAME , LINE , CHAR ) \ + do { \ + RPC_SRDLocation loc( &errors[ INDEX ].location( ) ); \ + int i( EID ); \ + while ( i > 0 ) { \ + assert( loc->isChained( ) ); \ + loc = RPC_SRDLocation( loc->chaining( ).location ); \ + i --; \ + } \ + auto const& l( *loc ); \ + CPPUNIT_ASSERT( l.source( ) == (NAME) ); \ + CPPUNIT_ASSERT( l.line( ) == (LINE) ); \ + CPPUNIT_ASSERT( l.character( ) == (CHAR) ); \ + } while ( 0 ) + +void SRDPreprocCmdCoreTest::testRethrowNothing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( ( \"error text\"\n" + "( \"source\" 2 3 )\n" + ") ( \"second error\"\n" + "( \"source 2\" 4 5 )\n" + ") ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "error text" , false , "source" , 2 , 3 , 0 ); + M_CKERRFULL_( 1 , "second error" , false , "source 2" , 4 , 5 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowEmptyList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowUnwrapped( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error text\"\n" + "( \"source\" 2 3 )\n" + ") ( \"second error\"\n" + "( \"source 2\" 4 5 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERRFULL_( 0 , "error text" , false , "source" , 2 , 3 , 0 ); + M_CKERRFULL_( 1 , "second error" , false , "source 2" , 4 , 5 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowErrorWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow ( error ( \"source\" 2 3 ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "error" , false , "source" , 2 , 3 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowSourceWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( source 2 3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "error" , false , "source" , 2 , 3 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowDetails( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"not an error\" details\n" + "( source 2 3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "not an error" , true , "source" , 2 , 3 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChained( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 1 1 )\n" + "called ( \"src\" 2 2 )\n" + "loaded ( \"src\" 3 3 )\n" + "included ( \"src\" 4 4 )\n" + "generated ( \"src\" 5 5 )\n" + "expanded ( \"src\" 6 6 )\n" + "evaluated ( \"src\" 7 7 )\n" + "substituted ( \"src\" 8 8 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "error" , false , "src" , 1 , 1 , 7 ); + M_CKERRCHAIN_( 0 , 0 , CALLED , "src" , 2 , 2 , 0 ); + M_CKERRCHAIN_( 0 , 1 , LOADED , "src" , 3 , 3 , 0 ); + M_CKERRCHAIN_( 0 , 2 , INCLUDED , "src" , 4 , 4 , 0 ); + M_CKERRCHAIN_( 0 , 3 , GENERATED , "src" , 5 , 5 , 0 ); + M_CKERRCHAIN_( 0 , 4 , EXPANDED , "src" , 6 , 6 , 0 ); + M_CKERRCHAIN_( 0 , 5 , EVALUATED , "src" , 7 , 7 , 0 ); + M_CKERRCHAIN_( 0 , 6 , SUBSTITUTED , "src" , 8 , 8 , 0 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedDepth( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 1 1 )\n" + "called 5 ( \"src\" 2 2 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + M_CKERRFULL_( 0 , "error" , false , "src" , 1 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 0 , CALLED , "src" , 2 , 2 , 5 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdCoreTest::testRethrowNoList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow nope)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowBadMessage( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow ( 12 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "error message expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowNoLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow ( error ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "'details' or start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 1 , 19 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowBadLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow ( \"error\" nope ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "'details' or start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 1 , 21 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationBadSource( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-rethrow ( \"error\" ( 12 1 2 ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "source name expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 1 , 23 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationBadLine( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" nope 3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "line number expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationNegativeLine( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" -1 3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid line number" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationLineTooHigh( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 5000000000 3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid line number" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 2 , 9 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationBadChar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 nope )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "character/byte number expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 2 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowLocationNegativeChar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 -3 )\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid character/byte number" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 2 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "12\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "chaining type or end of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 1 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedInvalid( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "blah\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid chaining type" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 1 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedNoLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "depth or start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedBadLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded nope\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "depth or start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedNegativeDepth( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded -1\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid recursion depth" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedDepthTooHigh( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded 5000000000\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid recursion depth" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedDepthNoLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded 5\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdCoreTest::testRethrowChainedDepthBadLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-rethrow ( \"error\"\n" + "( \"src\" 2 3 )\n" + "loaded 5 nope\n" + ") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "start of list expected" , 1 , 2 ); + M_CKERR_( 1 , "previous error cause" , 3 , 10 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} diff --git a/tests/srd-preproc-cmd-functions.cc b/tests/srd-preproc-cmd-functions.cc new file mode 100644 index 0000000..b8d32c3 --- /dev/null +++ b/tests/srd-preproc-cmd-functions.cc @@ -0,0 +1,441 @@ +#include "srd-preproc-cmd-common.hh" + +class SRDPreprocCmdFunctionsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdFunctionsTest ); + + CPPUNIT_TEST( testBlessNoArgs ); + CPPUNIT_TEST( testBlessMissing ); + CPPUNIT_TEST( testBlessEmpty ); + CPPUNIT_TEST( testBlessInvalid1 ); + CPPUNIT_TEST( testBlessInvalid2 ); + CPPUNIT_TEST( testBlessInvalid3 ); + CPPUNIT_TEST( testBlessInvalid4 ); + CPPUNIT_TEST( testBlessEmptyFunction ); + CPPUNIT_TEST( testBlessFnWithArgs ); + CPPUNIT_TEST( testBlessFnWithDupArgs ); + CPPUNIT_TEST( testBlessFnWithOptArgs ); + CPPUNIT_TEST( testBlessFnWithDupOptArgs ); + CPPUNIT_TEST( testBlessFnWithOptArgsOnly ); + CPPUNIT_TEST( testBlessFnWithDupArgsAll ); + + CPPUNIT_TEST( testCallNoArg ); + CPPUNIT_TEST( testCallBlessedVar ); + CPPUNIT_TEST( testCallBadArg ); + CPPUNIT_TEST( testCallInvalidFunction ); + CPPUNIT_TEST( testCallValidFunction ); + CPPUNIT_TEST( testCallTooManyArgs ); + CPPUNIT_TEST( testCallNotEnoughArgs ); + CPPUNIT_TEST( testCallNotEnoughArgsOpt ); + + CPPUNIT_TEST( testBodyInnerList ); + CPPUNIT_TEST( testDashFunction ); + + CPPUNIT_TEST( testBug00 ); + CPPUNIT_TEST( testBug01 ); + CPPUNIT_TEST( testBug02 ); + CPPUNIT_TEST( testBug03 ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testBlessNoArgs( ); + void testBlessMissing( ); + void testBlessEmpty( ); + void testBlessInvalid1( ); + void testBlessInvalid2( ); + void testBlessInvalid3( ); + void testBlessInvalid4( ); + void testBlessEmptyFunction( ); + void testBlessFnWithArgs( ); + void testBlessFnWithDupArgs( ); + void testBlessFnWithOptArgs( ); + void testBlessFnWithDupOptArgs( ); + void testBlessFnWithOptArgsOnly( ); + void testBlessFnWithDupArgsAll( ); + + void testCallNoArg( ); + void testCallBlessedVar( ); + void testCallBadArg( ); + void testCallInvalidFunction( ); + void testCallValidFunction( ); + void testCallTooManyArgs( ); + void testCallNotEnoughArgs( ); + void testCallNotEnoughArgsOpt( ); + + void testBodyInnerList( ); + void testDashFunction( ); + + void testBug00( ); + void testBug01( ); + void testBug02( ); + void testBug03( ); + +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdFunctionsTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdFunctionsTest::testBlessNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bless )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessMissing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bless a ) " , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a )\n" + "( -bless a )\n" + "( $a ) " , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "( )" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessInvalid1( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set x\n" + "nope\n" + ")\n" + "( -bless x )\n" + "($x)" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 4 , 3 ); + M_CKERR_( 1 , "previous error cause" , 4 , 10 ); + CPPUNIT_ASSERT( check( "(nope)" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessInvalid2( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set x\n" + "()\n" + ")\n" + "( -bless x )\n" + "($x)" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 4 , 3 ); + M_CKERR_( 1 , "previous error cause" , 4 , 10 ); + CPPUNIT_ASSERT( check( "(())" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessInvalid3( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set x\n" + "(nope)\n" + ")\n" + "( -bless x )\n" + "($x)" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 4 , 3 ); + M_CKERR_( 1 , "previous error cause" , 4 , 10 ); + CPPUNIT_ASSERT( check( "((nope))" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessInvalid4( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set x\n" + "(()) nope\n" + ")\n" + "( -bless x )\n" + "($x)" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 4 , 3 ); + M_CKERR_( 1 , "previous error cause" , 4 , 10 ); + CPPUNIT_ASSERT( check( "((()) nope)" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessEmptyFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( ) x ) )\n" + "( -bless a )\n" + "( $a ) " , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( u v w ) ( -raw $u $v $w ) ) )\n" + "( -bless a )\n" + "( $a b c d ) " , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "b c d" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithDupArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( u v u ) ( -raw $u $v ) ) )\n" + "( -bless a )\n" + "( $a b c d ) " , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "( ( ( u v u ) $u $v ) b c d )" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithOptArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( u ( v ) ) ( -raw ( $u ) ( $v ) ) ) )\n" + "( -bless a )\n" + "($a b c d)\n" + "($a e)\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( b ) ( c d ) ( e ) ( )" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithDupOptArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( u ( v v ) ) ( -raw ( $u ) ( $v ) ) ) )\n" + "( -bless a )\n" + "( $a b )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "( ( ( u ( v v ) ) ( $u ) ( $v ) ) b )" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithOptArgsOnly( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( ( v ) ) ( -raw ( $v ) ) ) )\n" + "( -bless a )\n" + "( $a ) ( $a b ) ( $a c d )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( ) ( b ) ( c d )" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBlessFnWithDupArgsAll( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set a ( ( u ( v u ) ) ( -raw ( $u ) ( $v ) ) ) )\n" + "( -bless a )\n" + "( $a b )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid function" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "( ( ( u ( v u ) ) ( $u ) ( $v ) ) b )" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdFunctionsTest::testCallNoArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 9 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallBlessedVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set f (() x))\n" + "(-bless f)\n" + "( -call $f )" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "list expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 9 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallInvalidFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call ( nope ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "list expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallValidFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call ( -raw ( ( x ( y ) ) $y $x ) ) a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "b c a" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call ( -raw ( ( x ) $x ) ) a b )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 33 ); + CPPUNIT_ASSERT( check( "a" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call ( -raw ( ( x ) $x ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 31 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testCallNotEnoughArgsOpt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -call ( -raw ( ( x y ( z ) ) $x ) ) a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 41 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdFunctionsTest::testBodyInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -call ( -raw ( () -not-a-command ) ) )\n" + "( -call ( -raw ( () $x ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testDashFunction( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set -f ((x) (-raw yo $x)))\n" + "(-bless -f)\n" + "(-f bob)\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "yo bob" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdFunctionsTest::testBug00( ) +{ + /* A bug with tail calls - the code below would output "y x" */ + T_SRDErrors errors; + T_SRDList output( process( + "(-set f1 ((-raw (a) $a)))\n" + "(-set f2 ((-raw (a) ($f1 $a))))\n" + "(-bless f1 f2)\n" + "($f2 x) y\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "x y" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBug01( ) +{ + /* Similar to bug 00, except with an ending parens. */ + T_SRDErrors errors; + T_SRDList output( process( + "(-set f1 ((-raw (a) $a)))\n" + "(-set f2 ((-raw (a) ($f1 $a))))\n" + "(-bless f1 f2)\n" + "(($f2 x))\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(x)" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBug02( ) +{ + /* Causes a crash due to a missing location */ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw (((list))\n" + "(-set (first rest) $list)\n" + "(-if (-length ($first)) (\n" + "($first ($fn $rest))\n" + "))\n" + ")))\n" + "(-bless fn)\n" + "($fn a b)\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(a (b))" , output ) ); +} + +void SRDPreprocCmdFunctionsTest::testBug03( ) +{ + /* More tail-call problems (interaction between the "unswallow" + * part of the evaluator/function feeders and stuff that stands + * between them). + */ + T_SRDErrors errors; + T_SRDList output( process( + "(-set (f1 f2) (-raw\n" + "(() (-if 1 ()))\n" + "(() ($f1))\n" + "))\n" + "(-bless f1 f2)\n" + "(-eval ((-raw -eval) ((-raw $f2))))\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "(a (b))" , output ) ); +} diff --git a/tests/srd-preproc-cmd-input.cc b/tests/srd-preproc-cmd-input.cc new file mode 100644 index 0000000..91e11bb --- /dev/null +++ b/tests/srd-preproc-cmd-input.cc @@ -0,0 +1,543 @@ +#include "srd-preproc-cmd-common.hh" +#include "srd-preproc-location.hh" + + +class SRDPreprocCmdInputTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdInputTest ); + + CPPUNIT_TEST( testFromSourceNoName ); + CPPUNIT_TEST( testFromSourceNamed ); + CPPUNIT_TEST( testFromSourceError ); + CPPUNIT_TEST( testFromSourceErrorNamed ); + CPPUNIT_TEST( testFromSourceTooManyErrors ); + CPPUNIT_TEST( testFromSourceLocation ); + CPPUNIT_TEST( testFromSourceLocationNamed ); + CPPUNIT_TEST( testFromSourceLocationChaining ); + CPPUNIT_TEST( testFromSourceLocationRewind ); + CPPUNIT_TEST( testFromSourceLocationRewindTooMuch ); + + CPPUNIT_TEST( testFromSourceArgsNone ); + CPPUNIT_TEST( testFromSourceArgsExtra ); + CPPUNIT_TEST( testFromSourceArgsBadSource ); + CPPUNIT_TEST( testFromSourceArgsBadName ); + CPPUNIT_TEST( testFromSourceArgsBadChaining ); + CPPUNIT_TEST( testFromSourceArgsBadRewind ); + + CPPUNIT_TEST( testFromSRBNoName ); + CPPUNIT_TEST( testFromSRBNamed ); + CPPUNIT_TEST( testFromSRBError ); + CPPUNIT_TEST( testFromSRBErrorNamed ); + CPPUNIT_TEST( testFromSRBLocation ); + CPPUNIT_TEST( testFromSRBLocationNamed ); + CPPUNIT_TEST( testFromSRBLocationChaining ); + CPPUNIT_TEST( testFromSRBLocationRewind ); + CPPUNIT_TEST( testFromSRBLocationRewindTooMuch ); + + CPPUNIT_TEST( testFromSRBArgsNone ); + CPPUNIT_TEST( testFromSRBArgsExtra ); + CPPUNIT_TEST( testFromSRBArgsBadInput ); + CPPUNIT_TEST( testFromSRBArgsBadName ); + CPPUNIT_TEST( testFromSRBArgsBadChaining ); + CPPUNIT_TEST( testFromSRBArgsBadRewind ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testFromSourceNoName( ); + void testFromSourceNamed( ); + void testFromSourceError( ); + void testFromSourceErrorNamed( ); + void testFromSourceTooManyErrors( ); + void testFromSourceLocation( ); + void testFromSourceLocationNamed( ); + void testFromSourceLocationChaining( ); + void testFromSourceLocationRewind( ); + void testFromSourceLocationRewindTooMuch( ); + + void testFromSourceArgsNone( ); + void testFromSourceArgsExtra( ); + void testFromSourceArgsBadSource( ); + void testFromSourceArgsBadName( ); + void testFromSourceArgsBadChaining( ); + void testFromSourceArgsBadRewind( ); + + void testFromSRBNoName( ); + void testFromSRBNamed( ); + void testFromSRBError( ); + void testFromSRBErrorNamed( ); + void testFromSRBLocation( ); + void testFromSRBLocationNamed( ); + void testFromSRBLocationChaining( ); + void testFromSRBLocationRewind( ); + void testFromSRBLocationRewindTooMuch( ); + + void testFromSRBArgsNone( ); + void testFromSRBArgsExtra( ); + void testFromSRBArgsBadInput( ); + void testFromSRBArgsBadName( ); + void testFromSRBArgsBadChaining( ); + void testFromSRBArgsBadRewind( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdInputTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdInputTest::testFromSourceNoName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"word \\\"string\\\" 123 5000000000 1.2 $x ( some list )\" )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "word \"string\" 123 5000000000 1.2 $x ( some list )" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-source \"word \\\"string\\\" 123 5000000000 1.2 $x ( some list )\"\n" + "\"some_random_file.srd\" )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "word \"string\" 123 5000000000 1.2 $x ( some list )" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source\n\"( a\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 1 , 1 ); + CPPUNIT_ASSERT( errors[ 0 ].location( ).source( ) == "-from-source" ); + CPPUNIT_ASSERT( check( "( a )" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceErrorNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source\n\"( a\" \"some_random_file.srd\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 1 , 1 ); + CPPUNIT_ASSERT( errors[ 0 ].location( ).source( ) == "some_random_file.srd" ); + CPPUNIT_ASSERT( check( "( a )" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceTooManyErrors( ) +{ + T_StringBuilder sb( "( -from-source \"" ); + for ( auto i = 0u ; i < T_SRDErrors::MAX_ERRORS + 1 ; i ++ ) { + sb << "(\\u00a1"; + } + sb << "\" )" << '\0'; + + try { + T_SRDErrors errors; + T_SRDList output( process( sb.data( ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDErrors::MAX_ERRORS + 1 ) , errors.size( ) ); + for ( auto i = 0u ; i < T_SRDErrors::MAX_ERRORS ; i ++ ) { + M_CKERR_( i , "unexpected character" , 1 , ( i + 1 ) * 2 ); + } + M_CKERR_( T_SRDErrors::MAX_ERRORS , "too many errors" , 1 , 80 ); + + sb.clear( ); + for ( auto i = 0u ; i < T_SRDErrors::MAX_ERRORS ; i ++ ) { + sb << '('; + } + for ( auto i = 0u ; i < T_SRDErrors::MAX_ERRORS ; i ++ ) { + sb << ')'; + } + sb << '\0'; + CPPUNIT_ASSERT( check( sb.data( ) , output ) ); + } catch ( X_SRDErrors const& e ) { + auto const& errors( e.errors ); + for ( auto i = 0u ; i < errors.size( ) ; i ++ ) { + M_PRINTERR_( i ); + } + CPPUNIT_FAIL( "Shouldn't fail with exception" ); + } +} + +void SRDPreprocCmdInputTest::testFromSourceLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"test\\ntest\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "test test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "-from-source" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "-from-source" , 2 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceLocationNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"test\\ntest\" \"test.srd\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "test test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test.srd" , 2 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceLocationChaining( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"test\\ntest\" \"test.srd\" loaded )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "test test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test.srd" , 2 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , LOADED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceLocationRewind( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set doit ((-raw ()\n" + "( -from-source \"test\" \"test.srd\" loaded 2 )\n" + ")))\n" + "(-set tfn ((-raw (x)\n" + "(-if (-lt $x 1) (\n" + "($doit)\n" + ")(\n" + "($tfn (-sub $x 1))\n" + "))\n" + ")))\n" + "(-bless tfn doit)\n" + "($tfn 5)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 12 , 2 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceLocationRewindTooMuch( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"test\" \"test.srd\" loaded 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 1 , 3 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdInputTest::testFromSourceArgsNone( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceArgsExtra( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source \"x\" \"y\" generated 0 c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 36 ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceArgsBadSource( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-source 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSourceArgsBadName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-source \"test\"\n" + "12 )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + CPPUNIT_ASSERT( check( "test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "-from-source" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceArgsBadChaining( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-source \"test\" \"test.srd\"\n" + "nope )\n" + "( -from-source \"test\" \"test2.srd\"\n" + "12 )\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "'generated', 'included' or 'loaded' expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + M_CKERR_( 2 , "'generated', 'included' or 'loaded' expected" , 3 , 3 ); + M_CKERR_( 3 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "test test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test2.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 3 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSourceArgsBadRewind( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-source \"test\" \"test.srd\" generated\n" + "-1 )\n" + "( -from-source \"test\" \"test2.srd\" generated\n" + "nope )\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + M_CKERR_( 2 , "integer argument expected" , 3 , 3 ); + M_CKERR_( 3 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "test test" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test2.srd" , 1 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 3 , 3 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +#define SRB_PROLOGUE_ \ + "ce a1 7c ea " \ + "00 ee ff c0" + +#define SRB_DATA_ \ + "[ " \ + SRB_PROLOGUE_ \ + "02 03 00 00 00 4c 4f 4c" \ + "00" \ + "]" + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdInputTest::testFromSRBNoName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-srb " SRB_DATA_ "\n" + "\"some_random_file.srb\" )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBError( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb\n[ " SRB_PROLOGUE_ " ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unexpected end of file" , 0 , 8 ); + CPPUNIT_ASSERT( errors[ 0 ].location( ).source( ) == "-from-srb" ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBErrorNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-srb\n[ " + SRB_PROLOGUE_ + " ] \"some_random_file.srb\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unexpected end of file" , 0 , 8 ); + CPPUNIT_ASSERT( errors[ 0 ].location( ).source( ) == "some_random_file.srb" ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBLocation( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " )\n" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "-from-srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSRBLocationNamed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " \"test.srb\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSRBLocationChaining( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " \"test.srb\" loaded )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 1 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSRBLocationRewind( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set doit ((-raw ()\n" + "( -from-srb " SRB_DATA_ " \"test.srb\" loaded 2 )\n" + ")))\n" + "(-set tfn ((-raw (x)\n" + "(-if (-lt $x 1) (\n" + "($doit)\n" + ")(\n" + "($tfn (-sub $x 1))\n" + "))\n" + ")))\n" + "(-bless tfn doit)\n" + "($tfn 5)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 12 , 2 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSRBLocationRewindTooMuch( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " \"test.srb\" loaded 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , LOADED , "test" , 1 , 3 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdInputTest::testFromSRBArgsNone( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBArgsExtra( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb " SRB_DATA_ " a loaded 0\nnope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBArgsBadInput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -from-srb \"x\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBArgsBadName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-srb " SRB_DATA_ "\n12 )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + CPPUNIT_ASSERT( check( "LOL" , output ) ); +} + +void SRDPreprocCmdInputTest::testFromSRBArgsBadChaining( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-srb " SRB_DATA_ " \"test.srb\"\n" + "nope )\n" + "( -from-srb " SRB_DATA_ " \"test2.srb\"\n" + "12 )\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "'generated', 'included' or 'loaded' expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + M_CKERR_( 2 , "'generated', 'included' or 'loaded' expected" , 3 , 3 ); + M_CKERR_( 3 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "LOL LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test2.srb" , 0 , 8 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 3 , 3 , 0 ); +} + +void SRDPreprocCmdInputTest::testFromSRBArgsBadRewind( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -from-srb " SRB_DATA_ " \"test.srd\" generated\n" + "-1 )\n" + "( -from-srb " SRB_DATA_ " \"test2.srd\" generated\n" + "nope )\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + M_CKERR_( 2 , "integer argument expected" , 3 , 3 ); + M_CKERR_( 3 , "previous error cause" , 4 , 1 ); + CPPUNIT_ASSERT( check( "LOL LOL" , output ) ); + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test.srd" , 0 , 8 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 3 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test2.srd" , 0 , 8 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 3 , 3 , 0 ); +} diff --git a/tests/srd-preproc-cmd-introspect.cc b/tests/srd-preproc-cmd-introspect.cc new file mode 100644 index 0000000..e51d727 --- /dev/null +++ b/tests/srd-preproc-cmd-introspect.cc @@ -0,0 +1,322 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdIntrospectTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdIntrospectTest ); + + CPPUNIT_TEST( testIsSetMissingArgs ); + CPPUNIT_TEST( testIsSetExtraArgs ); + CPPUNIT_TEST( testIsSetBadArg ); + CPPUNIT_TEST( testIsSetTrue ); + CPPUNIT_TEST( testIsSetFalse ); + + CPPUNIT_TEST( testIsMacroMissingArgs ); + CPPUNIT_TEST( testIsMacroExtraArgs ); + CPPUNIT_TEST( testIsMacroBadArg ); + CPPUNIT_TEST( testIsMacroTrue ); + CPPUNIT_TEST( testIsMacroFalse ); + + CPPUNIT_TEST( testIsBlessedMissingArgs ); + CPPUNIT_TEST( testIsBlessedExtraArgs ); + CPPUNIT_TEST( testIsBlessedBadArg ); + CPPUNIT_TEST( testIsBlessedUnset ); + CPPUNIT_TEST( testIsBlessedUnblessed ); + CPPUNIT_TEST( testIsBlessedTrue ); + + CPPUNIT_TEST( testTypeOfMissingArg ); + CPPUNIT_TEST( testTypeOfExtraArgs ); + CPPUNIT_TEST( testTypeOfList ); + CPPUNIT_TEST( testTypeOfWord ); + CPPUNIT_TEST( testTypeOfString ); + CPPUNIT_TEST( testTypeOfInt ); + CPPUNIT_TEST( testTypeOfLong ); + CPPUNIT_TEST( testTypeOfReal ); + CPPUNIT_TEST( testTypeOfVar ); + CPPUNIT_TEST( testTypeOfBinary ); + CPPUNIT_TEST( testTypeOfComment ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testIsSetMissingArgs( ); + void testIsSetExtraArgs( ); + void testIsSetBadArg( ); + void testIsSetTrue( ); + void testIsSetFalse( ); + + void testIsMacroMissingArgs( ); + void testIsMacroExtraArgs( ); + void testIsMacroBadArg( ); + void testIsMacroTrue( ); + void testIsMacroFalse( ); + + void testIsBlessedMissingArgs( ); + void testIsBlessedExtraArgs( ); + void testIsBlessedBadArg( ); + void testIsBlessedUnset( ); + void testIsBlessedUnblessed( ); + void testIsBlessedTrue( ); + + void testTypeOfMissingArg( ); + void testTypeOfExtraArgs( ); + void testTypeOfList( ); + void testTypeOfWord( ); + void testTypeOfString( ); + void testTypeOfInt( ); + void testTypeOfLong( ); + void testTypeOfReal( ); + void testTypeOfVar( ); + void testTypeOfBinary( ); + void testTypeOfComment( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdIntrospectTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdIntrospectTest::testIsSetMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-set )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsSetExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-set a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsSetBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-set 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsSetTrue( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set x 1)( -is-set x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsSetFalse( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-set x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdIntrospectTest::testIsMacroMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-macro )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsMacroExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-macro a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsMacroBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-macro 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsMacroTrue( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set-macro x (()))( -is-macro x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsMacroFalse( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-macro x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdIntrospectTest::testIsBlessedMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-blessed )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsBlessedExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-blessed a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 17 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsBlessedBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-blessed 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 15 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsBlessedUnset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -is-blessed x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsBlessedUnblessed( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set x 1)( -is-blessed x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testIsBlessedTrue( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set x (()) )\n" + "( -bless x )\n" + "( -is-blessed x )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdIntrospectTest::testTypeOfMissingArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "none" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "word" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "list" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of w )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "word" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of \"w\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "string" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "int" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of 12123456789 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "long" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfReal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of 0. )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "real" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of ( -raw $x ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "var" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of [] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "binary" , output ) ); +} + +void SRDPreprocCmdIntrospectTest::testTypeOfComment( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -type-of (-from-source \"{x}\") )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "comment" , output ) ); +} diff --git a/tests/srd-preproc-cmd-logic.cc b/tests/srd-preproc-cmd-logic.cc new file mode 100644 index 0000000..52374a3 --- /dev/null +++ b/tests/srd-preproc-cmd-logic.cc @@ -0,0 +1,609 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdLogicTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdLogicTest ); + + CPPUNIT_TEST( testNotNoArgs ); + CPPUNIT_TEST( testNotTooManyArgs ); + CPPUNIT_TEST( testNotBadArg ); + CPPUNIT_TEST( testNotZero ); + CPPUNIT_TEST( testNotNonZeroInt ); + CPPUNIT_TEST( testNotNonZeroLong ); + + CPPUNIT_TEST( testBwNotNoArgs ); + CPPUNIT_TEST( testBwNotTooManyArgs ); + CPPUNIT_TEST( testBwNotBadArg ); + CPPUNIT_TEST( testBwNotInt ); + CPPUNIT_TEST( testBwNotLong ); + + CPPUNIT_TEST( testAndNoArgs ); + CPPUNIT_TEST( testAndBadArgs ); + CPPUNIT_TEST( testAnd00 ); + CPPUNIT_TEST( testAnd01 ); + CPPUNIT_TEST( testAnd10 ); + CPPUNIT_TEST( testAnd11 ); + CPPUNIT_TEST( testAndMultiple ); + CPPUNIT_TEST( testAndLong ); + CPPUNIT_TEST( testAndSillyValues ); + + CPPUNIT_TEST( testBwAndNoArgs ); + CPPUNIT_TEST( testBwAndBadArgs ); + CPPUNIT_TEST( testBwAndInts ); + CPPUNIT_TEST( testBwAndLongs ); + CPPUNIT_TEST( testBwAndMultiple ); + + CPPUNIT_TEST( testOrNoArgs ); + CPPUNIT_TEST( testOrBadArgs ); + CPPUNIT_TEST( testOr00 ); + CPPUNIT_TEST( testOr01 ); + CPPUNIT_TEST( testOr10 ); + CPPUNIT_TEST( testOr11 ); + CPPUNIT_TEST( testOrMultiple ); + CPPUNIT_TEST( testOrLong ); + CPPUNIT_TEST( testOrSillyValues ); + + CPPUNIT_TEST( testBwOrNoArgs ); + CPPUNIT_TEST( testBwOrBadArgs ); + CPPUNIT_TEST( testBwOrInts ); + CPPUNIT_TEST( testBwOrLongs ); + CPPUNIT_TEST( testBwOrMultiple ); + + CPPUNIT_TEST( testXorNoArgs ); + CPPUNIT_TEST( testXorBadArgs ); + CPPUNIT_TEST( testXor00 ); + CPPUNIT_TEST( testXor01 ); + CPPUNIT_TEST( testXor10 ); + CPPUNIT_TEST( testXor11 ); + CPPUNIT_TEST( testXorMultiple ); + CPPUNIT_TEST( testXorLong ); + CPPUNIT_TEST( testXorSillyValues ); + + CPPUNIT_TEST( testBwXorNoArgs ); + CPPUNIT_TEST( testBwXorBadArgs ); + CPPUNIT_TEST( testBwXorInts ); + CPPUNIT_TEST( testBwXorLongs ); + CPPUNIT_TEST( testBwXorMultiple ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testNotNoArgs( ); + void testNotTooManyArgs( ); + void testNotBadArg( ); + void testNotZero( ); + void testNotNonZeroInt( ); + void testNotNonZeroLong( ); + + void testBwNotNoArgs( ); + void testBwNotTooManyArgs( ); + void testBwNotBadArg( ); + void testBwNotInt( ); + void testBwNotLong( ); + + void testAndNoArgs( ); + void testAndBadArgs( ); + void testAnd00( ); + void testAnd01( ); + void testAnd10( ); + void testAnd11( ); + void testAndMultiple( ); + void testAndLong( ); + void testAndSillyValues( ); + + void testBwAndNoArgs( ); + void testBwAndBadArgs( ); + void testBwAndInts( ); + void testBwAndLongs( ); + void testBwAndMultiple( ); + + void testOrNoArgs( ); + void testOrBadArgs( ); + void testOr00( ); + void testOr01( ); + void testOr10( ); + void testOr11( ); + void testOrMultiple( ); + void testOrLong( ); + void testOrSillyValues( ); + + void testBwOrNoArgs( ); + void testBwOrBadArgs( ); + void testBwOrInts( ); + void testBwOrLongs( ); + void testBwOrMultiple( ); + + void testXorNoArgs( ); + void testXorBadArgs( ); + void testXor00( ); + void testXor01( ); + void testXor10( ); + void testXor11( ); + void testXorMultiple( ); + void testXorLong( ); + void testXorSillyValues( ); + + void testBwXorNoArgs( ); + void testBwXorBadArgs( ); + void testBwXorInts( ); + void testBwXorLongs( ); + void testBwXorMultiple( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdLogicTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testNotNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -not )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testNotTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -not 0 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testNotBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -not 0. )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testNotZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -not 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testNotNonZeroInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -not 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testNotNonZeroLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set x ( -not 12000000000 ))(-to-integer $x)(-type-of $x)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0 long" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testBwNotNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-not )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwNotTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-not 1 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "-2" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwNotBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-not 2.2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "-3" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwNotInt( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-not 129 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-130" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwNotLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-not 12000000000 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-12000000001" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testAndNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAndBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 1 1. ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAnd00( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 0 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAnd01( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 0 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAnd10( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 1 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAnd11( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 1 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAndMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 1 1 1 1 )( -and 1 0 1 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAndLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and 1 ( -to-long 1 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testAndSillyValues( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -and -42384 35845 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testBwAndNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-and )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwAndBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-and 1.2 ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwAndInts( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-and 5 6 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "4" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwAndLongs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-and 12884901888 4294967296 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "4294967296" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwAndMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-and 15 7 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "3" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testOrNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 7 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOrBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 1 1. ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 9 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOr00( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 0 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOr01( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 0 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOr10( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 1 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOr11( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 1 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOrMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 1 1 1 1 )( -or 1 0 1 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOrLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 1 ( -to-long 1 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testOrSillyValues( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -or 0 35845 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testBwOrNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-or )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwOrBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-or 1.2 ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwOrInts( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-or 5 6 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "7" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwOrLongs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-or 8589934592 4294967296 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "12884901888" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwOrMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-or 2 4 8 6 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "14" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testXorNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXorBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 1 1. ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXor00( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 0 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXor01( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 0 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXor10( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 1 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXor11( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 1 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXorMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 1 0 0 0 )( -xor 1 0 1 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXorLong( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 1 ( -to-long 1 ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testXorSillyValues( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -xor 0 35845 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdLogicTest::testBwXorNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-xor )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwXorBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-xor 1.2 ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwXorInts( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-xor 5 6 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "3" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwXorLongs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-xor 21474836480 25769803776 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "12884901888" , output ) ); +} + +void SRDPreprocCmdLogicTest::testBwXorMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -bw-xor 2 4 8 6 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "8" , output ) ); +} diff --git a/tests/srd-preproc-cmd-macros.cc b/tests/srd-preproc-cmd-macros.cc new file mode 100644 index 0000000..1f0105e --- /dev/null +++ b/tests/srd-preproc-cmd-macros.cc @@ -0,0 +1,243 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdMacrosTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdMacrosTest ); + + CPPUNIT_TEST( testSetMacroEmpty ); + CPPUNIT_TEST( testSetMacroMissingArgs ); + CPPUNIT_TEST( testSetMacroExtraArgs ); + CPPUNIT_TEST( testSetMacroBadName ); + CPPUNIT_TEST( testSetMacroBadBody ); + CPPUNIT_TEST( testSetMacroInvalidBody ); + CPPUNIT_TEST( testSetMacroSuccess ); + CPPUNIT_TEST( testSetMacroDuplicate ); + + CPPUNIT_TEST( testUnsetMacroNoArgs ); + CPPUNIT_TEST( testUnsetMacro ); + CPPUNIT_TEST( testUnsetMacroMissing ); + CPPUNIT_TEST( testUnsetMacroMultiple ); + + CPPUNIT_TEST( testListMacros ); + CPPUNIT_TEST( testListMacrosArgs ); + + CPPUNIT_TEST( testMacroBodyInnerList ); + CPPUNIT_TEST( testMacroOutputInnerList ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testSetMacroEmpty( ); + void testSetMacroMissingArgs( ); + void testSetMacroExtraArgs( ); + void testSetMacroBadName( ); + void testSetMacroBadBody( ); + void testSetMacroInvalidBody( ); + void testSetMacroSuccess( ); + void testSetMacroDuplicate( ); + + void testUnsetMacroNoArgs( ); + void testUnsetMacro( ); + void testUnsetMacroMissing( ); + void testUnsetMacroMultiple( ); + + void testListMacros( ); + void testListMacrosArgs( ); + + void testMacroBodyInnerList( ); + void testMacroOutputInnerList( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdMacrosTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMacrosTest::testSetMacroEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroMissingArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid macro" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro a ( () ) x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 23 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroBadName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro 1 ( () ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroBadBody( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro a x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid macro" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroInvalidBody( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set-macro a ( nope ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid macro" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroSuccess( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro test ( -raw ( ((list)) ( (-raw -raw) $list ) ) ) )\n" + "(test $x $y $z (-set x 1))", + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "$x $y $z (-set x 1)" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testSetMacroDuplicate( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro a (()) )\n" + "( -set-macro a (()) )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "duplicate macro" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 14 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMacrosTest::testUnsetMacroNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unset-macro ) " , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testUnsetMacro( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro a (()1) )\n" + "(a)\n" + "( -unset-macro a )\n" + "(a)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "1 (a)" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testUnsetMacroMissing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unset-macro a )(a)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "(a)" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testUnsetMacroMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro a (()1) )\n" + "( -set-macro b (()2) )\n" + "( -set-macro c (()3) )\n" + "( -unset-macro a c d )\n" + "(a)(b)(c)\n" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "(a) 2 (c)" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMacrosTest::testListMacros( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x )\n" + "( -set-macro y ( ( ) ) )\n" + "( -set f (-raw (() ((-ls-macros))) ))\n" + "( -bless f )\n" + "( ( -ls-macros ) )\n" + "( -scope\n" + "( -unset-macro y )\n" + "( ( -ls-macros ) )\n" + ")\n" + "($f)\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( y ) ( ) ( y )" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testListMacrosArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro y ( ( ) ) )\n" + "( -ls-macros blah )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 14 ); + CPPUNIT_ASSERT( check( "y" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMacrosTest::testMacroBodyInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro x (-raw ( () -not-a-command ) ) )\n" + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -set-macro y (-raw ( () $x ) ) )\n" + "(x)(y)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); +} + +void SRDPreprocCmdMacrosTest::testMacroOutputInnerList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set-macro x (-raw ( () (-raw -not-a-command) ) ) )\n" + "( -set x (-raw ( () ) ) )\n" + "( -bless x )\n" + "( -set-macro y (-raw ( () (-raw $x ) ) ) )\n" + "(x)(y)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-not-a-command (())" , output ) ); +} diff --git a/tests/srd-preproc-cmd-misc.cc b/tests/srd-preproc-cmd-misc.cc new file mode 100644 index 0000000..335578a --- /dev/null +++ b/tests/srd-preproc-cmd-misc.cc @@ -0,0 +1,165 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdMiscTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdMiscTest ); + + CPPUNIT_TEST( testIgnore ); + + CPPUNIT_TEST( testUnwrapMissingArg ); + CPPUNIT_TEST( testUnwrapBadArg ); + CPPUNIT_TEST( testUnwrapEmptyList ); + CPPUNIT_TEST( testUnwrapList ); + CPPUNIT_TEST( testUnwrapExtraArgs ); + + CPPUNIT_TEST( testLengthMissingArg ); + CPPUNIT_TEST( testLengthExtraArgs ); + CPPUNIT_TEST( testLengthBadArg ); + CPPUNIT_TEST( testLengthList ); + CPPUNIT_TEST( testLengthWord ); + CPPUNIT_TEST( testLengthString ); + CPPUNIT_TEST( testLengthBinary ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testIgnore( ); + + void testUnwrapMissingArg( ); + void testUnwrapBadArg( ); + void testUnwrapEmptyList( ); + void testUnwrapList( ); + void testUnwrapExtraArgs( ); + + void testLengthMissingArg( ); + void testLengthExtraArgs( ); + void testLengthBadArg( ); + void testLengthList( ); + void testLengthWord( ); + void testLengthString( ); + void testLengthBinary( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdMiscTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMiscTest::testIgnore( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ignore $does-not-exist ( -fail ) bleh )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMiscTest::testUnwrapMissingArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unwrap )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMiscTest::testUnwrapBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unwrap a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "list expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMiscTest::testUnwrapEmptyList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unwrap ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdMiscTest::testUnwrapList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unwrap ( a b c ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "a b c" , output ) ); +} + +void SRDPreprocCmdMiscTest::testUnwrapExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unwrap ( a ) ( b c ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 17 ); + CPPUNIT_ASSERT( check( "a" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdMiscTest::testLengthMissingArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "-1" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthExtraArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 13 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "list, word, string or binary array expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "-1" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length ( a ( b ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "2" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length abc )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "3" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length \"some string\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "11" , output ) ); +} + +void SRDPreprocCmdMiscTest::testLengthBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -length [ 01 02 03 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "3" , output ) ); +} diff --git a/tests/srd-preproc-cmd-strings.cc b/tests/srd-preproc-cmd-strings.cc new file mode 100644 index 0000000..8927566 --- /dev/null +++ b/tests/srd-preproc-cmd-strings.cc @@ -0,0 +1,931 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdStringsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdStringsTest ); + + CPPUNIT_TEST( testConcatNoArgs ); + CPPUNIT_TEST( testConcatBadArgs ); + CPPUNIT_TEST( testConcatOneString ); + CPPUNIT_TEST( testConcatStrings ); + CPPUNIT_TEST( testConcatStringWord ); + CPPUNIT_TEST( testConcatStringNumeric ); + CPPUNIT_TEST( testConcatStringBinary ); + CPPUNIT_TEST( testConcatOneBinary ); + CPPUNIT_TEST( testConcatBinary ); + + CPPUNIT_TEST( testSubstrNotEnoughArgs ); + CPPUNIT_TEST( testSubstrStringNotEnoughArgs ); + CPPUNIT_TEST( testSubstrBinaryNotEnoughArgs ); + CPPUNIT_TEST( testSubstrStringTooManyArgs ); + CPPUNIT_TEST( testSubstrBinaryTooManyArgs ); + CPPUNIT_TEST( testSubstrBadTooManyArgs ); + CPPUNIT_TEST( testSubstrStringOffset ); + CPPUNIT_TEST( testSubstrBinaryOffset ); + CPPUNIT_TEST( testSubstrBadOffset ); + CPPUNIT_TEST( testSubstrStringBad ); + CPPUNIT_TEST( testSubstrStringInvalid ); + CPPUNIT_TEST( testSubstrStringHigh ); + CPPUNIT_TEST( testSubstrStringOffsetLength ); + CPPUNIT_TEST( testSubstrBinaryOffsetLength ); + CPPUNIT_TEST( testSubstrStringOffsetBad ); + CPPUNIT_TEST( testSubstrStringOffsetToOffset ); + CPPUNIT_TEST( testSubstrBinaryOffsetToOffset ); + CPPUNIT_TEST( testSubstrStringRangeInverted ); + CPPUNIT_TEST( testSubstrBinaryRangeInverted ); + CPPUNIT_TEST( testSubstrStringOffsetBadOffset ); + CPPUNIT_TEST( testSubstrStringOffsetToBad ); + CPPUNIT_TEST( testSubstrRangeInvalidStart ); + CPPUNIT_TEST( testSubstrRangeInvalidEnd ); + + CPPUNIT_TEST( testSplitNotEnoughArgs ); + CPPUNIT_TEST( testSplitTooManyArgs ); + CPPUNIT_TEST( testSplitBadString ); + CPPUNIT_TEST( testSplitStringBad ); + CPPUNIT_TEST( testSplitStringEmpty ); + CPPUNIT_TEST( testSplitStringStringBad ); + CPPUNIT_TEST( testSplitStringStringNegative ); + CPPUNIT_TEST( testSplitSingleNormal ); + CPPUNIT_TEST( testSplitSingleStart ); + CPPUNIT_TEST( testSplitSingleEnd ); + CPPUNIT_TEST( testSplitSingleNowhere ); + CPPUNIT_TEST( testSplitMultiNormal ); + CPPUNIT_TEST( testSplitMultiStart ); + CPPUNIT_TEST( testSplitMultiEnd ); + CPPUNIT_TEST( testSplitMultiNowhere ); + CPPUNIT_TEST( testSplitMultiLonger ); + CPPUNIT_TEST( testSplitLimitZero ); + CPPUNIT_TEST( testSplitLimit ); + CPPUNIT_TEST( testSplitLimitGreater ); + + CPPUNIT_TEST( testSWNotEnoughArgs ); + CPPUNIT_TEST( testSWTooManyArgs ); + CPPUNIT_TEST( testSWStringYes ); + CPPUNIT_TEST( testSWStringYesEqual ); + CPPUNIT_TEST( testSWStringYesEmpty ); + CPPUNIT_TEST( testSWStringNoShorter ); + CPPUNIT_TEST( testSWStringNoLonger ); + CPPUNIT_TEST( testSWBinaryYes ); + CPPUNIT_TEST( testSWBinaryYesEqual ); + CPPUNIT_TEST( testSWBinaryYesEmpty ); + CPPUNIT_TEST( testSWBinaryNoShorter ); + CPPUNIT_TEST( testSWBinaryNoLonger ); + CPPUNIT_TEST( testSWWordString ); + CPPUNIT_TEST( testSWStringWord ); + CPPUNIT_TEST( testSWStringBad ); + CPPUNIT_TEST( testSWBadString ); + + CPPUNIT_TEST( testEWNotEnoughArgs ); + CPPUNIT_TEST( testEWTooManyArgs ); + CPPUNIT_TEST( testEWStringYes ); + CPPUNIT_TEST( testEWStringYesEqual ); + CPPUNIT_TEST( testEWStringYesEmpty ); + CPPUNIT_TEST( testEWStringNoShorter ); + CPPUNIT_TEST( testEWStringNoLonger ); + CPPUNIT_TEST( testEWBinaryYes ); + CPPUNIT_TEST( testEWBinaryYesEqual ); + CPPUNIT_TEST( testEWBinaryYesEmpty ); + CPPUNIT_TEST( testEWBinaryNoShorter ); + CPPUNIT_TEST( testEWBinaryNoLonger ); + CPPUNIT_TEST( testEWWordString ); + CPPUNIT_TEST( testEWStringWord ); + CPPUNIT_TEST( testEWStringBad ); + CPPUNIT_TEST( testEWBadString ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + // -concat + void testConcatNoArgs( ); + void testConcatBadArgs( ); + void testConcatOneString( ); + void testConcatStrings( ); + void testConcatStringWord( ); + void testConcatStringNumeric( ); + void testConcatStringBinary( ); + void testConcatOneBinary( ); + void testConcatBinary( ); + + // -substr + void testSubstrNotEnoughArgs( ); // Not enough arguments + void testSubstrStringNotEnoughArgs( ); // String + void testSubstrBinaryNotEnoughArgs( ); // Binary + void testSubstrStringTooManyArgs( ); // String + too many arguments + void testSubstrBinaryTooManyArgs( ); // Binary + too many arguments + void testSubstrBadTooManyArgs( ); // Invalid argument + too many arguments + void testSubstrStringOffset( ); // String and offset arguments + void testSubstrBinaryOffset( ); // Binary data and offset arguments + void testSubstrBadOffset( ); // Invalid argument + offset + void testSubstrStringBad( ); // String + invalid argument + void testSubstrStringInvalid( ); // String, offset < 0 + void testSubstrStringHigh( ); // String, offset > length + void testSubstrStringOffsetLength( ); // String, offset and length arguments + void testSubstrBinaryOffsetLength( ); // Binary, offset and length arguments + void testSubstrStringOffsetBad( ); // String and offset followed by invalid argument + void testSubstrStringOffsetToOffset( ); // String, range mode + void testSubstrBinaryOffsetToOffset( ); // Binary, range mode + void testSubstrStringRangeInverted( ); // String, range mode, end < start + void testSubstrBinaryRangeInverted( ); // Binary, range mode, end < start + void testSubstrStringOffsetBadOffset( ); + // String, range mode, wrong "to" argument + void testSubstrStringOffsetToBad( ); // String, range mode, wrong end argument + void testSubstrRangeInvalidStart( ); // String, range mode, start < 0 + void testSubstrRangeInvalidEnd( ); // String, range mode, end < 0 + + // -str-split + void testSplitNotEnoughArgs( ); // Not enough arguments + void testSplitTooManyArgs( ); // Too many arguments + void testSplitBadString( ); // Invalid argument, string + void testSplitStringBad( ); // String, invalid argument + void testSplitStringEmpty( ); // String, empty string + void testSplitStringStringBad( ); // String, string, invalid argument + void testSplitStringStringNegative( ); // String, string, negative integer + void testSplitSingleNormal( ); // Single-character delimiter, inside string + void testSplitSingleStart( ); // Single-character delimiter, start of string + void testSplitSingleEnd( ); // Single-character delimiter, end of string + void testSplitSingleNowhere( ); // Single-character delimiter, not in string + void testSplitMultiNormal( ); // Multi-character delimiter, inside string + void testSplitMultiStart( ); // Multi-character delimiter, start of string + void testSplitMultiEnd( ); // Multi-character delimiter, end of string + void testSplitMultiNowhere( ); // Multi-character delimiter, not in string + void testSplitMultiLonger( ); // Multi-character delimiter, longer than string + void testSplitLimitZero( ); // With limit set to 0 + void testSplitLimit( ); // With a non-zero limit + void testSplitLimitGreater( ); // With a limit higher than the result count + + // -starts-with + void testSWNotEnoughArgs( ); + void testSWTooManyArgs( ); + void testSWStringYes( ); + void testSWStringYesEqual( ); + void testSWStringYesEmpty( ); + void testSWStringNoShorter( ); + void testSWStringNoLonger( ); + void testSWBinaryYes( ); + void testSWBinaryYesEqual( ); + void testSWBinaryYesEmpty( ); + void testSWBinaryNoShorter( ); + void testSWBinaryNoLonger( ); + void testSWWordString( ); + void testSWStringWord( ); + void testSWStringBad( ); + void testSWBadString( ); + + // -ends-with + void testEWNotEnoughArgs( ); + void testEWTooManyArgs( ); + void testEWStringYes( ); + void testEWStringYesEqual( ); + void testEWStringYesEmpty( ); + void testEWStringNoShorter( ); + void testEWStringNoLonger( ); + void testEWBinaryYes( ); + void testEWBinaryYesEqual( ); + void testEWBinaryYesEmpty( ); + void testEWBinaryNoShorter( ); + void testEWBinaryNoLonger( ); + void testEWWordString( ); + void testEWStringWord( ); + void testEWStringBad( ); + void testEWBadString( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdStringsTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdStringsTest::testConcatNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatBadArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat ( nope ) [ 01 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array, text or numeric argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "[ 01 ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatOneString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat \"string\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"string\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatStrings( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat \"str\" \"ing\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"string\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatStringWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat \"str\" ing )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"string\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatStringNumeric( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat \"i\" 2 \"l\" ( -to-long 2 ) \"f\" 1.2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"i2l2f1.2\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatStringBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat \"string\"\n[ 01 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text or numeric argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 1 ); + CPPUNIT_ASSERT( check( "\"string\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatOneBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat [ ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testConcatBinary( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -concat [ 01 ] [] [ 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ 01 02 ]" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdStringsTest::testSubstrNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 33 ); + CPPUNIT_ASSERT( check( "[]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 1 to 2 blah )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 27 ); + CPPUNIT_ASSERT( check( "\"bc\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] 1 to 2 blah )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 40 ); + CPPUNIT_ASSERT( check( "[]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBadTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr -12 1 to 2 blah )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array or text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"def\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ 04 05 06 ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBadOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr 12 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array or text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 11 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"\" \"\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringInvalid( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" -1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringHigh( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 7 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffsetLength( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"cde\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryOffsetLength( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] 2 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ 03 04 05 ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffsetBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 nope )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "'to' or integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 22 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffsetToOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 to 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"cd\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryOffsetToOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] 2 to 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ 03 04 ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringRangeInverted( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 3 to 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrBinaryRangeInverted( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr [ 01 02 03 04 05 06 ] 3 to 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "[ ]" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffsetBadOffset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 ot 3 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "'to' or integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 22 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrStringOffsetToBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 to meh )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 25 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrRangeInvalidStart( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" -2 to 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSubstrRangeInvalidEnd( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -substr \"abcdef\" 2 to -2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 25 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdStringsTest::testSplitNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"x\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 18 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"b\" 12 a b c )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 25 ); + CPPUNIT_ASSERT( check( "\"b\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitBadString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split 12 \"x\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"x\" 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 18 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitStringEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"\" \"...\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "\"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitStringStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"...\" x )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "integer argument or end of list expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 24 ); + CPPUNIT_ASSERT( check( "\"...\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitStringStringNegative( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"bab\" -1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "invalid argument value" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 24 ); + CPPUNIT_ASSERT( check( "\"b\" \"b\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitSingleNormal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"babaab\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"b\" \"b\" \"\" \"b\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitSingleStart( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"ab\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\" \"b\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitSingleEnd( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"ba\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"b\" \"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitSingleNowhere( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"a\" \"nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"nope\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitMultiNormal( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"aba\" \"babaxabaababa\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"b\" \"x\" \"\" \"ba\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitMultiStart( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"aba\" \"abab\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"\" \"b\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitMultiEnd( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"aba\" \"baba\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"b\" \"\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitMultiNowhere( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"aba\" \"nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"nope\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitMultiLonger( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"moomoo\" \"nope\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"nope\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitLimitZero( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"x\" \"abababa\" 0 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"abababa\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitLimit( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"b\" \"abababa\" 2 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"a\" \"a\" \"aba\"" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSplitLimitGreater( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -str-split \"b\" \"abababa\" 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "\"a\" \"a\" \"a\" \"a\"" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdStringsTest::testSWNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"x\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with x y z )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringYes( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"abc\" \"abcdef\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringYesEqual( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"abc\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringYesEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringNoShorter( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"abc\" \"defg\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringNoLonger( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"defg\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBinaryYes( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with [ 00 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBinaryYesEqual( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with [ 00 01 02 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBinaryYesEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with [ ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBinaryNoShorter( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with [ 01 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBinaryNoLonger( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with [ 00 01 02 ] [ 00 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWWordString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with abc \"abc d\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"abc\" abc-d )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with \"abc\" 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 22 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testSWBadString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -starts-with 12 \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array or text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 16 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdStringsTest::testEWNotEnoughArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"x\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 18 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWTooManyArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with x y z )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 18 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringYes( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"def\" \"abcdef\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringYesEqual( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"abc\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringYesEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringNoShorter( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"abc\" \"defg\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringNoLonger( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"defg\" \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBinaryYes( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with [ 02 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBinaryYesEqual( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with [ 00 01 02 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBinaryYesEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with [ ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBinaryNoShorter( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with [ 01 ] [ 00 01 02 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBinaryNoLonger( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with [ 00 01 02 ] [ 00 ] )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWWordString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with d \"abc d\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringWord( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"c-d\" abc-d )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWStringBad( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with \"abc\" 12 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 20 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} + +void SRDPreprocCmdStringsTest::testEWBadString( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -ends-with 12 \"abc\" )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "binary array or text argument expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 14 ); + CPPUNIT_ASSERT( check( "0" , output ) ); +} diff --git a/tests/srd-preproc-cmd-variables.cc b/tests/srd-preproc-cmd-variables.cc new file mode 100644 index 0000000..5b1b654 --- /dev/null +++ b/tests/srd-preproc-cmd-variables.cc @@ -0,0 +1,372 @@ +#include "srd-preproc-cmd-common.hh" + + +class SRDPreprocCmdVariablesTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCmdVariablesTest ); + + CPPUNIT_TEST( testSetVarNoArgs ); + CPPUNIT_TEST( testSetVarBadName ); + CPPUNIT_TEST( testSetVarEmpty ); + CPPUNIT_TEST( testSetVarBasic ); + CPPUNIT_TEST( testSetVarList ); + CPPUNIT_TEST( testSetVarExisting ); + + CPPUNIT_TEST( testSetVarsNoNames ); + CPPUNIT_TEST( testSetVarsBadName ); + CPPUNIT_TEST( testSetVarsEmpty ); + CPPUNIT_TEST( testSetVarsMissing ); + CPPUNIT_TEST( testSetVarsMultiple ); + CPPUNIT_TEST( testSetVarsRest ); + CPPUNIT_TEST( testSetVarsRepeatName ); + CPPUNIT_TEST( testSetVarsExisting ); + CPPUNIT_TEST( testSetVarsExistingAndRepeated ); + + CPPUNIT_TEST( testUnsetNoArgs ); + CPPUNIT_TEST( testUnset ); + CPPUNIT_TEST( testUnsetMissing ); + CPPUNIT_TEST( testUnsetMultiple ); + CPPUNIT_TEST( testUnsetFromVarBeingUnset ); + + CPPUNIT_TEST( testGetEmpty ); + CPPUNIT_TEST( testGetBadArg ); + CPPUNIT_TEST( testGetMissing ); + CPPUNIT_TEST( testGetVar ); + CPPUNIT_TEST( testGetVars ); + CPPUNIT_TEST( testGetVarsWithErrors ); + + CPPUNIT_TEST( testListVariables ); + CPPUNIT_TEST( testListVariablesArgs ); + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testSetVarNoArgs( ); + void testSetVarBadName( ); + void testSetVarEmpty( ); + void testSetVarBasic( ); + void testSetVarList( ); + void testSetVarExisting( ); + + void testSetVarsNoNames( ); + void testSetVarsBadName( ); + void testSetVarsEmpty( ); + void testSetVarsMissing( ); + void testSetVarsMultiple( ); + void testSetVarsRest( ); + void testSetVarsRepeatName( ); + void testSetVarsExisting( ); + void testSetVarsExistingAndRepeated( ); + + void testUnsetNoArgs( ); + void testUnset( ); + void testUnsetMissing( ); + void testUnsetMultiple( ); + void testUnsetFromVarBeingUnset( ); + + void testGetEmpty( ); + void testGetBadArg( ); + void testGetMissing( ); + void testGetVar( ); + void testGetVars( ); + void testGetVarsWithErrors( ); + + void testListVariables( ); + void testListVariablesArgs( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCmdVariablesTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdVariablesTest::testSetVarNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "not enough arguments" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarBadName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set 1 )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "word or list expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set x )\n$x" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarBasic( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set x y )\n$x" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "y" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarList( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set x y z )\n$x" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "y z" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarExisting( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set x a )\n$x\n( -set x b )\n$x" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "duplicate variable name" , 3 , 3 ); + M_CKERR_( 1 , "previous error cause" , 3 , 8 ); + CPPUNIT_ASSERT( check( "a a" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdVariablesTest::testSetVarsNoNames( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "no variable names" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsBadName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( 1 ( ) ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + M_CKERR_( 2 , "word expected" , 1 , 3 ); + M_CKERR_( 3 , "previous error cause" , 1 , 12 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( a b ) )\n( $b ) ( $a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "()()" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsMissing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( a b ) a )\n( $b ) ( $a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( ) ( a )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( a b ) a b )\n( $b ) ( $a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( b ) ( a )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsRest( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( a b ) a b c )\n( $b ) ( $a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( b c ) ( a )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsRepeatName( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set ( a a ) a b c )\n( $a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERR_( 0 , "duplicate variable name" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 10 ); + M_CKERR_( 2 , "unknown variable" , 2 , 3 ); + CPPUNIT_ASSERT( check( "( )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsExisting( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set b x )\n( -set ( a b ) u v )\n( $a )( $b )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERR_( 0 , "duplicate variable name" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 12 ); + M_CKERR_( 2 , "unknown variable" , 3 , 3 ); + CPPUNIT_ASSERT( check( "( )( x )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testSetVarsExistingAndRepeated( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set b x )\n( -set ( a b b a ) u v )\n( $a )( $b )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 5u , errors.size( ) ); + M_CKERR_( 0 , "duplicate variable name" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + M_CKERR_( 2 , "duplicate variable name" , 2 , 3 ); + M_CKERR_( 3 , "previous error cause" , 2 , 12 ); + M_CKERR_( 4 , "unknown variable" , 3 , 3 ); + CPPUNIT_ASSERT( check( "( )( x )" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdVariablesTest::testUnsetNoArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unset ) " , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testUnset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -set a 1 )\n$a\n( -unset a )\n$a" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 4 , 1 ); + CPPUNIT_ASSERT( check( "1" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testUnsetMissing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -unset a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testUnsetMultiple( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set ( a b c ) 1 2 3)\n" + "( -unset a c )\n" + "( $a $b $c )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 3 , 3 ); + M_CKERR_( 1 , "unknown variable" , 3 , 9 ); + CPPUNIT_ASSERT( check( "( 2 )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testUnsetFromVarBeingUnset( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set ( c b a ) d c a b)\n" + "( -unset $a $b )\n" + "( $a $b $c )" , + errors ) ); + CPPUNIT_ASSERT_EQUAL( 3u , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 2 , 13 ); + M_CKERR_( 1 , "unknown variable" , 3 , 3 ); + M_CKERR_( 2 , "unknown variable" , 3 , 6 ); + CPPUNIT_ASSERT( check( "( d )" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdVariablesTest::testGetEmpty( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -get )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testGetBadArg( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -get 1 ( ) )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 4u , errors.size( ) ); + M_CKERR_( 0 , "word expected" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + M_CKERR_( 2 , "word expected" , 1 , 3 ); + M_CKERR_( 3 , "previous error cause" , 1 , 10 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testGetMissing( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -get a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 1 , 3 ); + M_CKERR_( 1 , "previous error cause" , 1 , 8 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testGetVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set a x)\n( -get a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testGetVars( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set ( a b c ) x y z w )\n( -get c b a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "z w y x" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testGetVarsWithErrors( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "(-set ( a b ) x y )\n( -get b c a )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 10 ); + CPPUNIT_ASSERT( check( "y x" , output ) ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCmdVariablesTest::testListVariables( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x )\n" + "( -set-macro y ( ( ) ) )\n" + "( -set f (-raw (() ((-ls-variables))) ))\n" + "( -bless f )\n" + "( ( -ls-variables ) )\n" + "( -scope\n" + "( -unset x )\n" + "( ( -ls-variables ) )\n" + ")\n" + "($f)\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "( x f ) ( f ) ( f )" , output ) ); +} + +void SRDPreprocCmdVariablesTest::testListVariablesArgs( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "( -set x )\n" + "( -ls-variables blah )\n" + , errors ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + M_CKERR_( 0 , "too many arguments" , 2 , 3 ); + M_CKERR_( 1 , "previous error cause" , 2 , 17 ); + CPPUNIT_ASSERT( check( "x" , output ) ); +} diff --git a/tests/srd-preproc-core.cc b/tests/srd-preproc-core.cc new file mode 100644 index 0000000..1395f3b --- /dev/null +++ b/tests/srd-preproc-core.cc @@ -0,0 +1,245 @@ +#include +#include +#include +#include +using namespace lw; + + +class SRDPreprocCoreTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocCoreTest ); + CPPUNIT_TEST( testEmptyInput ); + CPPUNIT_TEST( testBasicInput ); + CPPUNIT_TEST( testCommentsSkipped ); + CPPUNIT_TEST( testCommandWordInInput ); + CPPUNIT_TEST( testBadCommand ); + CPPUNIT_TEST( testBadVariable ); + CPPUNIT_TEST( testUnterminatedLists ); + CPPUNIT_TEST( testTooManyErrorsInside ); + CPPUNIT_TEST( testTooManyErrorsAtEnd ); + CPPUNIT_TEST_SUITE_END( ); + + public: + void testEmptyInput( ); + void testBasicInput( ); + void testCommentsSkipped( ); + void testCommandWordInInput( ); + void testBadCommand( ); + void testBadVariable( ); + void testUnterminatedLists( ); + void testTooManyErrorsInside( ); + void testTooManyErrorsAtEnd( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocCoreTest ); + +/*----------------------------------------------------------------------------*/ + +// M_PRINTERR_( index ) - Print an error message +#define M_PRINTERR_( IDX ) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + char err[ _e.error( ).size( ) + 1 ]; \ + err[ sizeof( err ) - 1 ] = 0; \ + memcpy( err , _e.error( ).data( ) , \ + sizeof( err ) - 1 ); \ + printf( "ERR %s l. %u c. %lu\n" , err , \ + _e.location( ).line( ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +// M_CKERR_( index , string , line , character ) - Check an error +#define M_CKERR_( IDX , STR , L , C ) \ + do { \ + auto const& _e( errors[ IDX ] ); \ + CPPUNIT_ASSERT( T_String( STR ) == _e.error( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( L ) , \ + _e.location( ).line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( C ) , \ + _e.location( ).character( ) ); \ + } while ( 0 ) + +namespace { + +T_SRDList process( + char const* const input , + T_SRDErrors& errors , + const bool clearFlushToken = true ) +{ + T_SRDMemoryTarget mt( false ); + mt.clearFlushToken( clearFlushToken ); + mt.start( errors ); + + T_SRDPreprocessorConfig cmd; + cmd.addBuiltinCommands( ); + T_SRDPreprocessor pp( cmd , mt ); + T_SRDLexer lexer( T_String( "test" ) , errors , pp ); + pp.start( errors ); + char const* ptr = input; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); + mt.end( errors ); + + return mt.list( ); +} + +bool checkMatch( T_SRDList const& expected , T_SRDList const& actual ) +{ + const uint32_t nExpected( expected.size( ) ); + const uint32_t nActual( actual.size( ) ); + + bool ok( nExpected == nActual ); + const uint32_t nCheck( std::min( nExpected , nActual ) ); + for ( uint32_t i = 0 ; i < nCheck ; i ++ ) { + T_SRDToken const& tExpected( expected[ i ] ); + T_SRDToken const& tActual( actual[ i ] ); + if ( tExpected.type( ) != tActual.type( ) ) { + std::cerr << "Expected token at " + << tExpected.location( ).line( ) << ":" + << tExpected.location( ).character( ) + << " has type " << int( tExpected.type( ) ) << "; actual token has type " + << int( tActual.type( ) ) << "\n"; + ok = false; + } else if ( tExpected.stringValue( ) != tActual.stringValue( ) ) { + std::cerr << "Expected token at " + << tExpected.location( ).line( ) << ":" + << tExpected.location( ).character( ) + << " has different text\n"; + ok = false; + } + } + + if ( nExpected != nActual ) { + std::cerr << "List size mismatch (" << nExpected << " expected, " + << nActual << " found)\n"; + } + return ok; +} + +bool check( char const* expected , T_SRDList const& actual ) +{ + T_SRDMemoryTarget mt( false ); + T_SRDErrors errors; + T_SRDLexer lexer( T_String( "expected" ) , errors , mt ); + mt.start( errors ); + char const* ptr = expected; + while ( *ptr != 0 ) { + lexer.processCharacter( *ptr ++ ); + } + lexer.processEnd( ); + + return checkMatch( mt.list( ) , actual ); +} + +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocCoreTest::testEmptyInput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCoreTest::testBasicInput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "this is a simple test 123 \"yes\" ( a test )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "this is a simple test 123 \"yes\" ( a test )" , output ) ); +} + +void SRDPreprocCoreTest::testCommentsSkipped( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "a {comment!} ( is #skipped!\n)" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "a ( is )" , output ) ); +} + +void SRDPreprocCoreTest::testCommandWordInInput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "-blah" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , errors.size( ) ); + CPPUNIT_ASSERT( check( "-blah" , output ) ); +} + +void SRDPreprocCoreTest::testBadCommand( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( -blah )" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unknown command" , 1 , 3 ); + CPPUNIT_ASSERT( check( "( -blah )" , output ) ); +} + +void SRDPreprocCoreTest::testBadVariable( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "$blah does not exist" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 1 ) , errors.size( ) ); + M_CKERR_( 0 , "unknown variable" , 1 , 1 ); + CPPUNIT_ASSERT( check( "does not exist" , output ) ); +} + +void SRDPreprocCoreTest::testUnterminatedLists( ) +{ + T_SRDErrors errors; + T_SRDList output( process( "( ( ) ( (" , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 3 ) , errors.size( ) ); + M_CKERR_( 0 , "unterminated list" , 1 , 1 ); + M_CKERR_( 1 , "unterminated list" , 1 , 7 ); + M_CKERR_( 2 , "unterminated list" , 1 , 9 ); + CPPUNIT_ASSERT( check( "( ( ) ( ( ) ) )" , output ) ); +} + +namespace { + +T_StringBuilder RepeatError_( char const* string ) +{ + T_StringBuilder sb; + for ( uint32_t i = 0 ; i < T_SRDErrors::MAX_ERRORS * 2 ; i ++ ) { + if ( i > 0 ) { + sb << ' '; + } + sb << string; + } + sb << " x" << '\0'; + return sb; +} + +} + +void SRDPreprocCoreTest::testTooManyErrorsInside( ) +{ + T_StringBuilder input( RepeatError_( "$x" ) ); + T_SRDErrors errors; + T_SRDList output( process( input.data( ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDErrors::MAX_ERRORS + 1 ) , errors.size( ) ); + M_CKERR_( T_SRDErrors::MAX_ERRORS , "too many errors" , 1 , T_SRDErrors::MAX_ERRORS * 3 - 2 ); + CPPUNIT_ASSERT( check( "" , output ) ); +} + +void SRDPreprocCoreTest::testTooManyErrorsAtEnd( ) +{ + T_StringBuilder input( RepeatError_( "(" ) ); + T_StringBuilder expectedOutput; + for ( uint32_t i = 0 ; i < T_SRDErrors::MAX_ERRORS * 2 ; i ++ ) { + expectedOutput << '('; + } + expectedOutput << 'x'; + for ( uint32_t i = 0 ; i < T_SRDErrors::MAX_ERRORS * 2 ; i ++ ) { + expectedOutput << ')'; + } + expectedOutput << '\0'; + + T_SRDErrors errors; + T_SRDList output( process( input.data( ) , errors ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_SRDErrors::MAX_ERRORS + 1 ) , errors.size( ) ); + M_CKERR_( T_SRDErrors::MAX_ERRORS , "too many errors" , 1 , T_SRDErrors::MAX_ERRORS * 2 - 1 ); + CPPUNIT_ASSERT( check( expectedOutput.data( ) , output ) ); +} diff --git a/tests/srd-preproc-location.hh b/tests/srd-preproc-location.hh new file mode 100644 index 0000000..bd66621 --- /dev/null +++ b/tests/srd-preproc-location.hh @@ -0,0 +1,133 @@ +#ifndef TESTS_SRDPREPROCLOCATION_H_ +#define TESTS_SRDPREPROCLOCATION_H_ + +#include +#include +#include +#include +#include +using namespace lw; + + +namespace { + +inline T_SRDLocationChaining const* NextChain_( + T_SRDLocation const* from ) noexcept +{ + return (from && from->isChained( ) ) ? &from->chaining( ) : nullptr; +} + +uint32_t LocationDepth_( + const RPC_SRDLocation location ) noexcept +{ + uint32_t depth( 0 ); + auto lc( NextChain_( location ) ); + while ( lc ) { + depth ++; + lc = NextChain_( RPC_SRDLocation( lc->location ) ); + } + return depth; +} + +uint32_t ErrorDepth_( + T_SRDErrors const& errors , + const size_t index ) noexcept +{ + auto const& error( errors[ index ] ); + return LocationDepth_( &error.location( ) ); +} + +uint32_t TokenDepth_( + T_SRDList const& tokens , + const size_t index ) noexcept +{ + auto const& tok( tokens[ index ] ); + assert( tok.hasLocation( ) ); + return LocationDepth_( &tok.location( ) ); +} + +T_SRDLocationChaining const& GetChain_( + RPC_SRDLocation const& top , + uint32_t depth ) noexcept +{ + auto lc( NextChain_( top ) ); + assert( lc ); + while ( depth ) { + depth --; + lc = NextChain_( RPC_SRDLocation( lc->location ) ); + assert( lc ); + } + return *lc; +} + +void PrintLoc_( RPC_SRDLocation location ) noexcept +{ + printf( "at %s, l. %d c. %lu\n" , location->source( ).toOSString( ).data( ) , + location->line( ) , location->character( ) ); + if ( location->isChained( ) ) { + auto const& chain( location->chaining( ) ); + printf( "\tChain type %d, depth %d, " , + int( chain.circumstances ) , chain.depth ); + PrintLoc_( RPC_SRDLocation( chain.location ) ); + } +} + +} + +#define M_CKTOKDEPTH_( IDX , DEPTH ) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( DEPTH ) , TokenDepth_( output , IDX ) ) + +#define M_CKTOKLOC_( IDX , NAME , LINE , CHAR ) \ + do { \ + auto const& loc( output[ IDX ].location( ) ); \ + CPPUNIT_ASSERT( T_String{ NAME } == loc.source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( LINE ) , loc.line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( CHAR ) , loc.character( ) ); \ + } while ( 0 ) + +#define M_CKTOKFULL_( IDX , NAME , LINE , CHAR , DEPTH ) \ + do { \ + auto const& loc( output[ IDX ].location( ) ); \ + CPPUNIT_ASSERT( T_String{ NAME } == loc.source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( LINE ) , loc.line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( CHAR ) , loc.character( ) ); \ + M_CKTOKDEPTH_( IDX , DEPTH ); \ + } while ( 0 ) + +#define M_CKTOKCHAIN_( IDX , DEPTH , HOW , NAME , LINE , CHAR , REC ) \ + do { \ + auto const& chain( GetChain_( &output[ IDX ].location( ) , DEPTH ) ); \ + CPPUNIT_ASSERT( E_SRDLocationChaining::HOW == chain.circumstances ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( REC ) , chain.depth ); \ + CPPUNIT_ASSERT( T_String{ NAME } == chain.location->source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( LINE ) , chain.location->line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( CHAR ) , chain.location->character( ) ); \ + } while ( 0 ) + + +#define M_CKERRDEPTH_( IDX , DEPTH ) \ + CPPUNIT_ASSERT_EQUAL( uint32_t( DEPTH ) , ErrorDepth_( errors , IDX ) ) + +#define M_CKERRFULL_( IDX , TEXT , DETAILS , NAME , LINE , CHAR , DEPTH ) \ + do { \ + CPPUNIT_ASSERT( T_String{ TEXT } == errors[ IDX ].error( ) ); \ + CPPUNIT_ASSERT_EQUAL( bool( DETAILS ) , errors[ IDX ].isDetails( ) ); \ + auto const& loc( errors[ IDX ].location( ) ); \ + CPPUNIT_ASSERT( T_String{ NAME } == loc.source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( LINE ) , loc.line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( CHAR ) , loc.character( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( DEPTH ) , ErrorDepth_( errors , IDX ) ); \ + } while ( 0 ) + +#define M_CKERRCHAIN_( IDX , DEPTH , HOW , NAME , LINE , CHAR , REC ) \ + do { \ + auto const& chain( GetChain_( &errors[ IDX ].location( ) , DEPTH ) ); \ + CPPUNIT_ASSERT( E_SRDLocationChaining::HOW == chain.circumstances ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( REC ) , chain.depth ); \ + CPPUNIT_ASSERT( T_String{ NAME } == chain.location->source( ) ); \ + CPPUNIT_ASSERT_EQUAL( uint32_t( LINE ) , chain.location->line( ) ); \ + CPPUNIT_ASSERT_EQUAL( size_t( CHAR ) , chain.location->character( ) ); \ + } while ( 0 ) + + +#endif // TESTS_SRDPREPROCLOCATION_H_ diff --git a/tests/srd-preproc-tracking.cc b/tests/srd-preproc-tracking.cc new file mode 100644 index 0000000..64cf5f9 --- /dev/null +++ b/tests/srd-preproc-tracking.cc @@ -0,0 +1,676 @@ +#include "srd-preproc-cmd-common.hh" +#include "srd-preproc-location.hh" +using namespace lw; + + +class SRDPreprocTrackingTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDPreprocTrackingTest ); + + CPPUNIT_TEST( testTopLevel ); + + CPPUNIT_TEST( testVar ); + CPPUNIT_TEST( testVarGet ); + + CPPUNIT_TEST( testCall ); + CPPUNIT_TEST( testCalls ); + CPPUNIT_TEST( testCallRecursive ); + + CPPUNIT_TEST( testEval ); + CPPUNIT_TEST( testEvalCalls ); + + CPPUNIT_TEST( testCommandOutput ); + +#if 0 + CPPUNIT_TEST( testMacroBody ); + CPPUNIT_TEST( testMacroBodyCall ); + CPPUNIT_TEST( testMacroBodyEval ); + CPPUNIT_TEST( testCallMacroBody ); + CPPUNIT_TEST( testEvalMacroBody ); + + CPPUNIT_TEST( testMacroOutput ); + CPPUNIT_TEST( testMacroOutputCall ); + CPPUNIT_TEST( testMacroOutputEval ); + CPPUNIT_TEST( testCallMacroOutput ); + CPPUNIT_TEST( testEvalMacroOutput ); + +#endif +// CPPUNIT_TEST( testErrorTopLevel ); +#if 0 + CPPUNIT_TEST( testErrorCall ); +#endif + + CPPUNIT_TEST_SUITE_END( ); + + public: + void testTopLevel( ); + + void testVar( ); + void testVarGet( ); + + void testCall( ); + void testCalls( ); + void testCallRecursive( ); + + void testEval( ); + void testEvalCalls( ); + + void testCommandOutput( ); + +#if 0 + void testMacroBody( ); + void testMacroBodyCall( ); + void testMacroBodyEval( ); + void testCallMacroBody( ); + void testEvalMacroBody( ); + + void testMacroOutput( ); + void testMacroOutputCall( ); + void testMacroOutputEval( ); + void testCallMacroOutput( ); + void testEvalMacroOutput( ); + +#endif + void testErrorTopLevel( ); +#if 0 + void testErrorCall( ); +#endif +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDPreprocTrackingTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testTopLevel( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "a\n" + "(-scope b)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "a b" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 0 ); + M_CKTOKLOC_( 0 , "test" , 1 , 1 ); + M_CKTOKDEPTH_( 1 , 0 ); + M_CKTOKLOC_( 1 , "test" , 2 , 9 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testVar( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set var value)\n" + "$var\n" + , errors ) ); + CPPUNIT_ASSERT( check( "value" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "$var" , 0 , 0 ); + M_CKTOKCHAIN_( 0 , 0 , SUBSTITUTED , "test" , 2 , 1 , 0 ); +} + +void SRDPreprocTrackingTest::testVarGet( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set (var vn) value var)\n" + "(-get var $vn)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "value value" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "$var" , 0 , 0 ); + M_CKTOKCHAIN_( 0 , 0 , SUBSTITUTED , "test" , 2 , 2 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "$var" , 0 , 0 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 2 , 2 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testCall( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( () b )))\n" + "(-bless fn)\n" + "a ($fn)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "a b" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 0 ); + M_CKTOKLOC_( 0 , "test" , 3 , 1 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "test" , 1 , 21 ); + M_CKTOKCHAIN_( 1 , 0 , CALLED , "test" , 3 , 4 , 0 ); +} + +void SRDPreprocTrackingTest::testCalls( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set ( fn1 fn2 ) (-raw\n" + "((d)\n" + "I ($fn2 $d) )\n" + "((d)\n" + "got $d)\n" + "))\n" + "(-bless fn1 fn2)\n" + "($fn1 it)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "I got it" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test" , 3 , 1 ); + M_CKTOKCHAIN_( 0 , 0 , CALLED , "test" , 8 , 2 , 0 ); + M_CKTOKDEPTH_( 1 , 2 ); + M_CKTOKLOC_( 1 , "test" , 5 , 1 ); + M_CKTOKCHAIN_( 1 , 0 , CALLED , "test" , 3 , 4 , 0 ); + M_CKTOKCHAIN_( 1 , 1 , CALLED , "test" , 8 , 2 , 0 ); + M_CKTOKDEPTH_( 2 , 3 ); + M_CKTOKLOC_( 2 , "$d" , 0 , 0 ); + M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "test" , 5 , 5 , 0 ); + M_CKTOKCHAIN_( 2 , 1 , CALLED , "test" , 3 , 4 , 0 ); + M_CKTOKCHAIN_( 2 , 2 , CALLED , "test" , 8 , 2 , 0 ); +} + +void SRDPreprocTrackingTest::testCallRecursive( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( (d)\n" + "$d\n" + "(-if (-gt $d 0) (\n" + "($fn (-sub $d 1))\n" + ")))))\n" + "(-bless fn)\n" + "($fn 6)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "6 5 4 3 2 1 0" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 2 ); + M_CKTOKCHAIN_( 0 , 0 , SUBSTITUTED , "test" , 2 , 1 , 0 ); + M_CKTOKCHAIN_( 0 , 1 , CALLED , "test" , 7 , 2 , 0 ); + + for ( auto i = 1u ; i < 7 ; i ++ ) { + M_CKTOKDEPTH_( i , 3 ); + M_CKTOKCHAIN_( i , 0 , SUBSTITUTED , "test" , 2 , 1 , 0 ); + M_CKTOKCHAIN_( i , 1 , CALLED , "test" , 4 , 2 , i - 1 ); + M_CKTOKCHAIN_( i , 2 , CALLED , "test" , 7 , 2 , 0 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set (x y f)\n" + "(-raw $y)\n" + "x\n" + "(() (-raw a (-raw $y) b))\n" + ")\n" + "(-bless f)\n" + "(-eval x $y $x ($f))\n" + , errors ) ); + CPPUNIT_ASSERT( check( "x x x a x b" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 0 ); + M_CKTOKLOC_( 0 , "test" , 7 , 8 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "$y" , 0 , 0 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 7 , 10 , 0 ); + M_CKTOKDEPTH_( 2 , 2 ); + M_CKTOKLOC_( 2 , "$y" , 0 , 0 ); + M_CKTOKCHAIN_( 2 , 0 , SUBSTITUTED , "$x" , 0 , 0 , 0 ); + M_CKTOKCHAIN_( 2 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 3 , 1 ); + M_CKTOKLOC_( 3 , "test" , 4 , 11 ); + M_CKTOKCHAIN_( 3 , 0 , CALLED , "test" , 7 , 17 , 0 ); + M_CKTOKDEPTH_( 4 , 2 ); + M_CKTOKLOC_( 4 , "$y" , 0 , 0 ); + M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 19 , 0 ); + M_CKTOKCHAIN_( 4 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 5 , 1 ); + M_CKTOKLOC_( 5 , "test" , 4 , 23 ); + M_CKTOKCHAIN_( 5 , 0 , CALLED , "test" , 7 , 17 , 0 ); +} + +void SRDPreprocTrackingTest::testEvalCalls( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set (x y f)\n" + "(-raw ($f 2))\n" + "(-raw $x)\n" + "(-raw ((x) a $x b))\n" + ")\n" + "(-bless f)\n" + "(-eval ($f 1) $x $y $x)\n" + , errors ) ); +#if 0 + printf( "\n\n" ); + for ( auto i = 0u ; i < output.size( ) ; i ++ ) { + PrintLoc_( &output[ i ].location( ) ); + printf( "\n" ); + } +#endif + CPPUNIT_ASSERT( check( "a 1 b a 2 b ($f 2) a 2 b" , output ) ); + + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "test" , 4 , 12 ); + M_CKTOKCHAIN_( 0 , 0 , CALLED , "test" , 7 , 9 , 0 ); + M_CKTOKDEPTH_( 1 , 2 ); + M_CKTOKLOC_( 1 , "$x" , 0 , 0 ); + M_CKTOKCHAIN_( 1 , 0 , SUBSTITUTED , "test" , 4 , 14 , 0 ); + M_CKTOKCHAIN_( 1 , 1 , CALLED , "test" , 7 , 9 , 0 ); + M_CKTOKDEPTH_( 2 , 1 ); + M_CKTOKLOC_( 2 , "test" , 4 , 17 ); + M_CKTOKCHAIN_( 2 , 0 , CALLED , "test" , 7 , 9 , 0 ); + + M_CKTOKDEPTH_( 3 , 2 ); + M_CKTOKLOC_( 3 , "test" , 4 , 12 ); + M_CKTOKCHAIN_( 3 , 0 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 3 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 4 , 3 ); + M_CKTOKLOC_( 4 , "$x" , 0 , 0 ); + M_CKTOKCHAIN_( 4 , 0 , SUBSTITUTED , "test" , 4 , 14 , 0 ); + M_CKTOKCHAIN_( 4 , 1 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 4 , 2 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 5 , 2 ); + M_CKTOKLOC_( 5 , "test" , 4 , 17 ); + M_CKTOKCHAIN_( 5 , 0 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 5 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + + for ( auto i = 0u ; i < 4 ; i ++ ) { + M_CKTOKDEPTH_( i + 6 , 2 ); + M_CKTOKLOC_( i + 6 , "$x" , 0 , i ); + M_CKTOKCHAIN_( i + 6 , 0 , SUBSTITUTED , "$y" , 0 , 0 , 0 ); + M_CKTOKCHAIN_( i + 6 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + } + + M_CKTOKDEPTH_( 10 , 2 ); + M_CKTOKLOC_( 10 , "test" , 4 , 12 ); + M_CKTOKCHAIN_( 10 , 0 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 10 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 11 , 3 ); + M_CKTOKLOC_( 11 , "$x" , 0 , 0 ); + M_CKTOKCHAIN_( 11 , 0 , SUBSTITUTED , "test" , 4 , 14 , 0 ); + M_CKTOKCHAIN_( 11 , 1 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 11 , 2 , EVALUATED , "test" , 7 , 2 , 0 ); + M_CKTOKDEPTH_( 12 , 2 ); + M_CKTOKLOC_( 12 , "test" , 4 , 17 ); + M_CKTOKCHAIN_( 12 , 0 , CALLED , "$x" , 0 , 1 , 0 ); + M_CKTOKCHAIN_( 12 , 1 , EVALUATED , "test" , 7 , 2 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testCommandOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-add 1 2)\n" + "(-is-set x)\n" + "(-type-of w)\n" + "(-eq a b)\n" + "(-to-string x \"x\")\n" + "(-length abcdef)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "3 0 word 0 \"x\" \"x\" 6" , output ) ); + CPPUNIT_ASSERT_EQUAL( 0u , errors.size( ) ); + + M_CKTOKDEPTH_( 0 , 1 ); + M_CKTOKLOC_( 0 , "-add" , 0 , 0 ); + M_CKTOKCHAIN_( 0 , 0 , GENERATED , "test" , 1 , 2 , 0 ); + M_CKTOKDEPTH_( 1 , 1 ); + M_CKTOKLOC_( 1 , "-is-set" , 0 , 0 ); + M_CKTOKCHAIN_( 1 , 0 , GENERATED , "test" , 2 , 2 , 0 ); + M_CKTOKDEPTH_( 2 , 1 ); + M_CKTOKLOC_( 2 , "-type-of" , 0 , 0 ); + M_CKTOKCHAIN_( 2 , 0 , GENERATED , "test" , 3 , 2 , 0 ); + M_CKTOKDEPTH_( 3 , 1 ); + M_CKTOKLOC_( 3 , "-eq" , 0 , 0 ); + M_CKTOKCHAIN_( 3 , 0 , GENERATED , "test" , 4 , 2 , 0 ); + M_CKTOKDEPTH_( 4 , 1 ); + M_CKTOKLOC_( 4 , "-to-string" , 0 , 0 ); + M_CKTOKCHAIN_( 4 , 0 , GENERATED , "test" , 5 , 2 , 0 ); + M_CKTOKDEPTH_( 5 , 0 ); + M_CKTOKLOC_( 5 , "test" , 5 , 15 ); + M_CKTOKDEPTH_( 6 , 1 ); + M_CKTOKLOC_( 6 , "-length" , 0 , 0 ); + M_CKTOKCHAIN_( 6 , 0 , GENERATED , "test" , 6 , 2 , 0 ); +} + +#if 0 +void SRDPreprocTrackingTest::testCallRecursive( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( (d)\n" + "(-if $d (\n" + "($fn (-sub $d 1))\n" + ") (\n" + "(-error)\n" + ")))))\n" + "(-bless fn)\n" + "($fn 6)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 5 , 1 ); + M_CKERRDEPTH_( 0 , 2 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 3 , 1 , 6 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 8 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testCalls( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn1 (-raw ( (d)\n" + "(-if $d (\n" + "($fn1 (-sub $d 1))\n" + ") (\n" + "($fn2 4)\n" + ")))))\n" + "(-set fn2 (-raw ( (d)\n" + "(-if $d (\n" + "($fn2 (-sub $d 1))\n" + ") (\n" + "(-error)\n" + ")))))\n" + "(-bless fn1 fn2)\n" + "($fn1 6)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 11 , 1 ); + M_CKERRDEPTH_( 0 , 4 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 9 , 1 , 4 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 5 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::CALLED , 3 , 1 , 6 ); + M_CKERRCHAIN_( 0 , 3 , E_SRDLocationChaining::CALLED , 14 , 1 , 1 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-eval\n" + "( (-raw -error ) )\n" + ")\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 2 , 1 ); + M_CKERRDEPTH_( 0 , 1 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 1 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testEvalCall( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( (d)\n" + "(-if $d (\n" + "($fn (-sub $d 1))\n" + ") (\n" + "(-error)\n" + ")))))\n" + "(-bless fn)\n" + "(-eval (-raw ($fn 6)))\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 5 , 1 ); + M_CKERRDEPTH_( 0 , 3 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 3 , 1 , 6 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 8 , 14 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::EVALUATED , 8 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testCallEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( (d)\n" + "(-if $d (\n" + "($fn (-sub $d 1))\n" + ") (\n" + "(-eval (-raw (-error)))\n" + ")))))\n" + "(-bless fn)\n" + "($fn 6)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 5 , 14 ); + M_CKERRDEPTH_( 0 , 3 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 5 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 3 , 1 , 6 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::CALLED , 8 , 1 , 1 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testMacroBody( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-error) )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 26 ); + M_CKERRDEPTH_( 0 , 1 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 2 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testMacroBodyCall( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( () (-error) )))\n" + "(-bless fn)\n" + "(-set-macro m (-raw ( () ($fn) )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 21 ); + M_CKERRDEPTH_( 0 , 2 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 3 , 26 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 4 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testMacroBodyEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-eval (-raw (-error))) )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 39 ); + M_CKERRDEPTH_( 0 , 2 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 1 , 26 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 2 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testCallMacroBody( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-error) )))\n" + "(-set fn (-raw ( () (m))))\n" + "(-bless fn)\n" + "($fn)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 26 ); + M_CKERRDEPTH_( 0 , 2 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 2 , 21 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::CALLED , 4 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testEvalMacroBody( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-error) )))\n" + "(-eval (-raw (m)))\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 26 ); + M_CKERRDEPTH_( 0 , 2 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 2 , 14 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::EVALUATED , 2 , 1 , 1 ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testMacroOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-raw (-error)) )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 32 ); + M_CKERRDEPTH_( 0 , 3 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 2 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::GENERATED , 1 , 32 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::CALLED , 2 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testMacroOutputCall( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set fn (-raw ( () (-error) )))\n" + "(-bless fn)\n" + "(-set-macro m (-raw ( () (-raw ($fn)) )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 21 ); + M_CKERRDEPTH_( 0 , 4 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::CALLED , 3 , 32 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::EVALUATED , 4 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::GENERATED , 3 , 32 , 1 ); + M_CKERRCHAIN_( 0 , 3 , E_SRDLocationChaining::CALLED , 4 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testMacroOutputEval( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-raw\n" + "(-eval (-raw (-error)))\n" + ") )))\n" + "(m)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 2 , 14 ); + M_CKERRDEPTH_( 0 , 4 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 2 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::EVALUATED , 4 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::GENERATED , 2 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 3 , E_SRDLocationChaining::CALLED , 4 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testCallMacroOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-raw\n" + "(-error)\n" + ") )))\n" + "(-set fn (-raw ( () (m) )))\n" + "(-bless fn)\n" + "($fn)\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 2 , 1 ); + M_CKERRDEPTH_( 0 , 4 ); + M_CKERRCHAIN_( 0 , 0 , E_SRDLocationChaining::EVALUATED , 4 , 21 , 1 ); + M_CKERRCHAIN_( 0 , 1 , E_SRDLocationChaining::GENERATED , 2 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 2 , E_SRDLocationChaining::CALLED , 4 , 21 , 1 ); + M_CKERRCHAIN_( 0 , 3 , E_SRDLocationChaining::CALLED , 6 , 1 , 1 ); +} + +void SRDPreprocTrackingTest::testEvalMacroOutput( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-set-macro m (-raw ( () (-raw\n" + "(-error)\n" + ") )))\n" + "(-eval (-raw (m)))\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 1u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 2 , 1 ); + M_CKERRDEPTH_( 0 , 4 ); + M_CKERRCHAIN_( 0 , 0 , EVALUATED , 4 , 14 , 1 ); + M_CKERRCHAIN_( 0 , 1 , GENERATED , 2 , 1 , 1 ); + M_CKERRCHAIN_( 0 , 2 , CALLED , 4 , 14 , 1 ); + M_CKERRCHAIN_( 0 , 3 , EVALUATED , 4 , 1 , 1 ); +} +#endif + +/*----------------------------------------------------------------------------*/ + +void SRDPreprocTrackingTest::testErrorTopLevel( ) +{ + T_SRDErrors errors; + T_SRDList output( process( + "(-error)\n" + "(-scope (-error))\n" + , errors ) ); + CPPUNIT_ASSERT( check( "" , output ) ); + CPPUNIT_ASSERT_EQUAL( 2u , errors.size( ) ); + + M_CKERR_( 0 , "user error" , 1 , 2 ); + M_CKERRDEPTH_( 0 , 0 ); + + M_CKERR_( 1 , "user error" , 2 , 10 ); + M_CKERRDEPTH_( 1 , 0 ); +} diff --git a/tests/srd-text-writer.cc b/tests/srd-text-writer.cc new file mode 100644 index 0000000..2e90d32 --- /dev/null +++ b/tests/srd-text-writer.cc @@ -0,0 +1,331 @@ +#include +#include +#include +#include +using namespace lw; + + +class SRDTextWriterTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SRDTextWriterTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testList ); + CPPUNIT_TEST( testListRecursive ); + CPPUNIT_TEST( testListUnbalanced ); + + CPPUNIT_TEST( testWord ); + CPPUNIT_TEST( testString ); + CPPUNIT_TEST( testStringEscapes ); + CPPUNIT_TEST( testStringUTF8Short ); + CPPUNIT_TEST( testStringUTF8Long ); + + CPPUNIT_TEST( testTextWord ); + CPPUNIT_TEST( testTextString ); + + CPPUNIT_TEST( testCommentLine ); + CPPUNIT_TEST( testCommentMultiLines ); + + CPPUNIT_TEST( testVar ); + + CPPUNIT_TEST( testInt ); + CPPUNIT_TEST( testInt32 ); + CPPUNIT_TEST( testInt64 ); + CPPUNIT_TEST( testFloat ); + + CPPUNIT_TEST( testBinaryEmpty ); + CPPUNIT_TEST( testBinaryShort ); + CPPUNIT_TEST( testBinaryLong ); + + CPPUNIT_TEST( testIndentation ); + CPPUNIT_TEST_SUITE_END( ); + + T_Buffer< char > buffer; + OP_MemoryOutputStream stream; + OP_SRDTextWriter writer; + T_String result; + + T_String getResult( ); + +public: + void setUp( ) override; + void tearDown( ) override; + + void testEmpty( ); + + void testList( ); + void testListRecursive( ); + void testListUnbalanced( ); + + void testWord( ); + void testString( ); + void testStringEscapes( ); + void testStringUTF8Short( ); + void testStringUTF8Long( ); + + void testTextWord( ); + void testTextString( ); + + void testCommentLine( ); + void testCommentMultiLines( ); + + void testVar( ); + + void testInt( ); + void testInt32( ); + void testInt64( ); + void testFloat( ); + + void testBinaryEmpty( ); + void testBinaryShort( ); + void testBinaryLong( ); + + void testIndentation( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( SRDTextWriterTest ); + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::setUp( ) +{ + stream = NewOwned< T_MemoryOutputStream >( buffer ); + writer = NewOwned< T_SRDTextWriter >( *stream ); + writer->start( ); +} + +void SRDTextWriterTest::tearDown( ) +{ + writer.clear( ); + stream.clear( ); + buffer.resize( 0 ); +} + +T_String SRDTextWriterTest::getResult( ) +{ + if ( writer ) { + writer->end( ); + writer.clear( ); + stream.clear( ); + if ( buffer.size( ) != 0 ) { + return T_String( &buffer[ 0 ] , buffer.size( ) ); + } + return T_String( ); + } + CPPUNIT_FAIL( "error in test" ); + return T_String( ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testEmpty( ) +{ + CPPUNIT_ASSERT( getResult( ) == "" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testList( ) +{ + writer->startList( ); + writer->endList( ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "()\r\n" ); +} + +void SRDTextWriterTest::testListRecursive( ) +{ + writer->startList( ); + writer->startList( ); + writer->endList( ); + writer->endList( ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "(())\r\n" ); +} + +void SRDTextWriterTest::testListUnbalanced( ) +{ + writer->startList( ); + writer->startList( ); + writer->endList( ); + try { + getResult( ); + CPPUNIT_FAIL( "X_SRDWriterError not thrown" ); + } catch ( X_SRDWriterError const& ) { + // OK + } +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testWord( ) +{ + writer->putWord( T_String( "this-is-a-word" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "this-is-a-word\r\n" ); +} + +void SRDTextWriterTest::testString( ) +{ + writer->putString( T_String( "just a string" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "\"just a string\"\r\n" ); +} + +void SRDTextWriterTest::testStringEscapes( ) +{ + writer->putString( T_String( "\n\r\t\x08\x0c\"\\" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "\"\\n\\r\\t\\b\\f\\\"\\\\\"\r\n" ); +} + +void SRDTextWriterTest::testStringUTF8Short( ) +{ + writer->putString( T_String( "\x01\xc3\x81\xc7\x81\xe2\x82\xac" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "\"\\u0001\\u00C1\\u01C1\\u20AC\"\r\n" ); +} + +void SRDTextWriterTest::testStringUTF8Long( ) +{ + writer->putString( T_String( "\xf0\x91\x87\x81" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "\"\\U000111C1\"\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testTextWord( ) +{ + writer->putText( T_String( "-whatever-that-is" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "-whatever-that-is\r\n" ); +} + +void SRDTextWriterTest::testTextString( ) +{ + writer->putText( T_String( "-whatever-that-is\x01" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "\"-whatever-that-is\\u0001\"\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testCommentLine( ) +{ + writer->putComment( T_String( " a comment" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "# a comment\r\n" ); +} + +void SRDTextWriterTest::testCommentMultiLines( ) +{ + writer->putComment( T_String( " a comment\n with a few\r\n lines" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "# a comment\r\n# with a few\r\n# lines\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testVar( ) +{ + writer->putVariable( T_String( "thingmabob" ) ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "$thingmabob\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testInt( ) +{ + writer->putInteger( 123 ); + writer->putInteger( 12345678000 ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "123 12345678000\r\n" ); +} + +void SRDTextWriterTest::testInt32( ) +{ + writer->putInt32( 123 ); + writer->putInt32( -123 ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "123 -123\r\n" ); +} + +void SRDTextWriterTest::testInt64( ) +{ + writer->putInt64( 12345678000 ); + writer->putInt64( -12345678000 ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "12345678000 -12345678000\r\n" ); +} + +void SRDTextWriterTest::testFloat( ) +{ + writer->putFloat( 1.5 ); + writer->putFloat( M_PI ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "1.5 3.141592653589793115997963\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testBinaryEmpty( ) +{ + T_Buffer< uint8_t > buf( 0 ); + writer->putBinary( buf ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "[]\r\n" ); +} + +void SRDTextWriterTest::testBinaryShort( ) +{ + T_Buffer< uint8_t > buf( 8 ); + for ( auto i = 0u ; i < 8 ; i ++ ) { + buf[ i ] = i << 3; + } + writer->putBinary( buf ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == "[ 00 08 10 18 20 28 30 38 ]\r\n" ); +} + +void SRDTextWriterTest::testBinaryLong( ) +{ + T_Buffer< uint8_t > buf( 18 ); + for ( auto i = 0u ; i < 18 ; i ++ ) { + buf[ i ] = i; + } + writer->putBinary( buf ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == + "[\r\n" + " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n" + " 10 11\r\n" + "]\r\n" ); +} + +/*----------------------------------------------------------------------------*/ + +void SRDTextWriterTest::testIndentation( ) +{ + writer->startList( ) + .putText( T_String( "this" ) ) + .putText( T_String( "is" ) ) + .startList( ) + .putText( T_String( "just" ) ) + .putText( T_String( "a" ) ) + .endList( ) + .putComment( T_String( " very, very, very...\n BAD!" ) ) + .putText( T_String( "test" ) ) + .putComment( T_String( " and then some." ) ) + .putComment( T_String( " Twice, even." ) ) + .endList( ); + const T_String result( getResult( ) ); + CPPUNIT_ASSERT( result == + "(this is\r\n" + " (just a) # very, very, very...\r\n" + " # BAD!\r\n" + " test # and then some.\r\n" + " # Twice, even.\r\n" + " )\r\n" ); +} + diff --git a/tests/stream-file-input.cc b/tests/stream-file-input.cc new file mode 100644 index 0000000..70e2017 --- /dev/null +++ b/tests/stream-file-input.cc @@ -0,0 +1,268 @@ +#include +#include +using namespace lw; + +#if 0 + +/*= FileInputStreamTest =====================================================*/ + +class FileInputStreamTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( FileInputStreamTest ); + CPPUNIT_TEST( testInitRealFile ); + CPPUNIT_TEST( testInitBadFile ); + + CPPUNIT_TEST( testOpenRealFile ); + CPPUNIT_TEST( testOpenBadFile ); + CPPUNIT_TEST( testClose ); + + CPPUNIT_TEST( testReadOpensFile ); + CPPUNIT_TEST( testReadAll ); + CPPUNIT_TEST( testReadPartial ); + + CPPUNIT_TEST( testSetPosOpensFile ); + CPPUNIT_TEST( testSetPosStart ); + CPPUNIT_TEST( testSetPosEnd ); + CPPUNIT_TEST( testSetPosEndBad ); + + CPPUNIT_TEST( testMoveOpensFile ); + CPPUNIT_TEST( testMoveForward ); + CPPUNIT_TEST( testMoveBack ); + CPPUNIT_TEST( testMoveBad ); + CPPUNIT_TEST_SUITE_END( ); + + T_String OK_FILE( ) + { + return T_String::Pooled( "tests/data/inputstream.txt" ); + } + + T_String BAD_FILE( ) + { + return T_String::Pooled( "tests/data/no_such_file.txt" ); + } + + char const* CONTENTS( ) + { + return "THIS IS A TEST\n"; + } + +public: + void testInitRealFile( ); + void testInitBadFile( ); + + void testOpenRealFile( ); + void testOpenBadFile( ); + void testClose( ); + + void testReadOpensFile( ); + void testReadAll( ); + void testReadPartial( ); + + void testSetPosOpensFile( ); + void testSetPosStart( ); + void testSetPosEnd( ); + void testSetPosEndBad( ); + + void testMoveOpensFile( ); + void testMoveForward( ); + void testMoveBack( ); + void testMoveBad( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( FileInputStreamTest ); + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testInitRealFile( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + CPPUNIT_ASSERT( fis.path( ) == OK_FILE( ) ); + CPPUNIT_ASSERT( fis.canUseSize( ) ); + CPPUNIT_ASSERT( fis.canRead( ) ); + CPPUNIT_ASSERT( fis.size( ) == 0 ); + CPPUNIT_ASSERT( fis.position( ) == 0 ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testInitBadFile( ) +{ + T_FileInputStream fis( BAD_FILE( ) ); + CPPUNIT_ASSERT( fis.path( ) == BAD_FILE( ) ); + CPPUNIT_ASSERT( fis.canUseSize( ) ); + CPPUNIT_ASSERT( fis.canRead( ) ); + CPPUNIT_ASSERT( fis.size( ) == 0 ); + CPPUNIT_ASSERT( fis.position( ) == 0 ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testOpenRealFile( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + CPPUNIT_ASSERT( fis.size( ) == 15 ); + CPPUNIT_ASSERT( fis.position( ) == 0 ); + CPPUNIT_ASSERT( fis.isOpen( ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testOpenBadFile( ) +{ + T_FileInputStream fis( BAD_FILE( ) ); + try { + fis.open( ); + CPPUNIT_FAIL( "No exception thrown" ); + } catch ( X_StreamError const& e ) { + CPPUNIT_ASSERT( e.code( ) == E_StreamError::SYSTEM_ERROR ); + CPPUNIT_ASSERT( e.systemError( ) == ENOENT ); + } + CPPUNIT_ASSERT( fis.size( ) == 0 ); + CPPUNIT_ASSERT( fis.position( ) == 0 ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testClose( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + fis.close( ); + CPPUNIT_ASSERT( !fis.isOpen( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , fis.size( ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 0 ) , fis.position( ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testReadOpensFile( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + uint8_t x; + fis.read( &x , 0 ); + CPPUNIT_ASSERT( fis.isOpen( ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testReadAll( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , sizeof( buffer ) ) ); + CPPUNIT_ASSERT_EQUAL( fis.size( ) , rv ); + CPPUNIT_ASSERT_EQUAL( fis.size( ) , fis.position( ) ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) , rv ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testReadPartial( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , 4 ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , rv ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , fis.position( ) ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) , 4 ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testSetPosOpensFile( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.position( 4 , false ); + CPPUNIT_ASSERT( fis.isOpen( ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testSetPosStart( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + fis.position( 4 , false ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , fis.position( ) ); + + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , 3 ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 3 ) , rv ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) + 4 , 3 ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testSetPosEnd( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + fis.position( 4 , true ); + CPPUNIT_ASSERT_EQUAL( size_t( 11 ) , fis.position( ) ); + + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , 1024 ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , rv ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) + 11 , 4 ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testSetPosEndBad( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + CPPUNIT_ASSERT_THROW( fis.position( fis.size( ) + 1 , true ) , + X_StreamError ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testMoveOpensFile( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.move( 0 ); + CPPUNIT_ASSERT( fis.isOpen( ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testMoveForward( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + fis.move( 4 ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , fis.position( ) ); + + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , 3 ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 3 ) , rv ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) + 4 , 3 ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testMoveBack( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + fis.position( 8 ); + fis.move( -4 ); + CPPUNIT_ASSERT_EQUAL( size_t( 4 ) , fis.position( ) ); + + uint8_t buffer[ 1024 ]; + auto rv( fis.read( buffer , 3 ) ); + CPPUNIT_ASSERT_EQUAL( size_t( 3 ) , rv ); + CPPUNIT_ASSERT( !memcmp( buffer , CONTENTS( ) + 4 , 3 ) ); +} + +/*---------------------------------------------------------------------------*/ + +void FileInputStreamTest::testMoveBad( ) +{ + T_FileInputStream fis( OK_FILE( ) ); + fis.open( ); + CPPUNIT_ASSERT_THROW( fis.move( -1 ) , X_StreamError ); +} + +#endif diff --git a/tests/strings-builder.cc b/tests/strings-builder.cc new file mode 100644 index 0000000..2637e4f --- /dev/null +++ b/tests/strings-builder.cc @@ -0,0 +1,1101 @@ +#include +#include +#include +using namespace lw; + + +class StringsBuilderTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StringsBuilderTest ); + CPPUNIT_TEST( testConsDefault ); + CPPUNIT_TEST( testConsFromAsciiBuffer ); + CPPUNIT_TEST( testConsFromUTF8Buffer ); + CPPUNIT_TEST( testConsFromAsciiNTS ); + CPPUNIT_TEST( testConsFromUTF8NTS ); + CPPUNIT_TEST( testConsFromString ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testEnsureCapacityFactor ); + CPPUNIT_TEST( testEnsureCapacityOther ); + CPPUNIT_TEST( testEnsureCapacitySmaller ); + + CPPUNIT_TEST( testClear ); + CPPUNIT_TEST( testFree ); + + CPPUNIT_TEST( testAppendOther ); + CPPUNIT_TEST( testAppendSwap ); + CPPUNIT_TEST( testAppendStr ); + CPPUNIT_TEST( testAppendBuf ); + CPPUNIT_TEST( testAppendChar ); + CPPUNIT_TEST( testAppendUnicode ); + + CPPUNIT_TEST( testAppendNumSigned ); + CPPUNIT_TEST( testAppendNumUnsigned ); + CPPUNIT_TEST( testAppendNumFloat ); + + CPPUNIT_TEST( testStringConsCopy ); + CPPUNIT_TEST( testStringAssCopy ); + + CPPUNIT_TEST( testStringConsMove ); + CPPUNIT_TEST( testStringAssMove ); + + CPPUNIT_TEST( testCmpOther ); + CPPUNIT_TEST( testCmpCString ); + CPPUNIT_TEST( testCmpTString ); + + CPPUNIT_TEST( testToUnsignedBasic ); + CPPUNIT_TEST( testToUnsignedOkValue ); + CPPUNIT_TEST( testToUnsignedInvalid ); + CPPUNIT_TEST( testToUnsignedTooLarge ); + CPPUNIT_TEST( testToUnsignedBaseOk ); + CPPUNIT_TEST( testToUnsignedBaseInvalid ); + CPPUNIT_TEST( testToUnsignedBaseAutoBinary ); + CPPUNIT_TEST( testToUnsignedBaseAutoOctal ); + CPPUNIT_TEST( testToUnsignedBaseAutoDecimal ); + CPPUNIT_TEST( testToUnsignedBaseAutoHexa ); + CPPUNIT_TEST( testToUnsignedBaseAutoZero ); + CPPUNIT_TEST( testToUnsignedBaseAutoInvalid ); + CPPUNIT_TEST( testToUnsignedBaseAutoCut ); + CPPUNIT_TEST( testToUnsignedSeparator ); + CPPUNIT_TEST( testToUnsignedSeparatorCustom ); + CPPUNIT_TEST( testToUnsignedSeparatorCustomUTF8 ); + CPPUNIT_TEST( testToUnsignedLeadingWhitespace ); + CPPUNIT_TEST( testToUnsignedLeadingWhitespaceOnly ); + + CPPUNIT_TEST( testToSignedBasic ); + CPPUNIT_TEST( testToSignedOkValue ); + CPPUNIT_TEST( testToSignedPlus ); + CPPUNIT_TEST( testToSignedPlusMultiple ); + CPPUNIT_TEST( testToSignedMinus ); + CPPUNIT_TEST( testToSignedMinusMultiple ); + CPPUNIT_TEST( testToSignedInvalid ); + CPPUNIT_TEST( testToSignedTooLarge ); + CPPUNIT_TEST( testToSignedTooSmall ); + CPPUNIT_TEST( testToSignedBaseOk ); + CPPUNIT_TEST( testToSignedBaseInvalid ); + CPPUNIT_TEST( testToSignedBaseAutoBinary ); + CPPUNIT_TEST( testToSignedBaseAutoOctal ); + CPPUNIT_TEST( testToSignedBaseAutoDecimal ); + CPPUNIT_TEST( testToSignedBaseAutoHexa ); + CPPUNIT_TEST( testToSignedBaseAutoZero ); + CPPUNIT_TEST( testToSignedBaseAutoInvalid ); + CPPUNIT_TEST( testToSignedBaseAutoCut ); + CPPUNIT_TEST( testToSignedBaseAutoPlus ); + CPPUNIT_TEST( testToSignedBaseAutoMinus ); + CPPUNIT_TEST( testToSignedSeparator ); + CPPUNIT_TEST( testToSignedSeparatorCustom ); + CPPUNIT_TEST( testToSignedSeparatorCustomUTF8 ); + CPPUNIT_TEST( testToSignedLeadingWhitespace ); + CPPUNIT_TEST( testToSignedLeadingWhitespaceOnly ); + + CPPUNIT_TEST( testToDouble ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testConsDefault( ); + void testConsFromAsciiBuffer( ); + void testConsFromUTF8Buffer( ); + void testConsFromAsciiNTS( ); + void testConsFromUTF8NTS( ); + void testConsFromString( ); + + void testCopyCons( ); + void testCopyAss( ); + + void testMoveCons( ); + void testMoveAss( ); + + void testSwap( ); + + void testEnsureCapacityFactor( ); + void testEnsureCapacityOther( ); + void testEnsureCapacitySmaller( ); + + void testClear( ); + void testFree( ); + + void testAppendOther( ); + void testAppendSwap( ); + void testAppendStr( ); + void testAppendBuf( ); + void testAppendChar( ); + void testAppendUnicode( ); + + void testAppendNumSigned( ); + void testAppendNumUnsigned( ); + void testAppendNumFloat( ); + + void testStringConsCopy( ); + void testStringAssCopy( ); + + void testStringConsMove( ); + void testStringAssMove( ); + + void testCmpOther( ); + void testCmpCString( ); + void testCmpTString( ); + + void testToUnsignedBasic( ); + void testToUnsignedOkValue( ); + void testToUnsignedInvalid( ); + void testToUnsignedTooLarge( ); + void testToUnsignedBaseOk( ); + void testToUnsignedBaseInvalid( ); + void testToUnsignedBaseAutoBinary( ); + void testToUnsignedBaseAutoOctal( ); + void testToUnsignedBaseAutoDecimal( ); + void testToUnsignedBaseAutoHexa( ); + void testToUnsignedBaseAutoZero( ); + void testToUnsignedBaseAutoInvalid( ); + void testToUnsignedBaseAutoCut( ); + void testToUnsignedSeparator( ); + void testToUnsignedSeparatorCustom( ); + void testToUnsignedSeparatorCustomUTF8( ); + void testToUnsignedLeadingWhitespace( ); + void testToUnsignedLeadingWhitespaceOnly( ); + + void testToSignedBasic( ); + void testToSignedOkValue( ); + void testToSignedPlus( ); + void testToSignedPlusMultiple( ); + void testToSignedMinus( ); + void testToSignedMinusMultiple( ); + void testToSignedInvalid( ); + void testToSignedTooLarge( ); + void testToSignedTooSmall( ); + void testToSignedBaseOk( ); + void testToSignedBaseInvalid( ); + void testToSignedBaseAutoBinary( ); + void testToSignedBaseAutoOctal( ); + void testToSignedBaseAutoDecimal( ); + void testToSignedBaseAutoHexa( ); + void testToSignedBaseAutoZero( ); + void testToSignedBaseAutoInvalid( ); + void testToSignedBaseAutoCut( ); + void testToSignedBaseAutoPlus( ); + void testToSignedBaseAutoMinus( ); + void testToSignedSeparator( ); + void testToSignedSeparatorCustom( ); + void testToSignedSeparatorCustomUTF8( ); + void testToSignedLeadingWhitespace( ); + void testToSignedLeadingWhitespaceOnly( ); + + void testToDouble( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( StringsBuilderTest ); + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testConsDefault( ) +{ + T_StringBuilder sb; + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) == nullptr ); +} + +void StringsBuilderTest::testConsFromAsciiBuffer( ) +{ + T_StringBuilder sb( "test" , 4 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , "test" , 4 ) ); +} + +void StringsBuilderTest::testConsFromUTF8Buffer( ) +{ + const char test[] = "t\xe2\x82\xacst"; + const uint32_t sz = sizeof( test ) - 1; + T_StringBuilder sb( test , sz ); + CPPUNIT_ASSERT_EQUAL( uint32_t( sz ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , test , sz ) ); +} + +void StringsBuilderTest::testConsFromAsciiNTS( ) +{ + const char test[] = "test"; + const uint32_t sz = sizeof( test ) - 1; + T_StringBuilder sb( test ); + CPPUNIT_ASSERT_EQUAL( uint32_t( sz ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , test , sz ) ); +} + +void StringsBuilderTest::testConsFromUTF8NTS( ) +{ + const char test[] = "t\xe2\x82\xacst"; + const uint32_t sz = sizeof( test ) - 1; + T_StringBuilder sb( test ); + CPPUNIT_ASSERT_EQUAL( uint32_t( sz ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , test , sz ) ); +} + +void StringsBuilderTest::testConsFromString( ) +{ + const T_String test( "t\xe2\x82\xacst" ); + T_StringBuilder sb( test ); + CPPUNIT_ASSERT_EQUAL( test.size( ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( test.length( ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , test.data( ) , test.size( ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testCopyCons( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder isb( str ); + T_StringBuilder sb( isb ); + + CPPUNIT_ASSERT_EQUAL( sz , isb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , isb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , isb.capacity( ) ); + CPPUNIT_ASSERT( isb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( isb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT( sb.data( ) != isb.data( ) ); +} + +void StringsBuilderTest::testCopyAss( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder isb( str ); + T_StringBuilder sb; + + sb = isb; + + CPPUNIT_ASSERT_EQUAL( sz , isb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , isb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , isb.capacity( ) ); + CPPUNIT_ASSERT( isb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( isb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT( sb.data( ) != isb.data( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testMoveCons( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder isb( str ); + T_StringBuilder sb( std::move( isb ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.capacity( ) ); + CPPUNIT_ASSERT( isb.data( ) == nullptr ); + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); +} + +void StringsBuilderTest::testMoveAss( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder isb( str ); + T_StringBuilder sb( "huh" ); + + sb = std::move( isb ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , isb.capacity( ) ); + CPPUNIT_ASSERT( isb.data( ) == nullptr ); + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testSwap( ) +{ + const char test[] = "test"; + const uint32_t sz = sizeof( test ) - 1; + T_StringBuilder sb1( test , sz ) , sb2; + swap( sb1 , sb2 ); + + CPPUNIT_ASSERT_EQUAL( sz , sb2.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb1.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb2.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb1.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , + sb2.capacity( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb1.capacity( ) ); + CPPUNIT_ASSERT( sb1.data( ) == nullptr ); + CPPUNIT_ASSERT( sb2.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb2.data( ) , test , sz ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testEnsureCapacityFactor( ) +{ + T_StringBuilder sb; + sb.ensureCapacity( 2 * sb.C_GROWTH ); + CPPUNIT_ASSERT_EQUAL( 2 * sb.C_GROWTH , sb.capacity( ) ); +} + +void StringsBuilderTest::testEnsureCapacityOther( ) +{ + T_StringBuilder sb; + sb.ensureCapacity( 3 * sb.C_GROWTH / 2 ); + CPPUNIT_ASSERT_EQUAL( 2 * sb.C_GROWTH , sb.capacity( ) ); +} + +void StringsBuilderTest::testEnsureCapacitySmaller( ) +{ + T_StringBuilder sb; + sb.ensureCapacity( 4 * sb.C_GROWTH ); + sb.ensureCapacity( 2 * sb.C_GROWTH ); + CPPUNIT_ASSERT_EQUAL( 4 * sb.C_GROWTH , sb.capacity( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testClear( ) +{ + T_StringBuilder sb( "this is a test" ); + sb.clear( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( sb.C_GROWTH ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); +} + +void StringsBuilderTest::testFree( ) +{ + T_StringBuilder sb( "this is a test" ); + sb.free( ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) == nullptr ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testAppendOther( ) +{ + T_StringBuilder sb1( "this is " ) , sb2( "a test!" ); + T_StringBuilder sb3( sb1 ); + sb3.append( sb2 ); + + CPPUNIT_ASSERT_EQUAL( sb1.size( ) + sb2.size( ) , sb3.size( ) ); + CPPUNIT_ASSERT_EQUAL( sb1.length( ) + sb2.length( ) , sb3.length( ) ); + CPPUNIT_ASSERT( !memcmp( sb3.data( ) , sb1.data( ) , sb1.size( ) ) ); + CPPUNIT_ASSERT( !memcmp( sb3.data( ) + sb1.size( ) , sb2.data( ) , + sb2.size( ) ) ); +} + +void StringsBuilderTest::testAppendSwap( ) +{ + const char test[] = "test"; + const uint32_t sz = sizeof( test ) - 1; + T_StringBuilder sbi( test , sz ) , sbo; + sbo.append( std::move( sbi ) ); + + CPPUNIT_ASSERT_EQUAL( sz , sbo.size( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sbo.length( ) ); + CPPUNIT_ASSERT( sbo.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , test , sz ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sbi.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sbi.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sbi.capacity( ) ); + CPPUNIT_ASSERT( sbi.data( ) == nullptr ); +} + +void StringsBuilderTest::testAppendStr( ) +{ + T_String str( "test\u20ac" ); + T_StringBuilder sbo; + sbo.append( str ).append( str ); + + CPPUNIT_ASSERT_EQUAL( str.size( ) * 2 , sbo.size( ) ); + CPPUNIT_ASSERT_EQUAL( str.length( ) * 2 , sbo.length( ) ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , str.data( ) , str.size( ) ) ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) + str.size( ) , str.data( ) , + str.size( ) ) ); +} + +void StringsBuilderTest::testAppendBuf( ) +{ + T_String str( "test\u20ac" ); + T_StringBuilder sbo; + sbo.append( str.data( ) , str.size( ) ); + + CPPUNIT_ASSERT_EQUAL( str.size( ) , sbo.size( ) ); + CPPUNIT_ASSERT_EQUAL( str.length( ) , sbo.length( ) ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , str.data( ) , str.size( ) ) ); +} + +void StringsBuilderTest::testAppendChar( ) +{ + T_StringBuilder sbo; + sbo.append( 'a' ).append( 'b' ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , sbo.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 2 ) , sbo.length( ) ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , "ab" , 2 ) ); +} + +void StringsBuilderTest::testAppendUnicode( ) +{ + const char expected[] = "a\u00e9\u20ac\U00010102"; + const uint32_t sz = sizeof( expected ) - 1; + + T_StringBuilder sbo; + sbo.append( T_Character( 'a' ) ) + .append( T_Character( 0xe9u ) ) + .append( T_Character( 0x20acu ) ) + .append( T_Character( 0x10102u ) ); + + CPPUNIT_ASSERT_EQUAL( sz , sbo.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 4 ) , sbo.length( ) ); + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , expected , sz ) ); +} + +/*----------------------------------------------------------------------------*/ + +#define M_CHECK_( STR ) \ + { \ + const char t[] = STR; \ + const uint32_t sz = sizeof( t ) - 1; \ + if ( sz != sbo.size( ) || memcmp( sbo.data( ) , t , sz ) ) { \ + const auto x( T_String( sbo ).toOSString( ) ); \ + printf( "'%s' | expected '%s'\n" , x.data( ) , STR ); \ + } \ + CPPUNIT_ASSERT_EQUAL( sz , sbo.size( ) ); \ + CPPUNIT_ASSERT( !memcmp( sbo.data( ) , t , sz ) ); \ + } + +void StringsBuilderTest::testAppendNumSigned( ) +{ + T_StringBuilder sbo; + sbo.clear( ).appendNumeric( int64_t( 1 ) ); M_CHECK_( "1" ); + sbo.clear( ).appendNumeric( int64_t( 0 ) ); M_CHECK_( "0" ); + sbo.clear( ).appendNumeric( int64_t( -1 ) ); M_CHECK_( "-1" ); + sbo.clear( ).appendNumeric( int64_t( 1234 ) ); M_CHECK_( "1234" ); + sbo.clear( ).appendNumeric( int64_t( -1234 ) ); M_CHECK_( "-1234" ); + + sbo.clear( ).appendNumeric( int64_t( 129 ) , 2 ); + M_CHECK_( "10000001" ); + sbo.clear( ).appendNumeric( int64_t( -129 ) , 2 ); + M_CHECK_( "-10000001" ); + + sbo.clear( ).appendNumeric( int64_t( 129 ) , 16 ); M_CHECK_( "81" ); + sbo.clear( ).appendNumeric( int64_t( -129 ) , 16 ); M_CHECK_( "-81" ); + + sbo.clear( ).appendNumeric( int64_t( 1234 ) , 10 , true ); + M_CHECK_( "1 234" ); + sbo.clear( ).appendNumeric( int64_t( -1234 ) , 10 , true ); + M_CHECK_( "-1 234" ); + + sbo.clear( ).appendNumeric( int64_t( 1234 ) , 10 , true , ',' ); + M_CHECK_( "1,234" ); + sbo.clear( ).appendNumeric( int64_t( -1234 ) , 10 , true , ',' ); + M_CHECK_( "-1,234" ); + + sbo.clear( ).appendNumeric( int64_t( 1234 ) , 10 , true , + T_Character( 0x20acu ) ); + M_CHECK_( "1\u20ac234" ); + sbo.clear( ).appendNumeric( int64_t( -1234 ) , 10 , true , 0x20acu ); + M_CHECK_( "-1\u20ac234" ); + + sbo.clear( ).appendNumeric( int64_t( 65535 ) , 2 , true , ' ' , 8 ); + M_CHECK_( "11111111 11111111" ); + sbo.clear( ).appendNumeric( int64_t( -65535 ) , 2 , true , ' ' , 8 ); + M_CHECK_( "-11111111 11111111" ); + + sbo.clear( ).appendNumeric( int64_t( 15 ) , 2 , true , ' ' , 1 ); + M_CHECK_( "1 1 1 1" ); + +} + +void StringsBuilderTest::testAppendNumUnsigned( ) +{ + T_StringBuilder sbo; + sbo.clear( ).appendNumeric( uint64_t( 1 ) ); M_CHECK_( "1" ); + sbo.clear( ).appendNumeric( uint64_t( 0 ) ); M_CHECK_( "0" ); + sbo.clear( ).appendNumeric( uint64_t( 1234 ) ); M_CHECK_( "1234" ); + + sbo.clear( ).appendNumeric( uint64_t( 129 ) , 2 ); + M_CHECK_( "10000001" ); + + sbo.clear( ).appendNumeric( uint64_t( 129 ) , 16 ); M_CHECK_( "81" ); + + sbo.clear( ).appendNumeric( uint64_t( 1234 ) , 10 , true ); + M_CHECK_( "1 234" ); + + sbo.clear( ).appendNumeric( uint64_t( 1234 ) , 10 , true , ',' ); + M_CHECK_( "1,234" ); + + sbo.clear( ).appendNumeric( uint64_t( 1234 ) , 10 , true , + T_Character( 0x20acu ) ); + M_CHECK_( "1\u20ac234" ); + + sbo.clear( ).appendNumeric( uint64_t( 65535 ) , 2 , true , ' ' , 8 ); + M_CHECK_( "11111111 11111111" ); + + sbo.clear( ).appendNumeric( uint64_t( 15 ) , 2 , true , ' ' , 1 ); + M_CHECK_( "1 1 1 1" ); +} + +void StringsBuilderTest::testAppendNumFloat( ) +{ + T_StringBuilder sbo; + sbo.clear( ).appendDouble( -1 ); M_CHECK_( "-1" ); + sbo.clear( ).appendDouble( 0 ); M_CHECK_( "0" ); + sbo.clear( ).appendDouble( 1234 ); M_CHECK_( "1234" ); + sbo.clear( ).appendDouble( 1.234 ); M_CHECK_( "1.234" ); + sbo.clear( ).appendDouble( 1.23456789 ); M_CHECK_( "1.23457" ); + sbo.clear( ).appendDouble( 10.23456789 ); M_CHECK_( "10.2346" ); + sbo.clear( ).appendDouble( -1e10 ); M_CHECK_( "-1e+10" ); + + sbo.clear( ).appendDouble( 1 , 2 ); M_CHECK_( "1" ); + sbo.clear( ).appendDouble( 1.5 , 2 ); M_CHECK_( "1.5" ); + sbo.clear( ).appendDouble( 1.5432 , 2 ); M_CHECK_( "1.5" ); + sbo.clear( ).appendDouble( 1e20 , 2 ); M_CHECK_( "1e+20" ); + + sbo.clear( ).appendDouble( 1.234 , 6 , true ); M_CHECK_( "1.234000" ); + sbo.clear( ).appendDouble( 1.23456789 , 6 , true ); M_CHECK_( "1.234568" ); + sbo.clear( ).appendDouble( 1 , 6 , true ); M_CHECK_( "1.000000" ); + sbo.clear( ).appendDouble( -1 , 6 , true ); M_CHECK_( "-1.000000" ); + sbo.clear( ).appendDouble( 1e10 , 6 , true ); M_CHECK_( "10000000000.000000" ); +} +#undef M_CHECK_ + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testStringConsCopy( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder sb( str ); + T_String tstr( sb ); + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT( tstr == str ); + CPPUNIT_ASSERT( tstr.data( ) != sb.data( ) ); +} + +void StringsBuilderTest::testStringAssCopy( ) +{ + const char str[] = "this is a test"; + const uint32_t sz = sizeof( str ) - 1; + T_StringBuilder sb( str ); + T_String tstr; + + tstr = sb; + + CPPUNIT_ASSERT_EQUAL( sz , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( sz , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( T_StringBuilder::C_GROWTH ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( sb.data( ) , str , sz ) ); + + CPPUNIT_ASSERT( tstr == str ); + CPPUNIT_ASSERT( tstr.data( ) != sb.data( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testStringConsMove( ) +{ + const char str[] = "this is a test"; + T_StringBuilder sb( str ); + T_String tstr( std::move( sb ) ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) == nullptr ); + + CPPUNIT_ASSERT( tstr == str ); +} + +void StringsBuilderTest::testStringAssMove( ) +{ + const char str[] = "this is a test"; + T_StringBuilder sb( str ); + T_String tstr; + + tstr = std::move( sb ); + + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.length( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , sb.capacity( ) ); + CPPUNIT_ASSERT( sb.data( ) == nullptr ); + + CPPUNIT_ASSERT( tstr == str ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testCmpOther( ) +{ + T_StringBuilder sb1( "this" ) , sb2( "that" ) , sb3( "this" ); + CPPUNIT_ASSERT( sb1 == sb3 ); + CPPUNIT_ASSERT( sb1 != sb2 ); +} + +void StringsBuilderTest::testCmpCString( ) +{ + T_StringBuilder sb( "this" ); + CPPUNIT_ASSERT( sb == "this" ); + CPPUNIT_ASSERT( sb != "that" ); +} + +void StringsBuilderTest::testCmpTString( ) +{ + T_StringBuilder sb( "this" ); + T_String s1( "this" ) , s2( "that" ); + CPPUNIT_ASSERT( sb == s1 ); + CPPUNIT_ASSERT( sb != s2 ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testToUnsignedBasic( ) +{ + T_StringBuilder sb( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( ) ); +} + +void StringsBuilderTest::testToUnsignedOkValue( ) +{ + bool ok; + T_StringBuilder sb( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedInvalid( ) +{ + bool ok; + T_StringBuilder sb( "123lol" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToUnsignedTooLarge( ) +{ + bool ok; + T_StringBuilder sb( "18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( UINT64_MAX ) , sb.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToUnsignedBaseOk( ) +{ + bool ok; + T_StringBuilder sb( "1a23" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 6691 ) , sb.toUnsignedInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseInvalid( ) +{ + bool ok; + T_StringBuilder sb( "12g3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 18 ) , sb.toUnsignedInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoBinary( ) +{ + bool ok; + T_StringBuilder sb( "0b1111" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 15 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoOctal( ) +{ + bool ok; + T_StringBuilder sb( "0123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 83 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoDecimal( ) +{ + bool ok; + T_StringBuilder sb( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoHexa( ) +{ + bool ok; + T_StringBuilder sb( "0x123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 291 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoZero( ) +{ + bool ok; + T_StringBuilder sb( "0" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoInvalid( ) +{ + bool ok; + T_StringBuilder sb( "0z123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToUnsignedBaseAutoCut( ) +{ + bool ok; + T_StringBuilder sb( "0x" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , sb.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToUnsignedSeparator( ) +{ + bool ok; + T_StringBuilder sb( "1 2 3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok , 10 , true ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedSeparatorCustom( ) +{ + bool ok; + T_StringBuilder sb( "1.2.3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok , 10 , true , '.' ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedSeparatorCustomUTF8( ) +{ + bool ok; + T_StringBuilder sb( "1""\xe2\x82\xac""23" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok , 10 , true , T_Character( 0x20ac ) ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedLeadingWhitespace( ) +{ + bool ok; + T_StringBuilder sb( " 123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , sb.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToUnsignedLeadingWhitespaceOnly( ) +{ + bool ok; + T_StringBuilder sb( " " ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , sb.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsBuilderTest::testToSignedBasic( ) +{ + T_StringBuilder sb( "123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , sb.toInteger( ) ); +} + +void StringsBuilderTest::testToSignedOkValue( ) +{ + bool ok; + T_StringBuilder sb( "123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedPlus( ) +{ + bool ok; + T_StringBuilder sb( "+123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedPlusMultiple( ) +{ + bool ok; + T_StringBuilder sb( "++123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedMinus( ) +{ + bool ok; + T_StringBuilder sb( "-123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -123 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedMinusMultiple( ) +{ + bool ok; + T_StringBuilder sb( "--123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedInvalid( ) +{ + bool ok; + T_StringBuilder sb( "1lol23wut" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 1 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedTooLarge( ) +{ + bool ok; + T_StringBuilder sb( "18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( INT64_MAX , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedTooSmall( ) +{ + bool ok; + T_StringBuilder sb( "-18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( INT64_MIN , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedBaseOk( ) +{ + bool ok; + T_StringBuilder sb( "1a2" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 418 ) , sb.toInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseInvalid( ) +{ + bool ok; + T_StringBuilder sb( "g12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoBinary( ) +{ + bool ok; + T_StringBuilder sb( "0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 3 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoOctal( ) +{ + bool ok; + T_StringBuilder sb( "011" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 9 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoDecimal( ) +{ + bool ok; + T_StringBuilder sb( "11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 11 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoHexa( ) +{ + bool ok; + T_StringBuilder sb( "0x12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 18 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoZero( ) +{ + bool ok; + T_StringBuilder sb( "0" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoInvalid( ) +{ + bool ok; + T_StringBuilder sb( "0z12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoCut( ) +{ + bool ok; + T_StringBuilder sb( "0x" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoPlus( ) +{ + bool ok; + T_StringBuilder sb( "+0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 3 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedBaseAutoMinus( ) +{ + bool ok; + T_StringBuilder sb( "-0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -3 ) , sb.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedSeparator( ) +{ + bool ok; + T_StringBuilder sb( "-1 1" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -11 ) , sb.toInteger( &ok , 10 , true ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedSeparatorCustom( ) +{ + bool ok; + T_StringBuilder sb( "-1x1" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -11 ) , sb.toInteger( &ok , 10 , true , 'x' ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedSeparatorCustomUTF8( ) +{ + bool ok; + T_StringBuilder sb( "1""\xe2\x82\xac""23" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , sb.toInteger( &ok , 10 , true , T_Character( 0x20ac ) ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedLeadingWhitespace( ) +{ + bool ok; + T_StringBuilder sb( " - 123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -123 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsBuilderTest::testToSignedLeadingWhitespaceOnly( ) +{ + bool ok; + T_StringBuilder sb( " " ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , sb.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +/*----------------------------------------------------------------------------*/ + +#define M_CHECK_( STR , VAL , OK ) \ + do { \ + bool ok; \ + double v( T_StringBuilder( STR ).toDouble( &ok , '.' , true , ',' ) ); \ + CPPUNIT_ASSERT_EQUAL( double( VAL ) , v ); \ + CPPUNIT_ASSERT_EQUAL( bool( OK ) , ok ); \ + } while ( 0 ) + +void StringsBuilderTest::testToDouble( ) +{ + M_CHECK_( " " , 0 , false ); + M_CHECK_( " 0" , 0 , true ); + M_CHECK_( " ,,0" , 0 , false ); + M_CHECK_( "+" , 0 , false ); + M_CHECK_( "-" , 0 , false ); + M_CHECK_( "123" , 123 , true ); + M_CHECK_( ",123" , 0 , false ); + M_CHECK_( "1,2,3" , 123 , true ); + M_CHECK_( "1,2," , 0 , false ); + M_CHECK_( ",-123" , 0 , false ); + M_CHECK_( "-123" , -123 , true ); + M_CHECK_( "+123" , +123 , true ); + M_CHECK_( "+,123" , 0 , false ); + M_CHECK_( ".12" , .12 , true ); + M_CHECK_( "-.12" , -.12 , true ); + M_CHECK_( "+.12" , .12 , true ); + M_CHECK_( "." , 0 , true ); + M_CHECK_( "12." , 12 , true ); + M_CHECK_( "12e" , 0 , false ); + M_CHECK_( "12e2" , 12e2 , true ); + M_CHECK_( "12e+" , 0 , false ); + M_CHECK_( "12e-" , 0 , false ); + M_CHECK_( "12e+2" , 12e2 , true ); + M_CHECK_( "12e-2" , 12e-2 , true ); + M_CHECK_( "1.2e2" , 120 , true ); + M_CHECK_( "1.2e+2" , 120 , true ); + M_CHECK_( "1.2e-2" , 1.2e-2 , true ); + M_CHECK_( "1e3000" , std::numeric_limits< double >::infinity( ) , false ); + M_CHECK_( "-1e3000" , -std::numeric_limits< double >::infinity( ) , false ); + M_CHECK_( "1e-3000" , 0 , false ); +} + +#undef M_CHECK_ diff --git a/tests/strings-iterator.cc b/tests/strings-iterator.cc new file mode 100644 index 0000000..35ac1a5 --- /dev/null +++ b/tests/strings-iterator.cc @@ -0,0 +1,156 @@ +#include +#include +using namespace lw; + + +class StringsIteratorTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StringsIteratorTest ); + CPPUNIT_TEST( testGet ); + CPPUNIT_TEST( testMove ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testCharacters ); + CPPUNIT_TEST( testIndex ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testGet( ); + void testMove( ); + void testEmpty( ); + void testCharacters( ); + void testIndex( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( StringsIteratorTest ); + +/*----------------------------------------------------------------------------*/ + +void StringsIteratorTest::testGet( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + CPPUNIT_ASSERT( !it.atEnd( ) ); +} + +void StringsIteratorTest::testMove( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + int counter( 0 ); + while ( !it.atEnd( ) ) { + counter ++; + it.next( ); + } + CPPUNIT_ASSERT_EQUAL( 5 , counter ); +} + +void StringsIteratorTest::testEmpty( ) +{ + T_String str; + T_StringIterator it( str ); + CPPUNIT_ASSERT( it.atEnd( ) ); +} + +void StringsIteratorTest::testCharacters( ) +{ + char const* const s( "wa\xe2\x82\xacrgl" ); + T_String str( s ); + T_StringIterator it( str ); + int counter( 0 ); + while ( !it.atEnd( ) ) { + T_Character c( it ); + if ( counter < 2 ) { + CPPUNIT_ASSERT( c == s[ counter ] ); + } else if ( counter == 2 ) { + CPPUNIT_ASSERT( c == 0x20ac ); + } else { + CPPUNIT_ASSERT( c == s[ counter + 2 ] ); + } + counter ++; + it.next( ); + } +} + +void StringsIteratorTest::testIndex( ) +{ + char const* const s( "wa\xe2\x82\xacrgl" ); + T_String str( s ); + T_StringIterator it( str ); + uint32_t counter( 0 ); + while ( !it.atEnd( ) ) { + CPPUNIT_ASSERT_EQUAL( counter , it.index( ) ); + counter ++; + it.next( ); + } +} + +/*----------------------------------------------------------------------------*/ + +void StringsIteratorTest::testCopyCons( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + it.next( ); + T_StringIterator copy( it ); + copy.next( ); + CPPUNIT_ASSERT( T_Character( it ) == 'a' ); + CPPUNIT_ASSERT( T_Character( copy ) == 'r' ); +} + +void StringsIteratorTest::testCopyAss( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + T_StringIterator copy( str ); + it.next( ); + copy = it; + copy.next( ); + CPPUNIT_ASSERT( T_Character( it ) == 'a' ); + CPPUNIT_ASSERT( T_Character( copy ) == 'r' ); +} + +void StringsIteratorTest::testMoveCons( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + it.next( ); + T_StringIterator copy( std::move( it ) ); + copy.next( ); + CPPUNIT_ASSERT( it.atEnd( ) ); + CPPUNIT_ASSERT( T_Character( copy ) == 'r' ); +} + +void StringsIteratorTest::testMoveAss( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + T_StringIterator copy( str ); + it.next( ); + copy = std::move( it ); + copy.next( ); + CPPUNIT_ASSERT( T_Character( it ) == 'w' ); + CPPUNIT_ASSERT( T_Character( copy ) == 'r' ); +} + +void StringsIteratorTest::testSwap( ) +{ + T_String str( "wargl" ); + T_StringIterator it( str ); + T_StringIterator copy( str ); + it.next( ); + swap( it , copy ); + CPPUNIT_ASSERT( T_Character( it ) == 'w' ); + CPPUNIT_ASSERT( T_Character( copy ) == 'a' ); +} + diff --git a/tests/strings-storage.cc b/tests/strings-storage.cc new file mode 100644 index 0000000..583b1b2 --- /dev/null +++ b/tests/strings-storage.cc @@ -0,0 +1,328 @@ +#include +#include +using namespace lw; + + +/*= StringsDynamicTest ========================================================*/ + +class StringsDynamicTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StringsDynamicTest ); + CPPUNIT_TEST( testNoPool ); + CPPUNIT_TEST( testPoolReg ); + CPPUNIT_TEST( testPoolPersist ); + CPPUNIT_TEST( testTStringPooled ); + CPPUNIT_TEST( testDynamicCons ); + CPPUNIT_TEST( testUsePool ); + CPPUNIT_TEST_SUITE_END( ); + +private: + static const char TEST_STRING_1[]; + static const char TEST_STRING_2[]; + static const char TEST_STRING_3[]; + static const char TEST_STRING_4[]; + static const char TEST_STRING_5[]; + static const char TEST_STRING_6[]; + +public: + void testNoPool( ); + void testPoolReg( ); + void testPoolPersist( ); + void testTStringPooled( ); + void testDynamicCons( ); + void testUsePool( ); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( StringsDynamicTest ); + +/*----------------------------------------------------------------------------*/ + +const char StringsDynamicTest::TEST_STRING_1[] = "Dynamic strings test 1"; +const char StringsDynamicTest::TEST_STRING_2[] = "Dynamic strings test 2"; +const char StringsDynamicTest::TEST_STRING_3[] = "Dynamic strings test 3"; +const char StringsDynamicTest::TEST_STRING_4[] = "Dynamic strings test 4"; +const char StringsDynamicTest::TEST_STRING_5[] = "Dynamic strings test 5"; +const char StringsDynamicTest::TEST_STRING_6[] = "Dynamic strings test 6"; + +/*----------------------------------------------------------------------------*/ + +void StringsDynamicTest::testNoPool( ) +{ + T_String test1( TEST_STRING_1 ) , test2( TEST_STRING_1 ); + CPPUNIT_ASSERT( test1.data( ) != test2.data( ) ); +} + +void StringsDynamicTest::testPoolReg( ) +{ + const T_String pooled( std::move( + T_String( TEST_STRING_2 ).addToPool( ) ) ); + const T_String test1( TEST_STRING_2 ) , test2( TEST_STRING_2 ); + CPPUNIT_ASSERT( test1.data( ) == pooled.data( ) ); + CPPUNIT_ASSERT( test2.data( ) == pooled.data( ) ); +} + +void StringsDynamicTest::testPoolPersist( ) +{ + { + T_String( TEST_STRING_3 ).addToPool( ); + } + const T_String test1( TEST_STRING_3 ) , test2( TEST_STRING_3 ); + CPPUNIT_ASSERT( test1.data( ) == test2.data( ) ); +} + +void StringsDynamicTest::testTStringPooled( ) +{ + const T_String pooled( T_String::Pooled( TEST_STRING_4 ) ); + const T_String test( TEST_STRING_4 ); + CPPUNIT_ASSERT( test.data( ) == pooled.data( ) ); +} + +void StringsDynamicTest::testDynamicCons( ) +{ + const T_String pooled( T_String::Pooled( TEST_STRING_5 ) ); + const T_String test( TEST_STRING_5 , strlen( TEST_STRING_5 ) ); + CPPUNIT_ASSERT( test.data( ) != pooled.data( ) ); +} + +void StringsDynamicTest::testUsePool( ) +{ + T_String test( TEST_STRING_6 ); + const T_String pooled( T_String::Pooled( TEST_STRING_6 ) ); + CPPUNIT_ASSERT( test.data( ) != pooled.data( ) ); + test.usePool( ); + CPPUNIT_ASSERT( test.data( ) == pooled.data( ) ); +} + + +/*= StringsStaticTest ========================================================*/ + +class StringsStaticTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StringsStaticTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testUnpooled ); + CPPUNIT_TEST( testPooled ); + CPPUNIT_TEST_SUITE_END( ); + +private: + static const char TEST_STRING_1[]; + static const char TEST_STRING_2[]; + +public: + void testEmpty( ); + void testUnpooled( ); + void testPooled( ); +}; + +const char StringsStaticTest::TEST_STRING_1[] = "Static strings test 1"; +const char StringsStaticTest::TEST_STRING_2[] = "Static strings test 2"; + +CPPUNIT_TEST_SUITE_REGISTRATION( StringsStaticTest ); + +/*----------------------------------------------------------------------------*/ + +void StringsStaticTest::testEmpty( ) +{ + T_String test1 , test2( "" ) , test3( T_String::Pooled( "" ) ); + CPPUNIT_ASSERT( test1.data( ) == nullptr ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test1.length( ) ); + + CPPUNIT_ASSERT( test2.data( ) == nullptr ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test2.length( ) ); + + CPPUNIT_ASSERT( test3.data( ) == nullptr ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , test3.length( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsStaticTest::testUnpooled( ) +{ + T_String test1( TEST_STRING_1 ) , test2( TEST_STRING_1 ); + CPPUNIT_ASSERT( test1.data( ) != test2.data( ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsStaticTest::testPooled( ) +{ + char const* ptr; + { + const T_String reg( T_String::Pooled( TEST_STRING_2 ) ); + const T_String test1( TEST_STRING_2 ) , test2( TEST_STRING_2 ); + CPPUNIT_ASSERT( test1.data( ) == reg.data( ) ); + CPPUNIT_ASSERT( test2.data( ) == reg.data( ) ); + + ptr = reg.data( ); + } + { + const T_String test( TEST_STRING_2 ); + CPPUNIT_ASSERT( test.data( ) == ptr ); + } + { + const T_String test( T_String::Pooled( TEST_STRING_2 ) ); + CPPUNIT_ASSERT( test.data( ) == ptr ); + } + { + const T_String test( TEST_STRING_2 , strlen( TEST_STRING_2 ) ); + CPPUNIT_ASSERT( test.data( ) != ptr ); + } + { + T_String test( TEST_STRING_2 , strlen( TEST_STRING_2 ) ); + test.usePool( ); + CPPUNIT_ASSERT( test.data( ) == ptr ); + } + { + T_String test( TEST_STRING_2 , strlen( TEST_STRING_2 ) ); + test.addToPool( ); + CPPUNIT_ASSERT( test.data( ) == ptr ); + } +} + + +/*= SubstringsTest ===========================================================*/ + +class SubstringsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( SubstringsTest ); + CPPUNIT_TEST( testLeft ); + CPPUNIT_TEST( testRight ); + CPPUNIT_TEST( testSubstr ); + CPPUNIT_TEST( testRange ); + CPPUNIT_TEST( testPooling ); + CPPUNIT_TEST_SUITE_END( ); + +private: + static const char TEST_STRING[]; + +public: + void testLeft( ); + void testRight( ); + void testSubstr( ); + void testRange( ); + void testPooling( ); +}; + +const char SubstringsTest::TEST_STRING[] = "abcdefghijkl"; + +CPPUNIT_TEST_SUITE_REGISTRATION( SubstringsTest ); + +/*----------------------------------------------------------------------------*/ + +void SubstringsTest::testLeft( ) +{ + const T_String s( TEST_STRING ); + { + const T_String sub( s.left( 4 ) ); + CPPUNIT_ASSERT( sub == "abcd" ); + } + { + const T_String sub( s.left( 0 ) ); + CPPUNIT_ASSERT( sub == "" ); + } + { + const T_String sub( s.left( s.length( ) * 2 ) ); + CPPUNIT_ASSERT( sub == s ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SubstringsTest::testRight( ) +{ + const T_String s( TEST_STRING ); + { + const T_String sub( s.right( 4 ) ); + CPPUNIT_ASSERT( sub == "ijkl" ); + } + { + const T_String sub( s.right( 0 ) ); + CPPUNIT_ASSERT( sub == "" ); + } + { + const T_String sub( s.right( s.length( ) * 2 ) ); + CPPUNIT_ASSERT( sub == s ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SubstringsTest::testSubstr( ) +{ + const T_String s( TEST_STRING ); + { + const T_String sub( s.substr( 2 ) ); + CPPUNIT_ASSERT( sub == "cdefghijkl" ); + } + { + const T_String sub( s.substr( 2 , 2 ) ); + CPPUNIT_ASSERT( sub == "cd" ); + } + { + const T_String sub( s.substr( 0 ) ); + CPPUNIT_ASSERT( sub == s ); + } + { + const T_String sub( s.substr( 0 , 2 ) ); + CPPUNIT_ASSERT( sub == "ab" ); + } + { + const T_String sub( s.substr( s.length( ) ) ); + CPPUNIT_ASSERT( sub == "" ); + } + { + const T_String sub( s.substr( 0 , 0 ) ); + CPPUNIT_ASSERT( sub == "" ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SubstringsTest::testRange( ) +{ + const T_String s( TEST_STRING ); + { + const T_String sub( s.range( 2 , UINT32_MAX ) ); + CPPUNIT_ASSERT( sub == "cdefghijkl" ); + } + { + const T_String sub( s.range( 2 , 3 ) ); + CPPUNIT_ASSERT( sub == "cd" ); + } + { + const T_String sub( s.range( 0 , UINT32_MAX ) ); + CPPUNIT_ASSERT( sub == s ); + } + { + const T_String sub( s.range( 0 , 1 ) ); + CPPUNIT_ASSERT( sub == "ab" ); + } + { + const T_String sub( s.range( s.length( ) , UINT32_MAX ) ); + CPPUNIT_ASSERT( sub == "" ); + } + { + const T_String sub( s.range( 0 , 0 ) ); + CPPUNIT_ASSERT( sub == "a" ); + } + { + const T_String sub( s.range( 1 , 0 ) ); + CPPUNIT_ASSERT( sub == "" ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SubstringsTest::testPooling( ) +{ + const T_String s( TEST_STRING ); + const T_String pooled( T_String::Pooled( "abcd" ) ); + + T_String sub( s.left( 4 ) ); + CPPUNIT_ASSERT( sub == pooled ); + CPPUNIT_ASSERT( sub.data( ) != pooled.data( ) ); + + sub.addToPool( ); + CPPUNIT_ASSERT( sub == pooled ); + CPPUNIT_ASSERT( sub.data( ) == pooled.data( ) ); +} diff --git a/tests/strings.cc b/tests/strings.cc new file mode 100644 index 0000000..81b354b --- /dev/null +++ b/tests/strings.cc @@ -0,0 +1,1184 @@ +#include +#include +using namespace lw; + + +class StringsTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( StringsTest ); + CPPUNIT_TEST( testEmpty ); + CPPUNIT_TEST( testValidASCII ); + CPPUNIT_TEST( testValidUTF8 ); + CPPUNIT_TEST( testInvalid ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testCharAccess ); + + CPPUNIT_TEST( testEquals ); + CPPUNIT_TEST( testEqualsEmpty ); + CPPUNIT_TEST( testEqualsCString ); + + CPPUNIT_TEST( testCompareEmpty ); + CPPUNIT_TEST( testCompareSame ); + CPPUNIT_TEST( testCompareShorter ); + CPPUNIT_TEST( testCompareLonger ); + CPPUNIT_TEST( testCompareInf ); + CPPUNIT_TEST( testCompareSup ); + CPPUNIT_TEST( testCompareInvalid ); + + CPPUNIT_TEST( testCompareICEmpty ); + CPPUNIT_TEST( testCompareICSame ); + CPPUNIT_TEST( testCompareICShorter ); + CPPUNIT_TEST( testCompareICLonger ); + CPPUNIT_TEST( testCompareICInf ); + CPPUNIT_TEST( testCompareICSup ); + CPPUNIT_TEST( testCompareICInvalid ); + + CPPUNIT_TEST( testStartsWithEmpty ); + CPPUNIT_TEST( testStartsWithLonger ); + CPPUNIT_TEST( testStartsWithYes ); + CPPUNIT_TEST( testStartsWithNope ); + CPPUNIT_TEST( testStartsWithInvalid ); + + CPPUNIT_TEST( testEndsWithEmpty ); + CPPUNIT_TEST( testEndsWithLonger ); + CPPUNIT_TEST( testEndsWithYes ); + CPPUNIT_TEST( testEndsWithNope ); + CPPUNIT_TEST( testEndsWithInvalid ); + + CPPUNIT_TEST( testCmpOpEq ); + CPPUNIT_TEST( testCmpOpNe ); + CPPUNIT_TEST( testCmpOpLt ); + CPPUNIT_TEST( testCmpOpLe ); + CPPUNIT_TEST( testCmpOpGt ); + CPPUNIT_TEST( testCmpOpGe ); + CPPUNIT_TEST( testCmpOpEqCString ); + CPPUNIT_TEST( testCmpOpNeCString ); + + CPPUNIT_TEST( testFindEmpty ); + CPPUNIT_TEST( testFindLonger ); + CPPUNIT_TEST( testFindYes ); + CPPUNIT_TEST( testFindNope ); + CPPUNIT_TEST( testFindInvalidStrings ); + CPPUNIT_TEST( testFindOffsetEmpty ); + CPPUNIT_TEST( testFindOffsetLonger ); + CPPUNIT_TEST( testFindOffsetBad ); + CPPUNIT_TEST( testFindOffsetYes ); + CPPUNIT_TEST( testFindOffsetNo ); + + CPPUNIT_TEST( testFindCharEmpty ); + CPPUNIT_TEST( testFindCharYes ); + CPPUNIT_TEST( testFindCharNo ); + CPPUNIT_TEST( testFindCharInvalidString ); + CPPUNIT_TEST( testFindCharInvalidChar ); + CPPUNIT_TEST( testFindCharOffsetBad ); + CPPUNIT_TEST( testFindCharOffsetYes ); + CPPUNIT_TEST( testFindCharOffsetNo ); + + CPPUNIT_TEST( testToUnsignedBasic ); + CPPUNIT_TEST( testToUnsignedOkValue ); + CPPUNIT_TEST( testToUnsignedInvalid ); + CPPUNIT_TEST( testToUnsignedTooLarge ); + CPPUNIT_TEST( testToUnsignedBaseOk ); + CPPUNIT_TEST( testToUnsignedBaseInvalid ); + CPPUNIT_TEST( testToUnsignedBaseAutoBinary ); + CPPUNIT_TEST( testToUnsignedBaseAutoOctal ); + CPPUNIT_TEST( testToUnsignedBaseAutoDecimal ); + CPPUNIT_TEST( testToUnsignedBaseAutoHexa ); + CPPUNIT_TEST( testToUnsignedBaseAutoZero ); + CPPUNIT_TEST( testToUnsignedBaseAutoInvalid ); + CPPUNIT_TEST( testToUnsignedBaseAutoCut ); + CPPUNIT_TEST( testToUnsignedSeparator ); + CPPUNIT_TEST( testToUnsignedSeparatorCustom ); + CPPUNIT_TEST( testToUnsignedSeparatorCustomUTF8 ); + CPPUNIT_TEST( testToUnsignedLeadingWhitespace ); + CPPUNIT_TEST( testToUnsignedLeadingWhitespaceOnly ); + + CPPUNIT_TEST( testToSignedBasic ); + CPPUNIT_TEST( testToSignedOkValue ); + CPPUNIT_TEST( testToSignedPlus ); + CPPUNIT_TEST( testToSignedPlusMultiple ); + CPPUNIT_TEST( testToSignedMinus ); + CPPUNIT_TEST( testToSignedMinusMultiple ); + CPPUNIT_TEST( testToSignedInvalid ); + CPPUNIT_TEST( testToSignedTooLarge ); + CPPUNIT_TEST( testToSignedTooSmall ); + CPPUNIT_TEST( testToSignedBaseOk ); + CPPUNIT_TEST( testToSignedBaseInvalid ); + CPPUNIT_TEST( testToSignedBaseAutoBinary ); + CPPUNIT_TEST( testToSignedBaseAutoOctal ); + CPPUNIT_TEST( testToSignedBaseAutoDecimal ); + CPPUNIT_TEST( testToSignedBaseAutoHexa ); + CPPUNIT_TEST( testToSignedBaseAutoZero ); + CPPUNIT_TEST( testToSignedBaseAutoInvalid ); + CPPUNIT_TEST( testToSignedBaseAutoCut ); + CPPUNIT_TEST( testToSignedBaseAutoPlus ); + CPPUNIT_TEST( testToSignedBaseAutoMinus ); + CPPUNIT_TEST( testToSignedSeparator ); + CPPUNIT_TEST( testToSignedSeparatorCustom ); + CPPUNIT_TEST( testToSignedSeparatorCustomUTF8 ); + CPPUNIT_TEST( testToSignedLeadingWhitespace ); + CPPUNIT_TEST( testToSignedLeadingWhitespaceOnly ); + + CPPUNIT_TEST( testToDouble ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testEmpty( ); + void testValidASCII( ); + void testValidUTF8( ); + void testInvalid( ); + + void testCopyCons( ); + void testCopyAss( ); + void testMoveCons( ); + void testMoveAss( ); + void testSwap( ); + + void testCharAccess( ); + + void testEquals( ); + void testEqualsEmpty( ); + void testEqualsCString( ); + + void testCompareEmpty( ); + void testCompareSame( ); + void testCompareShorter( ); + void testCompareLonger( ); + void testCompareInf( ); + void testCompareSup( ); + void testCompareInvalid( ); + + void testCompareICEmpty( ); + void testCompareICSame( ); + void testCompareICShorter( ); + void testCompareICLonger( ); + void testCompareICInf( ); + void testCompareICSup( ); + void testCompareICInvalid( ); + + void testStartsWithEmpty( ); + void testStartsWithLonger( ); + void testStartsWithYes( ); + void testStartsWithNope( ); + void testStartsWithInvalid( ); + + void testEndsWithEmpty( ); + void testEndsWithLonger( ); + void testEndsWithYes( ); + void testEndsWithNope( ); + void testEndsWithInvalid( ); + + void testCmpOpEq( ); + void testCmpOpNe( ); + void testCmpOpLt( ); + void testCmpOpLe( ); + void testCmpOpGt( ); + void testCmpOpGe( ); + void testCmpOpEqCString( ); + void testCmpOpNeCString( ); + + void testFindEmpty( ); + void testFindLonger( ); + void testFindYes( ); + void testFindNope( ); + void testFindInvalidStrings( ); + void testFindOffsetEmpty( ); + void testFindOffsetLonger( ); + void testFindOffsetBad( ); + void testFindOffsetYes( ); + void testFindOffsetNo( ); + + void testFindCharEmpty( ); + void testFindCharYes( ); + void testFindCharNo( ); + void testFindCharInvalidString( ); + void testFindCharInvalidChar( ); + void testFindCharOffsetBad( ); + void testFindCharOffsetYes( ); + void testFindCharOffsetNo( ); + + void testToUnsignedBasic( ); + void testToUnsignedOkValue( ); + void testToUnsignedInvalid( ); + void testToUnsignedTooLarge( ); + void testToUnsignedBaseOk( ); + void testToUnsignedBaseInvalid( ); + void testToUnsignedBaseAutoBinary( ); + void testToUnsignedBaseAutoOctal( ); + void testToUnsignedBaseAutoDecimal( ); + void testToUnsignedBaseAutoHexa( ); + void testToUnsignedBaseAutoZero( ); + void testToUnsignedBaseAutoInvalid( ); + void testToUnsignedBaseAutoCut( ); + void testToUnsignedSeparator( ); + void testToUnsignedSeparatorCustom( ); + void testToUnsignedSeparatorCustomUTF8( ); + void testToUnsignedLeadingWhitespace( ); + void testToUnsignedLeadingWhitespaceOnly( ); + + void testToSignedBasic( ); + void testToSignedOkValue( ); + void testToSignedPlus( ); + void testToSignedPlusMultiple( ); + void testToSignedMinus( ); + void testToSignedMinusMultiple( ); + void testToSignedInvalid( ); + void testToSignedTooLarge( ); + void testToSignedTooSmall( ); + void testToSignedBaseOk( ); + void testToSignedBaseInvalid( ); + void testToSignedBaseAutoBinary( ); + void testToSignedBaseAutoOctal( ); + void testToSignedBaseAutoDecimal( ); + void testToSignedBaseAutoHexa( ); + void testToSignedBaseAutoZero( ); + void testToSignedBaseAutoInvalid( ); + void testToSignedBaseAutoCut( ); + void testToSignedBaseAutoPlus( ); + void testToSignedBaseAutoMinus( ); + void testToSignedSeparator( ); + void testToSignedSeparatorCustom( ); + void testToSignedSeparatorCustomUTF8( ); + void testToSignedLeadingWhitespace( ); + void testToSignedLeadingWhitespaceOnly( ); + + void testToDouble( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( StringsTest ); + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testEmpty( ) +{ + T_String empty; + CPPUNIT_ASSERT( empty.valid( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , empty.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 0 ) , empty.length( ) ); + CPPUNIT_ASSERT( empty.data( ) == nullptr ); + CPPUNIT_ASSERT( !bool( empty ) ); + CPPUNIT_ASSERT( !empty ); +} + +void StringsTest::testValidASCII( ) +{ + char const* const s = "le saut ASCII"; + const uint32_t sl( strlen( s ) ); + T_String ascii( s ); + CPPUNIT_ASSERT( ascii.valid( ) ); + CPPUNIT_ASSERT_EQUAL( sl , ascii.size( ) ); + CPPUNIT_ASSERT_EQUAL( sl , ascii.length( ) ); + CPPUNIT_ASSERT( ascii.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( ascii.data( ) , s , sl ) ); + CPPUNIT_ASSERT( bool( ascii ) ); + CPPUNIT_ASSERT( !!ascii ); +} + +void StringsTest::testValidUTF8( ) +{ + char const* const s = "euro sign: \xe2\x82\xac"; + const uint32_t ss( strlen( s ) ); + const uint32_t sl( ss - 2 ); + T_String utf8( s ); + CPPUNIT_ASSERT( utf8.valid( ) ); + CPPUNIT_ASSERT_EQUAL( ss , utf8.size( ) ); + CPPUNIT_ASSERT_EQUAL( sl , utf8.length( ) ); + CPPUNIT_ASSERT( utf8.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( utf8.data( ) , s , ss ) ); + CPPUNIT_ASSERT( bool( utf8 ) ); + CPPUNIT_ASSERT( !!utf8 ); +} + +void StringsTest::testInvalid( ) +{ + char const* const s = "much \x80 fail"; + const uint32_t ss( strlen( s ) ); + T_String bad( s ); + CPPUNIT_ASSERT( !bad.valid( ) ); + CPPUNIT_ASSERT_EQUAL( ss , bad.size( ) ); + CPPUNIT_ASSERT_EQUAL( ss , bad.length( ) ); + CPPUNIT_ASSERT( bad.data( ) != nullptr ); + CPPUNIT_ASSERT( !memcmp( bad.data( ) , s , ss ) ); + CPPUNIT_ASSERT( bool( bad ) ); + CPPUNIT_ASSERT( !!bad ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testCopyCons( ) +{ + char const* const s = "wargl gargl"; + const uint32_t sl( strlen( s ) ); + T_String s1( s ); + T_String s2( s1 ); + CPPUNIT_ASSERT( s1 ); + CPPUNIT_ASSERT( s2 ); + CPPUNIT_ASSERT( s2.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl , s2.size( ) ); + CPPUNIT_ASSERT( !memcmp( s1.data( ) , s2.data( ) , sl ) ); +} + +void StringsTest::testCopyAss( ) +{ + char const* const s = "wargl gargl"; + const uint32_t sl( strlen( s ) ); + T_String s1( s ); + T_String s2; + s2 = s1; + CPPUNIT_ASSERT( s1 ); + CPPUNIT_ASSERT( s2 ); + CPPUNIT_ASSERT( s2.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl , s2.size( ) ); + CPPUNIT_ASSERT( !memcmp( s , s2.data( ) , sl ) ); +} + +void StringsTest::testMoveCons( ) +{ + char const* const s = "wargl gargl"; + const uint32_t sl( strlen( s ) ); + T_String s1( s ); + T_String s2( std::move( s1 ) ); + CPPUNIT_ASSERT( !s1 ); + CPPUNIT_ASSERT( s2 ); + CPPUNIT_ASSERT( s2.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl , s2.size( ) ); + CPPUNIT_ASSERT( !memcmp( s , s2.data( ) , sl ) ); +} + +void StringsTest::testMoveAss( ) +{ + char const* const s = "wargl gargl"; + const uint32_t sl( strlen( s ) ); + T_String s1( s ); + T_String s2( "woot" ); + s2 = std::move( s1 ); + CPPUNIT_ASSERT( !s1 ); + CPPUNIT_ASSERT( s2 ); + CPPUNIT_ASSERT( s2.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl , s2.size( ) ); + CPPUNIT_ASSERT( !memcmp( s , s2.data( ) , sl ) ); +} + +void StringsTest::testSwap( ) +{ + char const* const str1 = "wargl gargl"; + const uint32_t sl1( strlen( str1 ) ); + char const* const str2 = "bite my shiny metal ass"; + const uint32_t sl2( strlen( str2 ) ); + + T_String s1( str1 ); + T_String s2( str2 ); + swap( s1 , s2 ); + + CPPUNIT_ASSERT( s1 ); + CPPUNIT_ASSERT( s1.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl2 , s1.size( ) ); + CPPUNIT_ASSERT( !memcmp( str2 , s1.data( ) , sl2 ) ); + + CPPUNIT_ASSERT( s2 ); + CPPUNIT_ASSERT( s2.data( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( sl1 , s2.size( ) ); + CPPUNIT_ASSERT( !memcmp( str1 , s2.data( ) , sl1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testCharAccess( ) +{ + char const* const s = "a\xe2\x82\xac\nb\0c"; + const T_Character chars[] = { + 'a' , 0x20ac , '\n' , 'b' , 0 , 'c' + }; + + T_String test( s , 8 ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 8 ) , test.size( ) ); + CPPUNIT_ASSERT_EQUAL( uint32_t( 6 ) , test.length( ) ); + CPPUNIT_ASSERT_EQUAL( chars[ 0 ] , test[ 0 ] ); + CPPUNIT_ASSERT_EQUAL( chars[ 1 ] , test[ 1 ] ); + CPPUNIT_ASSERT_EQUAL( chars[ 2 ] , test[ 2 ] ); + CPPUNIT_ASSERT_EQUAL( chars[ 3 ] , test[ 3 ] ); + CPPUNIT_ASSERT_EQUAL( chars[ 4 ] , test[ 4 ] ); + CPPUNIT_ASSERT_EQUAL( chars[ 5 ] , test[ 5 ] ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testEquals( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( s1.equals( s1 ) ); + CPPUNIT_ASSERT( s1.equals( s2 ) ); + CPPUNIT_ASSERT( !s1.equals( s3 ) ); + CPPUNIT_ASSERT( s2.equals( s2 ) ); + CPPUNIT_ASSERT( s2.equals( s1 ) ); + CPPUNIT_ASSERT( !s2.equals( s3 ) ); + CPPUNIT_ASSERT( s3.equals( s3 ) ); + CPPUNIT_ASSERT( !s3.equals( s1 ) ); + CPPUNIT_ASSERT( !s3.equals( s2 ) ); +} + +void StringsTest::testEqualsEmpty( ) +{ + T_String s1 , s2 , s3( "gargl" ); + CPPUNIT_ASSERT( s1.equals( s1 ) ); + CPPUNIT_ASSERT( s1.equals( s2 ) ); + CPPUNIT_ASSERT( !s1.equals( s3 ) ); + CPPUNIT_ASSERT( s2.equals( s2 ) ); + CPPUNIT_ASSERT( s2.equals( s1 ) ); + CPPUNIT_ASSERT( !s2.equals( s3 ) ); + CPPUNIT_ASSERT( s3.equals( s3 ) ); + CPPUNIT_ASSERT( !s3.equals( s1 ) ); + CPPUNIT_ASSERT( !s3.equals( s2 ) ); +} + +void StringsTest::testEqualsCString( ) +{ + T_String s1( "wargl" ) , s2; + CPPUNIT_ASSERT( s1.equals( "wargl" ) ); + CPPUNIT_ASSERT( !s1.equals( "gargl" ) ); + CPPUNIT_ASSERT( s2.equals( "" ) ); + CPPUNIT_ASSERT( !s2.equals( "wargl" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testCompareEmpty( ) +{ + T_String s1 , s2 , s3( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compare( s2 ) ); + CPPUNIT_ASSERT( s1.compare( s3 ) != 0 ); +} + +void StringsTest::testCompareSame( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compare( s2 ) ); +} + +void StringsTest::testCompareShorter( ) +{ + T_String s1( "wargl gargl" ) , s2( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , s1.compare( s2 ) ); +} + +void StringsTest::testCompareLonger( ) +{ + T_String s1( "wargl" ) , s2( "wargl gargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , s1.compare( s2 ) ); +} + +void StringsTest::testCompareInf( ) +{ + T_String s1( "wargl" ) , s2( "gargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , s1.compare( s2 ) ); +} + +void StringsTest::testCompareSup( ) +{ + T_String s1( "gargl" ) , s2( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , s1.compare( s2 ) ); +} + +void StringsTest::testCompareInvalid( ) +{ + T_String s1( "gargl\x80" ) , s2( "wargl\x80" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compare( s2 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testCompareICEmpty( ) +{ + T_String s1 , s2 , s3( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compareIgnoreCase( s2 ) ); + CPPUNIT_ASSERT( s1.compareIgnoreCase( s3 ) != 0 ); +} + +void StringsTest::testCompareICSame( ) +{ + T_String s1( "wArgl" ) , s2( "waRgl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compareIgnoreCase( s2 ) ); +} + +void StringsTest::testCompareICShorter( ) +{ + T_String s1( "waRgl gargl" ) , s2( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , s1.compareIgnoreCase( s2 ) ); +} + +void StringsTest::testCompareICLonger( ) +{ + T_String s1( "wargl" ) , s2( "WArgl gargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , s1.compareIgnoreCase( s2 ) ); +} + +void StringsTest::testCompareICInf( ) +{ + T_String s1( "wArgl" ) , s2( "wargh" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , s1.compareIgnoreCase( s2 ) ); +} + +void StringsTest::testCompareICSup( ) +{ + T_String s1( "wargh" ) , s2( "wArgl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , s1.compareIgnoreCase( s2 ) ); +} + +void StringsTest::testCompareICInvalid( ) +{ + T_String s1( "gargl\x80" ) , s2( "wargl\x80" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , s1.compareIgnoreCase( s2 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testStartsWithEmpty( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( str.startsWith( T_String( ) ) ); +} + +void StringsTest::testStartsWithLonger( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT( !str.startsWith( T_String( "wargl gargl" ) ) ); +} + +void StringsTest::testStartsWithYes( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( str.startsWith( T_String( "wargl" ) ) ); +} + +void StringsTest::testStartsWithNope( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( !str.startsWith( T_String( "gargl" ) ) ); +} + +void StringsTest::testStartsWithInvalid( ) +{ + T_String str( "\x80 wargl" ); + CPPUNIT_ASSERT( !str.startsWith( T_String( "\x80" ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testEndsWithEmpty( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( str.endsWith( T_String( ) ) ); +} + +void StringsTest::testEndsWithLonger( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT( !str.endsWith( T_String( "wargl gargl" ) ) ); +} + +void StringsTest::testEndsWithYes( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( str.endsWith( T_String( "gargl" ) ) ); +} + +void StringsTest::testEndsWithNope( ) +{ + T_String str( "wargl gargl" ); + CPPUNIT_ASSERT( !str.endsWith( T_String( "wargl" ) ) ); +} + +void StringsTest::testEndsWithInvalid( ) +{ + T_String str( "wargl \x80" ); + CPPUNIT_ASSERT( !str.endsWith( T_String( "\x80" ) ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testCmpOpEq( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( s1 == s2 ); + CPPUNIT_ASSERT( s2 == s1 ); + CPPUNIT_ASSERT( !( s1 == s3 ) ); + CPPUNIT_ASSERT( !( s3 == s1 ) ); + CPPUNIT_ASSERT( !( s2 == s3 ) ); + CPPUNIT_ASSERT( !( s3 == s2 ) ); +} + +void StringsTest::testCmpOpNe( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( !( s1 != s2 ) ); + CPPUNIT_ASSERT( !( s2 != s1 ) ); + CPPUNIT_ASSERT( s1 != s3 ); + CPPUNIT_ASSERT( s3 != s1 ); + CPPUNIT_ASSERT( s2 != s3 ); + CPPUNIT_ASSERT( s3 != s2 ); +} + +void StringsTest::testCmpOpLt( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( !( s1 < s2 ) ); + CPPUNIT_ASSERT( !( s1 < s3 ) ); + CPPUNIT_ASSERT( !( s2 < s1 ) ); + CPPUNIT_ASSERT( !( s2 < s3 ) ); + CPPUNIT_ASSERT( s3 < s1 ); + CPPUNIT_ASSERT( s3 < s2 ); +} + +void StringsTest::testCmpOpLe( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( s1 <= s2 ); + CPPUNIT_ASSERT( !( s1 < s3 ) ); + CPPUNIT_ASSERT( s2 <= s1 ); + CPPUNIT_ASSERT( !( s2 < s3 ) ); + CPPUNIT_ASSERT( s3 <= s1 ); + CPPUNIT_ASSERT( s3 <= s2 ); +} + +void StringsTest::testCmpOpGt( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( !( s1 > s2 ) ); + CPPUNIT_ASSERT( s1 > s3 ); + CPPUNIT_ASSERT( !( s2 > s1 ) ); + CPPUNIT_ASSERT( s2 > s3 ); + CPPUNIT_ASSERT( !( s3 > s1 ) ); + CPPUNIT_ASSERT( !( s3 > s2 ) ); +} + +void StringsTest::testCmpOpGe( ) +{ + T_String s1( "wargl" ) , s2( "wargl" ) , s3( "gargl" ); + CPPUNIT_ASSERT( s1 >= s2 ); + CPPUNIT_ASSERT( s1 >= s3 ); + CPPUNIT_ASSERT( s2 >= s1 ); + CPPUNIT_ASSERT( s2 >= s3 ); + CPPUNIT_ASSERT( !( s3 >= s1 ) ); + CPPUNIT_ASSERT( !( s3 >= s2 ) ); +} + +void StringsTest::testCmpOpEqCString( ) +{ + T_String s1( "wargl" ); + CPPUNIT_ASSERT( s1 == "wargl" ); + CPPUNIT_ASSERT( !( s1 == "gargl" ) ); +} + +void StringsTest::testCmpOpNeCString( ) +{ + T_String s1( "wargl" ); + CPPUNIT_ASSERT( s1 != "gargl" ); + CPPUNIT_ASSERT( !( s1 != "wargl" ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testFindEmpty( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 0 ) , str.find( T_String( ) ) ); +} + +void StringsTest::testFindLonger( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "wargl gargl" ) ) ); +} + +void StringsTest::testFindYes( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , str.find( T_String( "arg" ) ) ); +} + +void StringsTest::testFindNope( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "gar" ) ) ); +} + +void StringsTest::testFindInvalidStrings( ) +{ + T_String str( "lol\x80" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "\x80" ) ) ); +} + +void StringsTest::testFindOffsetEmpty( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 1 ) , str.find( T_String( ) , 1 ) ); +} + +void StringsTest::testFindOffsetLonger( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "wargl" ) , 1 ) ); +} + +void StringsTest::testFindOffsetBad( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "arg" ) , UINT32_MAX ) ); +} + +void StringsTest::testFindOffsetYes( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 2 ) , str.find( T_String( "rg" ) , 1 ) ); +} + +void StringsTest::testFindOffsetNo( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_String( "wa" ) , 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testFindCharEmpty( ) +{ + T_String str; + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( 'c' ) ); +} + +void StringsTest::testFindCharYes( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 2 ) , str.find( 'r' ) ); +} + +void StringsTest::testFindCharNo( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( 'c' ) ); +} + +void StringsTest::testFindCharInvalidString( ) +{ + T_String str( "wa\x80rgl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( 'w' ) ); +} + +void StringsTest::testFindCharInvalidChar( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( T_Character( 0x10000000 ) ) ); +} + +void StringsTest::testFindCharOffsetBad( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( 'l' , UINT32_MAX ) ); +} + +void StringsTest::testFindCharOffsetYes( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( 4 ) , str.find( 'l' , 1 ) ); +} + +void StringsTest::testFindCharOffsetNo( ) +{ + T_String str( "wargl" ); + CPPUNIT_ASSERT_EQUAL( int32_t( -1 ) , str.find( 'w' , 1 ) ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testToUnsignedBasic( ) +{ + T_String str( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( ) ); +} + +void StringsTest::testToUnsignedOkValue( ) +{ + bool ok; + T_String str( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedInvalid( ) +{ + bool ok; + T_String str( "123lol" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToUnsignedTooLarge( ) +{ + bool ok; + T_String str( "18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( UINT64_MAX ) , str.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToUnsignedBaseOk( ) +{ + bool ok; + T_String str( "1a23" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 6691 ) , str.toUnsignedInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseInvalid( ) +{ + bool ok; + T_String str( "12g3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 18 ) , str.toUnsignedInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToUnsignedBaseAutoBinary( ) +{ + bool ok; + T_String str( "0b1111" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 15 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseAutoOctal( ) +{ + bool ok; + T_String str( "0123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 83 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseAutoDecimal( ) +{ + bool ok; + T_String str( "123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseAutoHexa( ) +{ + bool ok; + T_String str( "0x123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 291 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseAutoZero( ) +{ + bool ok; + T_String str( "0" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedBaseAutoInvalid( ) +{ + bool ok; + T_String str( "0z123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToUnsignedBaseAutoCut( ) +{ + bool ok; + T_String str( "0x" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , str.toUnsignedInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToUnsignedSeparator( ) +{ + bool ok; + T_String str( "1 2 3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok , 10 , true ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedSeparatorCustom( ) +{ + bool ok; + T_String str( "1.2.3" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok , 10 , true , '.' ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedSeparatorCustomUTF8( ) +{ + bool ok; + T_String str( "1""\xe2\x82\xac""23" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok , 10 , true , T_Character( 0x20ac ) ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedLeadingWhitespace( ) +{ + bool ok; + T_String str( " 123" ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 123 ) , str.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToUnsignedLeadingWhitespaceOnly( ) +{ + bool ok; + T_String str( " " ); + CPPUNIT_ASSERT_EQUAL( uint64_t( 0 ) , str.toUnsignedInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +/*----------------------------------------------------------------------------*/ + +void StringsTest::testToSignedBasic( ) +{ + T_String str( "123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , str.toInteger( ) ); +} + +void StringsTest::testToSignedOkValue( ) +{ + bool ok; + T_String str( "123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedPlus( ) +{ + bool ok; + T_String str( "+123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedPlusMultiple( ) +{ + bool ok; + T_String str( "++123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedMinus( ) +{ + bool ok; + T_String str( "-123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -123 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedMinusMultiple( ) +{ + bool ok; + T_String str( "--123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedInvalid( ) +{ + bool ok; + T_String str( "1lol23wut" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 1 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedTooLarge( ) +{ + bool ok; + T_String str( "18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( INT64_MAX , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedTooSmall( ) +{ + bool ok; + T_String str( "-18446744073709551616" ); + CPPUNIT_ASSERT_EQUAL( INT64_MIN , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedBaseOk( ) +{ + bool ok; + T_String str( "1a2" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 418 ) , str.toInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseInvalid( ) +{ + bool ok; + T_String str( "g12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok , 16 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedBaseAutoBinary( ) +{ + bool ok; + T_String str( "0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 3 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoOctal( ) +{ + bool ok; + T_String str( "011" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 9 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoDecimal( ) +{ + bool ok; + T_String str( "11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 11 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoHexa( ) +{ + bool ok; + T_String str( "0x12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 18 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoZero( ) +{ + bool ok; + T_String str( "0" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoInvalid( ) +{ + bool ok; + T_String str( "0z12" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedBaseAutoCut( ) +{ + bool ok; + T_String str( "0x" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( !ok ); +} + +void StringsTest::testToSignedBaseAutoPlus( ) +{ + bool ok; + T_String str( "+0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 3 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedBaseAutoMinus( ) +{ + bool ok; + T_String str( "-0b11" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -3 ) , str.toInteger( &ok , 0 ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedSeparator( ) +{ + bool ok; + T_String str( "-1 1" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -11 ) , str.toInteger( &ok , 10 , true ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedSeparatorCustom( ) +{ + bool ok; + T_String str( "-1x1" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -11 ) , str.toInteger( &ok , 10 , true , 'x' ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedSeparatorCustomUTF8( ) +{ + bool ok; + T_String str( "1""\xe2\x82\xac""23" ); + CPPUNIT_ASSERT_EQUAL( int64_t( 123 ) , str.toInteger( &ok , 10 , true , T_Character( 0x20ac ) ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedLeadingWhitespace( ) +{ + bool ok; + T_String str( " - 123" ); + CPPUNIT_ASSERT_EQUAL( int64_t( -123 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( ok ); +} + +void StringsTest::testToSignedLeadingWhitespaceOnly( ) +{ + bool ok; + T_String str( " " ); + CPPUNIT_ASSERT_EQUAL( int64_t( 0 ) , str.toInteger( &ok ) ); + CPPUNIT_ASSERT( !ok ); +} + +/*----------------------------------------------------------------------------*/ + +#define M_CHECK_( STR , VAL , OK ) \ + do { \ + bool ok; \ + double v( T_String( STR ).toDouble( &ok , '.' , true , ',' ) ); \ + CPPUNIT_ASSERT_EQUAL( double( VAL ) , v ); \ + CPPUNIT_ASSERT_EQUAL( bool( OK ) , ok ); \ + } while ( 0 ) + +void StringsTest::testToDouble( ) +{ + M_CHECK_( " " , 0 , false ); + M_CHECK_( " 0" , 0 , true ); + M_CHECK_( " ,,0" , 0 , false ); + M_CHECK_( "+" , 0 , false ); + M_CHECK_( "-" , 0 , false ); + M_CHECK_( "123" , 123 , true ); + M_CHECK_( ",123" , 0 , false ); + M_CHECK_( "1,2,3" , 123 , true ); + M_CHECK_( "1,2," , 0 , false ); + M_CHECK_( ",-123" , 0 , false ); + M_CHECK_( "-123" , -123 , true ); + M_CHECK_( "+123" , +123 , true ); + M_CHECK_( "+,123" , 0 , false ); + M_CHECK_( ".12" , .12 , true ); + M_CHECK_( "-.12" , -.12 , true ); + M_CHECK_( "+.12" , .12 , true ); + M_CHECK_( "." , 0 , true ); + M_CHECK_( "12." , 12 , true ); + M_CHECK_( "12e" , 0 , false ); + M_CHECK_( "12e2" , 12e2 , true ); + M_CHECK_( "12e+" , 0 , false ); + M_CHECK_( "12e-" , 0 , false ); + M_CHECK_( "12e+2" , 12e2 , true ); + M_CHECK_( "12e-2" , 12e-2 , true ); + M_CHECK_( "1.2e2" , 120 , true ); + M_CHECK_( "1.2e+2" , 120 , true ); + M_CHECK_( "1.2e-2" , 1.2e-2 , true ); + M_CHECK_( "1e3000" , std::numeric_limits< double >::infinity( ) , false ); + M_CHECK_( "-1e3000" , -std::numeric_limits< double >::infinity( ) , false ); + M_CHECK_( "1e-3000" , 0 , false ); +} + +#undef M_CHECK_ diff --git a/tests/union.cc b/tests/union.cc new file mode 100644 index 0000000..fbb7d16 --- /dev/null +++ b/tests/union.cc @@ -0,0 +1,386 @@ +#include +#include +using namespace lw; + + +class UnionTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( UnionTest ); + CPPUNIT_TEST( testDefaultConsSimple ); + CPPUNIT_TEST( testDefaultConsObject ); + CPPUNIT_TEST( testNoDefaultCons ); + CPPUNIT_TEST( testInPlaceCons ); + CPPUNIT_TEST( testDestruct ); + + CPPUNIT_TEST( testMoveCons ); + CPPUNIT_TEST( testMoveAss ); + CPPUNIT_TEST( testMoveConsNoCopy ); + CPPUNIT_TEST( testMoveAssNoCopy ); + + CPPUNIT_TEST( testCopyCons ); + CPPUNIT_TEST( testCopyAss ); + CPPUNIT_TEST( testCopyConsNoMove ); + CPPUNIT_TEST( testCopyAssNoMove ); + + CPPUNIT_TEST( testMoveContentsCons ); + CPPUNIT_TEST( testMoveContentsAss ); + CPPUNIT_TEST( testCopyContentsCons ); + CPPUNIT_TEST( testCopyContentsAss ); + + CPPUNIT_TEST( testSwap ); + + CPPUNIT_TEST( testTypeInfo ); + CPPUNIT_TEST( testTargetModify ); + + CPPUNIT_TEST( testValue ); + CPPUNIT_TEST( testValueModify ); + CPPUNIT_TEST( testValueException ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testDefaultConsSimple( ); + void testDefaultConsObject( ); + void testNoDefaultCons( ); + void testInPlaceCons( ); + void testDestruct( ); + + void testMoveCons( ); + void testMoveAss( ); + void testMoveConsNoCopy( ); + void testMoveAssNoCopy( ); + + void testCopyCons( ); + void testCopyAss( ); + void testCopyConsNoMove( ); + void testCopyAssNoMove( ); + + void testMoveContentsCons( ); + void testMoveContentsAss( ); + void testCopyContentsCons( ); + void testCopyContentsAss( ); + + void testSwap( ); + + void testTypeInfo( ); + void testTargetModify( ); + + void testValue( ); + void testValueModify( ); + void testValueException( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( UnionTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +struct T_DefCons_ +{ + int x; + T_DefCons_( ) noexcept : x( 123 ) { } +}; + +struct T_NoDefCons_ +{ + int x; + T_NoDefCons_( ) = delete; + explicit T_NoDefCons_( int x ) noexcept : x( x ) { } +}; + +struct T_NoMove_ +{ + int x; + T_NoMove_( ) = delete; + explicit T_NoMove_( int x ) noexcept : x( x ) { } + T_NoMove_( T_NoMove_ const& o ) noexcept : x( o.x ) { } + T_NoMove_( T_NoMove_&& ) noexcept = delete; +}; + +struct T_NoCopy_ +{ + int x; + T_NoCopy_( ) = delete; + explicit T_NoCopy_( int x ) noexcept : x( x ) { } + T_NoCopy_( T_NoCopy_ const& ) noexcept = delete; + T_NoCopy_( T_NoCopy_&& o ) noexcept : x( o.x ) { } +}; + +int consCounter = 0; +int delCounter = 0; +int mvCounter = 0; +int cpCounter = 0; + +struct T_Counter_ +{ + int value; + + T_Counter_( ) noexcept : value( 0 ) { consCounter ++; } + explicit T_Counter_( int x ) noexcept : value( x ) { consCounter ++; } + + T_Counter_( T_Counter_ const& o ) noexcept : value( o.value ) { cpCounter ++; } + T_Counter_( T_Counter_&& o ) noexcept : value( o.value ) { mvCounter ++; } + + ~T_Counter_( ) { delCounter ++; } +}; + +#define M_COUNTS_(C,D,CP,M) \ + do { \ + CPPUNIT_ASSERT_EQUAL( int( C ) , consCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( D ) , delCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( CP ) , cpCounter ); \ + CPPUNIT_ASSERT_EQUAL( int( M ) , mvCounter ); \ + } while ( 0 ) + +} // namespace + +void UnionTest::tearDown( ) +{ + consCounter = delCounter = mvCounter = cpCounter = 0; +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testDefaultConsSimple( ) +{ + const T_Union< uint32_t , T_DefCons_ > u; + CPPUNIT_ASSERT( u.hasType< uint32_t >( ) ); + CPPUNIT_ASSERT( u.target< uint32_t >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 0u , *( u.target< uint32_t >( ) ) ); +} + +void UnionTest::testDefaultConsObject( ) +{ + const T_Union< T_DefCons_ , uint32_t > u; + CPPUNIT_ASSERT( u.hasType< T_DefCons_ >( ) ); + CPPUNIT_ASSERT( u.target< T_DefCons_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u.target< T_DefCons_ >( )->x ); +} + +void UnionTest::testNoDefaultCons( ) +{ + constexpr bool r( !std::is_default_constructible< T_Union< T_NoDefCons_ , uint32_t > >::value ); + CPPUNIT_ASSERT( r ); +} + +void UnionTest::testInPlaceCons( ) +{ + const T_Union< uint32_t , T_NoDefCons_ > u( Construct< T_NoDefCons_ >( ) , 456 ); + CPPUNIT_ASSERT( u.hasType< T_NoDefCons_ >( ) ); + CPPUNIT_ASSERT( u.target< T_NoDefCons_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 456 , u.target< T_NoDefCons_ >( )->x ); +} + +void UnionTest::testDestruct( ) +{ + { + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + } + M_COUNTS_( 1 , 1 , 0 , 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testMoveCons( ) +{ + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , uint32_t > u1( std::move( u0 ) ); + M_COUNTS_( 1 , 0 , 0 , 1 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testMoveAss( ) +{ + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , uint32_t > u1( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + u1 = std::move( u0 ); + M_COUNTS_( 2 , 1 , 0 , 1 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testMoveConsNoCopy( ) +{ + T_Union< T_Counter_ , T_NoCopy_ > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , T_NoCopy_ > u1( std::move( u0 ) ); + M_COUNTS_( 1 , 0 , 0 , 1 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testMoveAssNoCopy( ) +{ + T_Union< T_Counter_ , T_NoCopy_ > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , T_NoCopy_ > u1( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + u1 = std::move( u0 ); + M_COUNTS_( 2 , 1 , 0 , 1 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testCopyCons( ) +{ + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , uint32_t > u1( u0 ); + M_COUNTS_( 1 , 0 , 1 , 0 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testCopyAss( ) +{ + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , uint32_t > u1( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + u1 = u0; + M_COUNTS_( 2 , 1 , 1 , 0 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testCopyConsNoMove( ) +{ + T_Union< T_Counter_ , T_NoMove_ > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , T_NoMove_ > u1( u0 ); + M_COUNTS_( 1 , 0 , 1 , 0 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +void UnionTest::testCopyAssNoMove( ) +{ + T_Union< T_Counter_ , T_NoMove_ > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , T_NoMove_ > u1( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + u1 = u0; + M_COUNTS_( 2 , 1 , 1 , 0 ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testMoveContentsCons( ) +{ + T_Counter_ c( 123 ); + T_Union< T_Counter_ , uint32_t > u( std::move( c ) ); + M_COUNTS_( 1 , 0 , 0 , 1 ); + CPPUNIT_ASSERT( u.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u.target< T_Counter_ >( )->value ); +} + +void UnionTest::testMoveContentsAss( ) +{ + T_Union< T_Counter_ , uint32_t > u( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 1 , 0 , 0 , 0 ); + u = T_Counter_( 123 ); + M_COUNTS_( 2 , 2 , 0 , 1 ); + CPPUNIT_ASSERT( u.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u.target< T_Counter_ >( )->value ); +} + +void UnionTest::testCopyContentsCons( ) +{ + T_Counter_ c( 123 ); + T_Union< T_Counter_ , uint32_t > u( c ); + M_COUNTS_( 1 , 0 , 1 , 0 ); + CPPUNIT_ASSERT( u.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u.target< T_Counter_ >( )->value ); +} + +void UnionTest::testCopyContentsAss( ) +{ + T_Counter_ c( 123 ); + T_Union< T_Counter_ , uint32_t > u( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + u = c; + M_COUNTS_( 2 , 1 , 1 , 0 ); + CPPUNIT_ASSERT( u.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u.target< T_Counter_ >( )->value ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testSwap( ) +{ + T_Union< T_Counter_ , uint32_t > u0( Construct< T_Counter_ >( ) , 123 ); + T_Union< T_Counter_ , uint32_t > u1( Construct< T_Counter_ >( ) , 456 ); + M_COUNTS_( 2 , 0 , 0 , 0 ); + swap( u0 , u1 ); + M_COUNTS_( 2 , 3 , 0 , 3 ); + CPPUNIT_ASSERT( u0.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u0.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 456 , u0.target< T_Counter_ >( )->value ); + CPPUNIT_ASSERT( u1.hasType< T_Counter_ >( ) ); + CPPUNIT_ASSERT( u1.target< T_Counter_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123 , u1.target< T_Counter_ >( )->value ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testTypeInfo( ) +{ + T_Union< T_DefCons_ , uint32_t > u; + CPPUNIT_ASSERT( u.typeInfo( ) == typeid( T_DefCons_ ) ); + u = 1u; + CPPUNIT_ASSERT( u.typeInfo( ) == typeid( uint32_t ) ); +} + +void UnionTest::testTargetModify( ) +{ + T_Union< T_DefCons_ , uint32_t > u; + u.target< T_DefCons_ >( )->x = 987; + CPPUNIT_ASSERT( u.hasType< T_DefCons_ >( ) ); + CPPUNIT_ASSERT( u.target< T_DefCons_ >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 987 , u.target< T_DefCons_ >( )->x ); +} + +/*----------------------------------------------------------------------------*/ + +void UnionTest::testValue( ) +{ + T_Union< uint32_t , T_DefCons_ > u( 123u ); + CPPUNIT_ASSERT( u.hasType< uint32_t >( ) ); + CPPUNIT_ASSERT( u.target< uint32_t >( ) != nullptr ); + CPPUNIT_ASSERT_EQUAL( 123u , u.value< uint32_t >( ) ); +} + +void UnionTest::testValueModify( ) +{ + T_Union< uint32_t , T_DefCons_ > u( 123u ); + CPPUNIT_ASSERT( u.hasType< uint32_t >( ) ); + CPPUNIT_ASSERT( u.target< uint32_t >( ) != nullptr ); + u.value< uint32_t >( ) = 456; + CPPUNIT_ASSERT_EQUAL( 456u , u.value< uint32_t >( ) ); +} + +void UnionTest::testValueException( ) +{ + T_Union< uint32_t , T_DefCons_ > u( T_DefCons_{} ); + CPPUNIT_ASSERT( u.hasType< T_DefCons_ >( ) ); + try { + u.value< uint32_t >( ) = 456u; + CPPUNIT_FAIL( "exception not thrown" ); + } catch ( std::bad_cast const& ) { + // OK + } +} + diff --git a/tests/variant.cc b/tests/variant.cc new file mode 100644 index 0000000..73329b7 --- /dev/null +++ b/tests/variant.cc @@ -0,0 +1,1016 @@ +#include +#include +using namespace lw; + + +class VariantTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( VariantTest ); + CPPUNIT_TEST( testEmpty ); + + CPPUNIT_TEST( testValueCopyCons ); + CPPUNIT_TEST( testValueMoveCons ); + CPPUNIT_TEST( testValueCopyAss ); + CPPUNIT_TEST( testValueMoveAss ); + CPPUNIT_TEST( testValueNoMoveCons ); + CPPUNIT_TEST( testValueNoMoveAss ); + + CPPUNIT_TEST( testLargeCopyCons ); + CPPUNIT_TEST( testLargeMoveCons ); + CPPUNIT_TEST( testLargeCopyAss ); + CPPUNIT_TEST( testLargeMoveAss ); + + CPPUNIT_TEST( testLIPCopyCons ); + CPPUNIT_TEST( testLIPMoveCons ); + CPPUNIT_TEST( testLIPCopyAss ); + CPPUNIT_TEST( testLIPMoveAss ); + + CPPUNIT_TEST( testValueRead ); + CPPUNIT_TEST( testLargeRead ); + CPPUNIT_TEST( testLIPRead ); + CPPUNIT_TEST( testValueReadBadType ); + CPPUNIT_TEST( testValueWrite ); + CPPUNIT_TEST( testLargeWrite ); + CPPUNIT_TEST( testLIPWrite ); + CPPUNIT_TEST( testValueWriteBadType ); + + CPPUNIT_TEST( testCopyConsSmall ); + CPPUNIT_TEST( testCopyConsLarge ); + CPPUNIT_TEST( testCopyConsLIP ); + CPPUNIT_TEST( testCopyAssSmall ); + CPPUNIT_TEST( testCopyAssLarge ); + CPPUNIT_TEST( testCopyAssLIP ); + CPPUNIT_TEST( testMoveConsSmall ); + CPPUNIT_TEST( testMoveConsLarge ); + CPPUNIT_TEST( testMoveConsLIP ); + CPPUNIT_TEST( testMoveAssSmall ); + CPPUNIT_TEST( testMoveAssLarge ); + CPPUNIT_TEST( testMoveAssLIP ); + + CPPUNIT_TEST( testSwapSS ); + CPPUNIT_TEST( testSwapLL ); + CPPUNIT_TEST( testSwapSL ); + CPPUNIT_TEST( testSwapES ); + CPPUNIT_TEST( testSwapEL ); + + CPPUNIT_TEST( testSwapLIPSS ); + CPPUNIT_TEST( testSwapLIPLL ); + CPPUNIT_TEST( testSwapLIPSL ); + CPPUNIT_TEST( testSwapLIPES ); + CPPUNIT_TEST( testSwapLIPEL ); + + CPPUNIT_TEST( testCopyConsLIPToSmall ); + CPPUNIT_TEST( testCopyConsLIPToLarge ); + CPPUNIT_TEST( testCopyConsSmallToLIP ); + CPPUNIT_TEST( testCopyConsLargeToLIP ); + CPPUNIT_TEST( testMoveConsLIPToSmall ); + CPPUNIT_TEST( testMoveConsLIPToLarge ); + CPPUNIT_TEST( testMoveConsSmallToLIP ); + CPPUNIT_TEST( testMoveConsLargeToLIP ); + CPPUNIT_TEST( testCopyAssLIPToSmall ); + CPPUNIT_TEST( testCopyAssLIPToLarge ); + CPPUNIT_TEST( testCopyAssSmallToLIP ); + CPPUNIT_TEST( testCopyAssLargeToLIP ); + CPPUNIT_TEST( testMoveAssLIPToSmall ); + CPPUNIT_TEST( testMoveAssLIPToLarge ); + CPPUNIT_TEST( testMoveAssSmallToLIP ); + CPPUNIT_TEST( testMoveAssLargeToLIP ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void tearDown( ) override; + + void testEmpty( ); + + void testValueCopyCons( ); + void testValueMoveCons( ); + void testValueCopyAss( ); + void testValueMoveAss( ); + void testValueNoMoveCons( ); + void testValueNoMoveAss( ); + + void testLargeCopyCons( ); + void testLargeMoveCons( ); + void testLargeCopyAss( ); + void testLargeMoveAss( ); + + void testLIPCopyCons( ); + void testLIPMoveCons( ); + void testLIPCopyAss( ); + void testLIPMoveAss( ); + + void testValueRead( ); + void testLargeRead( ); + void testLIPRead( ); + void testValueReadBadType( ); + void testValueWrite( ); + void testLargeWrite( ); + void testLIPWrite( ); + void testValueWriteBadType( ); + + void testCopyConsSmall( ); + void testCopyConsLarge( ); + void testCopyConsLIP( ); + void testCopyAssSmall( ); + void testCopyAssLarge( ); + void testCopyAssLIP( ); + void testMoveConsSmall( ); + void testMoveConsLarge( ); + void testMoveConsLIP( ); + void testMoveAssSmall( ); + void testMoveAssLarge( ); + void testMoveAssLIP( ); + + void testSwapSS( ); + void testSwapLL( ); + void testSwapSL( ); + void testSwapES( ); + void testSwapEL( ); + + void testSwapLIPSS( ); + void testSwapLIPLL( ); + void testSwapLIPSL( ); + void testSwapLIPES( ); + void testSwapLIPEL( ); + + void testCopyConsLIPToSmall( ); + void testCopyConsLIPToLarge( ); + void testCopyConsSmallToLIP( ); + void testCopyConsLargeToLIP( ); + void testMoveConsLIPToSmall( ); + void testMoveConsLIPToLarge( ); + void testMoveConsSmallToLIP( ); + void testMoveConsLargeToLIP( ); + void testCopyAssLIPToSmall( ); + void testCopyAssLIPToLarge( ); + void testCopyAssSmallToLIP( ); + void testCopyAssLargeToLIP( ); + void testMoveAssLIPToSmall( ); + void testMoveAssLIPToLarge( ); + void testMoveAssSmallToLIP( ); + void testMoveAssLargeToLIP( ); +}; +CPPUNIT_TEST_SUITE_REGISTRATION( VariantTest ); + +/*----------------------------------------------------------------------------*/ + +namespace { + +struct T_Base +{ + static int copies; + static int moves; + static void reset( ) { copies = moves = 0; } +}; + +int T_Base::copies = 0; +int T_Base::moves = 0; + +struct T_Obj : public T_Base +{ + uint32_t v; + T_Obj( ) : T_Obj( 0 ) { } + T_Obj( uint32_t v ) : v( v ) { } + T_Obj( T_Obj const& o ) : T_Obj( o.v ) { copies ++; } + T_Obj& operator =( T_Obj const& o ) { v = o.v; copies ++; return *this; } + T_Obj( T_Obj&& o ) noexcept : T_Obj( o.v ) { moves ++; } + T_Obj& operator =( T_Obj&& o ) noexcept { v = o.v; moves ++; return *this; } +}; + +struct T_ObjCopy : public T_Base +{ + uint32_t v; + T_ObjCopy( ) : T_ObjCopy( 0 ) { } + T_ObjCopy( uint32_t v ) : v( v ) { } + T_ObjCopy( T_ObjCopy const& o ) : T_ObjCopy( o.v ) { copies ++; } + T_ObjCopy& operator =( T_ObjCopy const& o ) { v = o.v; copies ++; return *this; } + T_ObjCopy( T_ObjCopy&& o ) noexcept = delete; + T_ObjCopy& operator =( T_ObjCopy&& o ) noexcept = delete; +}; + +struct T_ObjLarge : public T_Base +{ + uint32_t v[ 32 ]; + T_ObjLarge( ) : T_ObjLarge( 0 ) { } + + T_ObjLarge( uint32_t value ) + { + for ( uint32_t i = 0 ; i < sizeof( v ) / sizeof( uint32_t ) ; i ++ ) { + v[ i ] = value; + } + } + + T_ObjLarge( T_ObjLarge const& o ) : T_ObjLarge( o.v[ 0 ] ) { copies ++; } + T_ObjLarge& operator =( T_ObjLarge const& o ) + { + memcpy( v , o.v , sizeof( v ) ); + copies ++; + return *this; + } + + T_ObjLarge( T_ObjLarge&& o ) noexcept : T_ObjLarge( o.v[ 0 ] ) { moves ++; } + T_ObjLarge& operator =( T_ObjLarge&& o ) noexcept + { + memcpy( v , o.v , sizeof( v ) ); + moves ++; + return *this; + } +}; + +static_assert( sizeof( T_Obj ) <= sizeof( void* ) && alignof( T_Obj ) <= alignof( void * ) , + "T_Obj would not be stored in-place" ); +static_assert( sizeof( T_ObjCopy ) <= sizeof( void* ) && alignof( T_ObjCopy ) <= alignof( void * ) , + "T_ObjCopy would not be stored in-place" ); +static_assert( sizeof( T_ObjLarge ) > sizeof( void* ) || alignof( T_ObjLarge ) > alignof( void * ) , + "T_ObjLarge would be stored in-place" ); + +using T_LIP_ = T_VariantExt< sizeof( T_ObjLarge ) >; + +template< typename Type , size_t S > +bool IsInPlace( T_VariantExt< S > const* container , + Type const* contained ) noexcept +{ + char const* cStart( (char const*) container ); + char const* cEnd( cStart + sizeof( T_VariantExt< S > ) ); + char const* oStart( (char const*) contained ); + char const* oEnd( oStart + sizeof( Type ) ); + return oStart >= cStart && oEnd <= cEnd; +} + +#define M_INPLACE( Container , Type ) \ + IsInPlace( &Container , &( Container.value< Type >( ) ) ) + +} + +void VariantTest::tearDown( ) +{ + T_Base::reset( ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testEmpty( ) +{ + T_Variant test; + CPPUNIT_ASSERT( !bool( test ) ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( void ) ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testValueCopyCons( ) +{ + T_Obj init( 12 ); + T_Variant test( init ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testValueMoveCons( ) +{ + T_Variant test( T_Obj( 12 ) ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testValueCopyAss( ) +{ + T_Obj init( 12 ); + T_Variant test; + test = init; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testValueMoveAss( ) +{ + T_Variant test; + test = T_Obj( 12 ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testValueNoMoveCons( ) +{ + T_Variant test( T_ObjCopy( 12 ) ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjCopy ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjCopy ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testValueNoMoveAss( ) +{ + T_Variant test; + test = T_ObjCopy( 12 ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjCopy ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjCopy ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testLargeCopyCons( ) +{ + T_ObjLarge init( 12 ); + T_Variant test( init ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testLargeMoveCons( ) +{ + T_Variant test( T_ObjLarge( 12 ) ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testLargeCopyAss( ) +{ + T_ObjLarge init( 12 ); + T_Variant test; + test = init; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testLargeMoveAss( ) +{ + T_Variant test; + test = T_ObjLarge( 12 ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( !M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testLIPCopyCons( ) +{ + T_ObjLarge init( 12 ); + T_LIP_ test( init ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testLIPMoveCons( ) +{ + T_LIP_ test( T_ObjLarge( 12 ) ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testLIPCopyAss( ) +{ + T_ObjLarge init( 12 ); + T_LIP_ test; + test = init; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testLIPMoveAss( ) +{ + T_LIP_ test; + test = T_ObjLarge( 12 ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( !( !test ) ); + CPPUNIT_ASSERT( test.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( M_INPLACE( test , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testValueRead( ) +{ + T_Variant test( T_Obj( 12 ) ); + CPPUNIT_ASSERT( test.value< T_Obj >( ).v == 12 ); +} + +void VariantTest::testLargeRead( ) +{ + T_Variant test( T_ObjLarge( 12 ) ); + CPPUNIT_ASSERT( test.value< T_ObjLarge >( ).v[ 0 ] == 12 ); +} + +void VariantTest::testLIPRead( ) +{ + T_LIP_ test( T_ObjLarge( 12 ) ); + CPPUNIT_ASSERT( test.value< T_ObjLarge >( ).v[ 0 ] == 12 ); +} + +void VariantTest::testValueReadBadType( ) +{ + T_Variant test( T_ObjCopy( 12 ) ); + try { + test.value< T_Obj >( ); + CPPUNIT_FAIL( "exception not thrown" ); + } catch ( std::bad_cast const& ) { } +} + +void VariantTest::testValueWrite( ) +{ + T_Variant test( T_Obj( 1 ) ); + test.value< T_Obj >( ).v = 2; + CPPUNIT_ASSERT( test.value< T_Obj >( ).v == 2 ); +} + +void VariantTest::testLargeWrite( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + test.value< T_ObjLarge >( ).v[ 5 ] = 2; + CPPUNIT_ASSERT( test.value< T_ObjLarge >( ).v[ 5 ] == 2 ); +} + +void VariantTest::testLIPWrite( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + test.value< T_ObjLarge >( ).v[ 5 ] = 2; + CPPUNIT_ASSERT( test.value< T_ObjLarge >( ).v[ 5 ] == 2 ); +} + +void VariantTest::testValueWriteBadType( ) +{ + T_Variant test( T_ObjCopy( 12 ) ); + try { + test.value< T_Obj >( ).v = 1; + CPPUNIT_FAIL( "exception not thrown" ); + } catch ( std::bad_cast const& ) { } +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testCopyConsSmall( ) +{ + T_Variant test( T_Obj( 1 ) ); + T_Base::reset( ); + + T_Variant copy( test ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( copy.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testCopyConsLarge( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_Variant copy( test ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( !M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testCopyConsLIP( ) +{ + T_LIP_ test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_LIP_ copy( test ); + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testCopyAssSmall( ) +{ + T_Variant test( T_Obj( 1 ) ); + T_Base::reset( ); + + T_Variant copy; + copy = test; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( copy.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testCopyAssLarge( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_Variant copy; + copy = test; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( !M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testCopyAssLIP( ) +{ + T_LIP_ test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_LIP_ copy; + copy = test; + CPPUNIT_ASSERT( bool( test ) ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testMoveConsSmall( ) +{ + T_Variant test( T_Obj( 1 ) ); + T_Base::reset( ); + + T_Variant copy( std::move( test ) ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( copy.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testMoveConsLarge( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_Variant copy( std::move( test ) ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( !M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testMoveConsLIP( ) +{ + T_LIP_ test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_LIP_ copy( std::move( test ) ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testMoveAssSmall( ) +{ + T_Variant test( T_Obj( 1 ) ); + T_Base::reset( ); + + T_Variant copy; + copy = std::move( test ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( copy.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_Obj ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testMoveAssLarge( ) +{ + T_Variant test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_Variant copy; + copy = std::move( test ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( !M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testMoveAssLIP( ) +{ + T_LIP_ test( T_ObjLarge( 1 ) ); + T_Base::reset( ); + + T_LIP_ copy; + copy = std::move( test ); + CPPUNIT_ASSERT( !test ); + CPPUNIT_ASSERT( bool( copy ) ); + CPPUNIT_ASSERT( !( !copy ) ); + CPPUNIT_ASSERT( copy.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( copy.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT( M_INPLACE( copy , T_ObjLarge ) ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testSwapSS( ) +{ + T_Variant test1( T_ObjCopy( 1 ) ) , test2( T_Obj( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test1.value< T_Obj >( ).v == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjCopy ) ); + CPPUNIT_ASSERT( test2.value< T_ObjCopy >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 2 , T_Base::moves ); +} + +void VariantTest::testSwapLL( ) +{ + T_Variant test1( T_ObjLarge( 1 ) ) , test2( T_ObjLarge( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test1.value< T_ObjLarge >( ).v[ 0 ] == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test2.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +void VariantTest::testSwapSL( ) +{ + T_Variant test1( T_Obj( 1 ) ) , test2( T_ObjLarge( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test1.value< T_ObjLarge >( ).v[ 0 ] == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test2.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testSwapES( ) +{ + T_Variant test1( T_Obj( 1 ) ) , test2; + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( !test1 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test2.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testSwapEL( ) +{ + T_Variant test1( T_ObjLarge( 1 ) ) , test2; + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( !test1 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test2.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testSwapLIPSS( ) +{ + T_LIP_ test1( T_ObjCopy( 1 ) ) , test2( T_Obj( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test1.value< T_Obj >( ).v == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjCopy ) ); + CPPUNIT_ASSERT( test2.value< T_ObjCopy >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 2 , T_Base::moves ); +} + +void VariantTest::testSwapLIPLL( ) +{ + T_LIP_ test1( T_ObjLarge( 1 ) ) , test2( T_ObjLarge( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test1.value< T_ObjLarge >( ).v[ 0 ] == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test2.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 3 , T_Base::moves ); +} + +void VariantTest::testSwapLIPSL( ) +{ + T_LIP_ test1( T_Obj( 1 ) ) , test2( T_ObjLarge( 2 ) ); + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( test1.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test1.value< T_ObjLarge >( ).v[ 0 ] == 2 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test2.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 3 , T_Base::moves ); +} + +void VariantTest::testSwapLIPES( ) +{ + T_LIP_ test1( T_Obj( 1 ) ) , test2; + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( !test1 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( test2.value< T_Obj >( ).v == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +void VariantTest::testSwapLIPEL( ) +{ + T_LIP_ test1( T_ObjLarge( 1 ) ) , test2; + T_Base::reset( ); + swap( test1 , test2 ); + + CPPUNIT_ASSERT( !test1 ); + CPPUNIT_ASSERT( test2.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( test2.value< T_ObjLarge >( ).v[ 0 ] == 1 ); + CPPUNIT_ASSERT_EQUAL( 0 , T_Base::copies ); + CPPUNIT_ASSERT_EQUAL( 1 , T_Base::moves ); +} + +/*----------------------------------------------------------------------------*/ + +void VariantTest::testCopyConsLIPToSmall( ) +{ + T_LIP_ src( T_Obj( 12 ) ); + T_Variant dest( src ); + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testCopyConsLIPToLarge( ) +{ + T_LIP_ src( T_ObjLarge( 12 ) ); + T_Variant dest( src ); + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testCopyConsSmallToLIP( ) +{ + T_Variant src( T_Obj( 2 ) ); + T_LIP_ dest( src ); + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 2 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testCopyConsLargeToLIP( ) +{ + T_Variant src( T_ObjLarge( 12 ) ); + T_LIP_ dest( src ); + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testMoveConsLIPToSmall( ) +{ + T_LIP_ src( T_Obj( 12 ) ); + T_Variant dest( std::move( src ) ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testMoveConsLIPToLarge( ) +{ + T_LIP_ src( T_ObjLarge( 12 ) ); + T_Variant dest( std::move( src ) ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testMoveConsSmallToLIP( ) +{ + T_Variant src( T_Obj( 12 ) ); + T_LIP_ dest( std::move( src ) ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testMoveConsLargeToLIP( ) +{ + T_Variant src( T_ObjLarge( 12 ) ); + T_LIP_ dest( std::move( src ) ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testCopyAssLIPToSmall( ) +{ + T_LIP_ src( T_Obj( 12 ) ); + T_Variant dest; + dest = src; + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testCopyAssLIPToLarge( ) +{ + T_LIP_ src( T_ObjLarge( 12 ) ); + T_Variant dest; + dest = src; + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testCopyAssSmallToLIP( ) +{ + T_Variant src( T_Obj( 2 ) ); + T_LIP_ dest; + dest = src; + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 2 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testCopyAssLargeToLIP( ) +{ + T_Variant src( T_ObjLarge( 12 ) ); + T_LIP_ dest; + dest = src; + CPPUNIT_ASSERT( bool( src ) ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testMoveAssLIPToSmall( ) +{ + T_LIP_ src( T_Obj( 12 ) ); + T_Variant dest; + dest = std::move( src ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 12 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testMoveAssLIPToLarge( ) +{ + T_LIP_ src( T_ObjLarge( 12 ) ); + T_Variant dest; + dest = std::move( src ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} + +void VariantTest::testMoveAssSmallToLIP( ) +{ + T_Variant src( T_Obj( 2 ) ); + T_LIP_ dest; + dest = std::move( src ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_Obj ) ); + CPPUNIT_ASSERT( dest.value< T_Obj >( ).v == 2 ); + CPPUNIT_ASSERT( M_INPLACE( dest , T_Obj ) ); +} + +void VariantTest::testMoveAssLargeToLIP( ) +{ + T_Variant src( T_ObjLarge( 12 ) ); + T_LIP_ dest; + dest = std::move( src ); + CPPUNIT_ASSERT( !src ); + CPPUNIT_ASSERT( bool( dest ) ); + CPPUNIT_ASSERT( dest.typeInfo( ) == typeid( T_ObjLarge ) ); + CPPUNIT_ASSERT( dest.value< T_ObjLarge >( ).v[ 0 ] == 12 ); + CPPUNIT_ASSERT( !M_INPLACE( dest , T_ObjLarge ) ); +} diff --git a/tests/vfs.cc b/tests/vfs.cc new file mode 100644 index 0000000..03be00b --- /dev/null +++ b/tests/vfs.cc @@ -0,0 +1,318 @@ +#include +#include +using namespace lw; + + +/*= VFSPathTest ===============================================================*/ + +class VFSPathTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( VFSPathTest ); + CPPUNIT_TEST( testUnknown ); + CPPUNIT_TEST( testRoot ); + CPPUNIT_TEST( testAbsolute ); + CPPUNIT_TEST( testRelative ); + + CPPUNIT_TEST( testValid ); + CPPUNIT_TEST( testInvalid ); + + CPPUNIT_TEST( testDotDirs ); + CPPUNIT_TEST( testDotInNames ); + CPPUNIT_TEST( testLeadingDots ); + + CPPUNIT_TEST( testTrailingSlash ); + CPPUNIT_TEST( testExtraSlashes ); + + CPPUNIT_TEST( testEquals ); + CPPUNIT_TEST( testToString ); + CPPUNIT_TEST( testAppend ); + CPPUNIT_TEST( testNormalize ); + CPPUNIT_TEST_SUITE_END( ); + +public: + void testUnknown( ); + void testRoot( ); + void testAbsolute( ); + void testRelative( ); + + void testValid( ); + void testInvalid( ); + + void testDotDirs( ); + void testDotInNames( ); + void testLeadingDots( ); + + void testTrailingSlash( ); + void testExtraSlashes( ); + + void testEquals( ); + void testToString( ); + void testAppend( ); + void testNormalize( ); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( VFSPathTest ); + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testUnknown( ) +{ + { + T_VFSPath path; + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::UNKNOWN ); + CPPUNIT_ASSERT( path.elements( ) == 0 ); + } + { + T_VFSPath path( "" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::UNKNOWN ); + CPPUNIT_ASSERT( path.elements( ) == 0 ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testRoot( ) +{ + T_VFSPath path( "/" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE ); + CPPUNIT_ASSERT( path.elements( ) == 0 ); +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testAbsolute( ) +{ + T_VFSPath path( "/this/is/a/test" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE ); + CPPUNIT_ASSERT( path.elements( ) == 4 ); + CPPUNIT_ASSERT( path[ 0 ] == "this" ); + CPPUNIT_ASSERT( path[ 1 ] == "is" ); + CPPUNIT_ASSERT( path[ 2 ] == "a" ); + CPPUNIT_ASSERT( path[ 3 ] == "test" ); +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testRelative( ) +{ + T_VFSPath path( "this/is/a/test" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE ); + CPPUNIT_ASSERT( path.elements( ) == 4 ); + CPPUNIT_ASSERT( path[ 0 ] == "this" ); + CPPUNIT_ASSERT( path[ 1 ] == "is" ); + CPPUNIT_ASSERT( path[ 2 ] == "a" ); + CPPUNIT_ASSERT( path[ 3 ] == "test" ); +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testValid( ) +{ + char const* const NAMES[] = { + "test" , "123" , "test-123" , "-test-123-" , "test_123" , "_" , "-" + }; + + for ( size_t i = 0 ; i < sizeof( NAMES ) / sizeof( NAMES[ 0 ] ) ; i ++ ) { + T_VFSPath path( NAMES[ i ] ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testInvalid( ) +{ + char const* const NAMES[] = { + "bad name" , "bad\\name" , "bad\u00e9name" , "bad\x09name" , + "BadName" + }; + + for ( size_t i = 0 ; i < sizeof( NAMES ) / sizeof( NAMES[ 0 ] ) ; i ++ ) { + T_VFSPath path( NAMES[ i ] ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::INVALID ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testDotDirs( ) +{ + { + T_VFSPath path( "./.././test" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE ); + CPPUNIT_ASSERT( path.elements( ) == 4 ); + CPPUNIT_ASSERT( path[ 0 ] == "." ); + CPPUNIT_ASSERT( path[ 1 ] == ".." ); + CPPUNIT_ASSERT( path[ 2 ] == "." ); + CPPUNIT_ASSERT( path[ 3 ] == "test" ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testDotInNames( ) +{ + { + T_VFSPath path( "test.test/test...test/test." ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::RELATIVE ); + CPPUNIT_ASSERT( path.elements( ) == 3 ); + CPPUNIT_ASSERT( path[ 0 ] == "test.test" ); + CPPUNIT_ASSERT( path[ 1 ] == "test...test" ); + CPPUNIT_ASSERT( path[ 2 ] == "test." ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testLeadingDots( ) +{ + { + T_VFSPath path( ".test" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::INVALID ); + CPPUNIT_ASSERT( path.elements( ) == 1 ); + CPPUNIT_ASSERT( path[ 0 ] == ".test" ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testTrailingSlash( ) +{ + T_VFSPath path( "/this/is/a/test/" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE ); + CPPUNIT_ASSERT( path.elements( ) == 4 ); + CPPUNIT_ASSERT( path[ 0 ] == "this" ); + CPPUNIT_ASSERT( path[ 1 ] == "is" ); + CPPUNIT_ASSERT( path[ 2 ] == "a" ); + CPPUNIT_ASSERT( path[ 3 ] == "test" ); +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testExtraSlashes( ) +{ + T_VFSPath path( "///this////is//a////test///" ); + CPPUNIT_ASSERT( path.type( ) == E_VFSPathType::ABSOLUTE ); + CPPUNIT_ASSERT( path.elements( ) == 4 ); + CPPUNIT_ASSERT( path[ 0 ] == "this" ); + CPPUNIT_ASSERT( path[ 1 ] == "is" ); + CPPUNIT_ASSERT( path[ 2 ] == "a" ); + CPPUNIT_ASSERT( path[ 3 ] == "test" ); +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testEquals( ) +{ + const T_VFSPath PATHS[] = { + {} , ".bad" , "test" , "/test" , "this/is/a/test" + }; + const auto n( sizeof( PATHS ) / sizeof( PATHS[ 0 ] ) ); + + T_VFSPath copies[ n ]; + for ( size_t i = 0 ; i < n ; i ++ ) { + copies[ i ] = PATHS[ i ]; + } + + for ( size_t i = 0 ; i < n ; i ++ ) { + for ( size_t j = 0 ; j < n ; j ++ ) { + if ( i == j ) { + CPPUNIT_ASSERT( PATHS[ i ] == PATHS[ j ] ); + CPPUNIT_ASSERT( PATHS[ i ] == copies[ j ] ); + } else { + CPPUNIT_ASSERT( PATHS[ i ] != PATHS[ j ] ); + CPPUNIT_ASSERT( PATHS[ i ] != copies[ j ] ); + } + } + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testToString( ) +{ + char const* const PATHS[] = { + "/test" , "test" , "test/test" , "/test/test" , "" , ".bad" + }; + for ( size_t i = 0 ; i < sizeof( PATHS ) / sizeof( PATHS[ 0 ] ) ; i ++ ) { + T_VFSPath path( PATHS[ i ] ); + CPPUNIT_ASSERT( T_String( path ) == PATHS[ i ] ); + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testAppend( ) +{ + const T_VFSPath paths[] = { + "/test" , "test" , ".bad" , {} + }; + const E_VFSPathType combos[] = { + // Parent is absolute + E_VFSPathType::ABSOLUTE , E_VFSPathType::ABSOLUTE , + E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN , + // Parent is relative + E_VFSPathType::RELATIVE , E_VFSPathType::RELATIVE , + E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN , + // Parent is invalid + E_VFSPathType::INVALID , E_VFSPathType::INVALID , + E_VFSPathType::INVALID , E_VFSPathType::UNKNOWN , + // Parent is unknown + E_VFSPathType::UNKNOWN , E_VFSPathType::UNKNOWN , + E_VFSPathType::UNKNOWN , E_VFSPathType::UNKNOWN + }; + + int cid = 0; + for ( int i = 0 ; i < 4 ; i ++ ) { + for ( int j = 0 ; j < 4 ; j ++ , cid ++ ) { + T_VFSPath parent( paths[ i ] ) , child( paths[ j ] ); + T_VFSPath path( parent , child ); + //printf( "i = %d j = %d t = %d\n" , i , j , int( path.type( ) ) ); + CPPUNIT_ASSERT( path.type( ) == combos[ cid ] ); + if ( combos[ cid ] == E_VFSPathType::UNKNOWN ) { + CPPUNIT_ASSERT( path.elements( ) == 0 ); + } else { + CPPUNIT_ASSERT( path.elements( ) == parent.elements( ) + child.elements( ) ); + CPPUNIT_ASSERT( path[ 0 ] == parent[ 0 ] ); + CPPUNIT_ASSERT( path[ 1 ] == child[ 0 ] ); + } + } + } +} + +/*----------------------------------------------------------------------------*/ + +void VFSPathTest::testNormalize( ) +{ + const T_VFSPath INPUTS[] = { + {} , ".bad" , "/test" , "test" , + "./test" , + "/." , + "/.." , + "." , + ".." , + "wat/../wet/../wut" , + "wat/../.." , + "wat/.." , + "/wat/../.." + }; + const T_VFSPath EXPECTED[] = { + {} , {} , "/test" , "test" , + "test" , + "/" , + "/" , + "." , + ".." , + "wut" , + ".." , + "." , + "/" + }; + static_assert( sizeof( INPUTS ) / sizeof( INPUTS[ 0 ] ) + == sizeof( EXPECTED ) / sizeof( EXPECTED[ 0 ] ) , + "yo, your test sucks!" ); + + for ( size_t i = 0 ; i < sizeof( INPUTS ) / sizeof( INPUTS[ 0 ] ) ; i ++ ) { + CPPUNIT_ASSERT( T_String( INPUTS[ i ].normalize( ) ) == T_String( EXPECTED[ i ] ) ); + } +}