diff --git a/.github/workflows/publish_PyPi.yml b/.github/workflows/publish_PyPi.yml index 01f57230b..fd90a39db 100644 --- a/.github/workflows/publish_PyPi.yml +++ b/.github/workflows/publish_PyPi.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install wheel + pip install wheel setuptools make python ls src/python echo "-------" @@ -35,17 +35,17 @@ jobs: - name: Build package working-directory: ./src/python/ run: | - python setup.py bdist_wheel --plat-name=manylinux1_x86_64 + python setup.py bdist_wheel --plat-name=manylinux_2_17_x86_64 - name: Publish package uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 with: packages_dir: ./src/python/dist user: __token__ password: ${{ secrets.PYPI_API_TOKEN_2022 }} - # password: ${{ secrets.TEST_PYPI_API_TOKEN }} - # repository_url: https://test.pypi.org/legacy/ - - deploy_windows: + #password: ${{ secrets.TEST_PYPI_API_TOKEN }} + #repository_url: https://test.pypi.org/legacy/ + + deploy_wsl: runs-on: windows-2022 steps: @@ -70,18 +70,19 @@ jobs: export PATH="/root/.local/bin:$PATH" python3 -m pip install --upgrade pip python3 -m pip install wheel twine + python3 -m pip install --upgrade setuptools make python ls src/python echo "-------" ls src/python/code/gnoll/ - name: Build package + shell: wsl-bash {0} working-directory: ./src/python/ run: | - touch ~/.pypirc - export PATH="/root/.local/bin:$PATH" - python3 setup.py bdist_wheel + # touch ~/.pypirc + python3 setup.py bdist_wheel --plat-name=manylinux1_x86_64 python3 -m twine upload dist/* --non-interactive -u __token__ -p ${{ secrets.PYPI_API_TOKEN_2022 }} -# python3 -m twine upload dist/* --non-interactive -u __token__ -p ${{ secrets.TEST_PYPI_API_TOKEN }} -r https://test.pypi.org/legacy/ + # python3 -m twine upload dist/* --non-interactive -u __token__ -p ${{ secrets.TEST_PYPI_API_TOKEN }} --repository-url https://test.pypi.org/legacy/ deploy_mac: runs-on: macos-12 @@ -97,7 +98,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install wheel twine + pip install wheel twine setuptools make python ls src/python echo "-------" diff --git a/.github/workflows/test_OS.yml b/.github/workflows/test_OS.yml index a1a31ead3..33a96e974 100644 --- a/.github/workflows/test_OS.yml +++ b/.github/workflows/test_OS.yml @@ -26,6 +26,8 @@ jobs: run: | make all ./build/dice d20 + make install + dice d100 build_win: diff --git a/.github/workflows/test_core.yml b/.github/workflows/test_core.yml index ffbee586f..cf2c4292e 100644 --- a/.github/workflows/test_core.yml +++ b/.github/workflows/test_core.yml @@ -30,3 +30,8 @@ jobs: make test USE_SECURE_RANDOM=1 - name: Lex/Yacc Fallback run: make test LEX_FALLBACK=1 YACC_FALLBACK=1 + - name: Test installation + run: | + make install + dice --version + dice --help diff --git a/.github/workflows/test_language_bindings.yml b/.github/workflows/test_language_bindings.yml index f50ceb42d..22e52f029 100644 --- a/.github/workflows/test_language_bindings.yml +++ b/.github/workflows/test_language_bindings.yml @@ -83,14 +83,8 @@ jobs: run: | sudo apt update sudo apt install cabal-install haskell-stack ghc - # curl -O https://downloads.haskell.org/~ghc/8.10.7/ghc-8.10.7-x86_64-deb10-linux.tar.xz - # tar xvf ghc-8.10.7-x86_64-deb10-linux.tar.xz - # cd ghc-8.10.7 && ./configure - # cd ghc-8.10.7 && make install ghc --version cabal update - # stack init - # stack --resolver lts-18 build - name: make haskell run: | make haskell @@ -124,14 +118,26 @@ jobs: version: "10.0" - name: python deps run: python3 -m pip install -r reqs/requirements.txt + - name: apt prerequisites + run: | + sudo apt install clang-14 lld-14 llvm-14 libjs-d3 python3-numpy + sudo apt install llvm -y - name: javascript prerequisites run: | - sudo apt install clang-14 lld-14 llvm-14 nodejs libjs-d3 python3-numpy - sudo apt install nodejs llvm -y - wget -c http://archive.ubuntu.com/ubuntu/pool/universe/e/emscripten/emscripten_3.1.6~dfsg-5_all.deb - sudo apt install ./emscripten_3.1.6~dfsg-5_all.deb + # NodeJS + curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + # Acorn-Node + git clone https://github.com/acornjs/acorn.git + cd acorn ; npm install ; cd - + # Emscripten + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk ; ./emsdk install latest ; cd - + # wget --no-verbose -c http://archive.ubuntu.com/ubuntu/pool/universe/e/emscripten/emscripten_3.1.6~dfsg-5_all.deb + # sudo apt install -f ./emscripten_3.1.6~dfsg-5_all.deb nodejs- node-acorn- # ignore nodejs acorn max version - name: make js run: | + cd emsdk ; ./emsdk activate latest ; source ./emsdk_env.sh ; cd - emcc -v make js node ./build/js/a.out.js 1d20 diff --git a/.github/workflows/test_packages.yml b/.github/workflows/test_packages.yml index 4821b4d4e..a4bf6b68d 100644 --- a/.github/workflows/test_packages.yml +++ b/.github/workflows/test_packages.yml @@ -19,9 +19,8 @@ jobs: - uses: actions/checkout@v2 - name: Test PyPi run: | - python3 -m pip install gnoll - # --index-url https://test.pypi.org/simple/ - python3 -c "from gnoll.parser import roll; roll('d34')" + python3 -m pip install gnoll # --index-url https://test.pypi.org/simple/ + python3 -c "from gnoll import roll; roll('d34')" pypi_windows: name: "PyPi: Windows" @@ -43,8 +42,8 @@ jobs: - name: Test Windows shell: wsl-bash {0} run: | - python3 -m pip install gnoll --index-url https://test.pypi.org/simple/ - python3 -c "from gnoll.parser import roll; roll('d34')" + python3 -m pip install gnoll # --index-url https://test.pypi.org/simple/ + python3 -c "from gnoll import roll; roll('d34')" pypi_mac: name: "PyPi: MacOS" @@ -54,7 +53,6 @@ jobs: - name: Build run: make all - name: Test - run: | - python3 -m pip install gnoll - # --index-url https://test.pypi.org/simple/ - python3 -c "from gnoll.parser import roll; roll('d34')" + run: | + python3 -m pip install gnoll # --index-url https://test.pypi.org/simple/ + python3 -c "from gnoll import roll; roll('d34')" diff --git a/CITATION.cff b/CITATION.cff index 069b1fa7f..2c731de45 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,22 +1,41 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + cff-version: 1.2.0 -message: "If you use this software, please cite it as below." +title: >- + GNOLL: Efficient Multi-Lingual Software for Real-World + Dice Notation and Extensions +message: 'If you use this software, please cite it using the following metadata' +type: software authors: -- family-names: "Hunter" - given-names: "Ian Frederick Vigogne Goodbody" - orcid: "https://orcid.org/0000-0003-3408-8138" -title: "GNOLL" -version: 1.0.0 -date-released: 2022-05-30 -url: "https://github.com/ianfhunter/GNOLL" -preferred-citation: - type: unpublished - authors: - - family-names: "Hunter" - given-names: "Ian Frederick Vigogne Goodbody" - orcid: "https://orcid.org/0000-0003-3408-8138" - year: 2022 - month: 5 - start: 1 # First page number - end: 11 # Last page number - title: "GNOLL: Efficient Software for Real-World Dice Notation and Extensions" - + - given-names: Ian Frederick Vigogne Goodbody + family-names: Hunter + email: ianfhunter@gmail.com + name-particle: Ian + orcid: 'https://orcid.org/0000-0003-3408-8138' +identifiers: + - type: doi + value: 10.21105/joss.04816 + description: JOSS Paper +repository-code: 'https://github.com/ianfhunter/GNOLL' +url: 'https://www.ianhunter.ie/GNOLL/' +abstract: >- + GNOLL is an open-source library for parsing commonly used + dice notations in gaming system + + research and/or software development. GNOLL is performant, + supports most popular and obscure + + notations, has permissive licensing, and is integratable + into many other systems due to it being + + written in C +keywords: + - dice notation + - dice + - board games + - tabletop roleplaying games +license: GPL-3.0 +commit: 3bd126678e4c87b652eabe535701faec8d0fab04 +version: v4.3.2 +date-released: '2023-01-17' diff --git a/GNOLL.ini b/GNOLL.ini index 1d7db1ebf..9e6688cf2 100644 --- a/GNOLL.ini +++ b/GNOLL.ini @@ -1,4 +1,4 @@ ; Placeholder file for future configuration setting. Currently unused [Meta Information] -version = v3.2.0 +version = v4.4.0 ttrpg_compatibility_rate = 98.66 diff --git a/Makefile b/Makefile index 2b0ac0fbd..9e23511d4 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else STANDARD= -std=c99 endif - +.DEFAULT_GOAL := all OPT=-O3 \ $(STANDARD) -Wall -Wextra -Werror -pedantic -Wcast-align \ @@ -114,6 +114,10 @@ CFILE_SUBDIRS=rolls util operations external all: clean yacc lex compile shared echo "== Build Complete ==" +install: all + mkdir -p /usr/local/bin/ + cp build/dice /usr/local/bin/dice + yacc: mkdir -p build $(foreach BD,$(CFILE_SUBDIRS),mkdir -p build/$(BD)) diff --git a/README.md b/README.md index b00248a04..98f96fbab 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ![GitHub last commit](https://img.shields.io/github/last-commit/ianfhunter/GNOLL.svg) [![Donate](https://img.shields.io/badge/Donate-Paypal-yellow.svg)](https://paypal.me/ianfhunter)

- +

An easy to integrate [dice notation](https://en.wikipedia.org/wiki/Dice_notation) library for multiple programming languages. @@ -77,9 +77,19 @@ pip3 install GNOLL Then, in your code: ```python -from gnoll.parser import roll +from gnoll import roll roll("1d20") ->> 7 +>> (0, [[12]], None) +# (return code, final result, dice breakdown (if enabled)) +``` + +Or, use the command-line interface (see `--help`): +```sh +$ python3 -m gnoll 2d4 +6 +$ function gnoll() { python3 -m gnoll --breakdown "$@" ; } +$ gnoll 3d6 + 10 +[5, 5, 4] --> 24 ``` ### ๐Ÿ› ๏ธ Installing From Source @@ -97,9 +107,11 @@ make test Or, just try some commands yourself! ```bash -$ ./dice 1d20 +$ ./build/dice 1d20 20 ``` +If you would like to run the 'dice' command from anywhere, use `make install` to add the executable to your path. + (Note that not all commands may not be able to be used this way as some symbols are reserved for use by different terminal interfaces (e.g. bash uses ! and #)) For languages other than Python/C/C++ call the corresponding make target after the commands above. diff --git a/doc/JOSS/paper.bib b/doc/JOSS/paper.bib index 4474e6b27..72024d8a1 100644 --- a/doc/JOSS/paper.bib +++ b/doc/JOSS/paper.bib @@ -5,7 +5,7 @@ @inproceedings{Mogensen2006DiceRollingMI } @misc{DNDDemographics2019, - title={a History Check on 2019 | D\&D's best year ever\_2019}, + title={a History Check on 2019 | {D}\&{D}'s best year ever\_2019}, year={2020}, author={{Natalt Design}}, publisher={Wizards of The Coast} @@ -32,7 +32,7 @@ @inproceedings{TROLL language = "English", isbn = "978-1-60558-166-8", pages = "1910--1915", - booktitle = "Proceedings of the 2009 ACM symposium on Applied Computing", + booktitle = "Proceedings of the 2009 ACM Symposium on Applied Computing", publisher = "Association for Computing Machinery", note = "null ; Conference date: 09-03-2009 Through 12-03-2009", } @@ -44,3 +44,11 @@ @online{pypi year={2021}, publisher={Python Software Foundation} } +@techreport{pep301, + author = {Richard Jones}, + title = {Package Index and Metadata for Distutils}, + year = {2002}, + type = {PEP}, + number = {301}, + url = {https://www.python.org/dev/peps/pep-0301/}, +} diff --git a/doc/JOSS/paper.md b/doc/JOSS/paper.md index bc8daf8a2..814f7b901 100644 --- a/doc/JOSS/paper.md +++ b/doc/JOSS/paper.md @@ -26,37 +26,34 @@ bibliography: paper.bib # Summary -Dice Notation is a system for describing how to roll collections of dice. It is often used to assist understanding of rules of games - particularly tabletop roleplaying games (TTRPGs). Existing research software in this space has been primarily designed for other researchers and statisticians despite the fact that a large population of those actually playing these games are young [@DNDDemographics2019] or not involved in statistical research. +Dice Notation is a system for describing how to roll collections of dice. It is often used to assist in understanding the rules of games - particularly tabletop roleplaying games (TTRPGs). Existing research software in this space has been primarily designed for other researchers and statisticians despite the fact that a large population of those actually playing these games are young [@DNDDemographics2019] or not involved in statistical research. -`GNOLL` is an open-source library for parsing commonly used dice notations - for use in gaming system research and/or software development. `GNOLL` is performant, supports most popular and obscure notations, has permissive licensing and is integratable into many other systems due to it being written in C. At present, the repository has working examples of integration into 13 different programming languages such as Go, Java, Julia, Python, Perl and R. +`GNOLL` is an open-source library for parsing commonly used dice notations in gaming system research and/or software development. `GNOLL` is performant, supports most popular and obscure notations, has permissive licensing, and is integratable into many other systems due to it being written in C. At present, the repository has working examples of integration into 13 different programming languages, such as Go, Java, Julia, Python, Perl and R. # Statement of Need -Whilst there are several dice rolling utilities on the market for research/commercial use there is no current solution which: +While there are several dice-rolling utilities on the market for research/commercial use, there is no current solution that: - is open source / permissively licensed. - can be easily integrated into other software despite language differences. -- provides reference for other implementations. +- provides a reference for other implementations. - supports such a diverse set of dice notation. While some solutions may offer one or two of these points, GNOLL addresses all of them. -Without a larger project for reference/integration, many software developers have created their own dice notation parsers (for a simple subset is not too difficult to develop). These are usually sufficient for their immediate needs, but often create discrepencies in notation standards and do little to change this path for future developers. +Without a more extensive project for reference/integration, many software developers have created their own dice notation parsers (because a parser for a simple subset is not too difficult to develop). These are usually sufficient for their immediate needs but often create discrepancies in notation standards and do little to change this path for future developers. # Related Reading -There are not many publications that specifically discuss dice notation. The most prominent papers are named `ROLL`[@ROLL] and `TROLL` [@TROLL]. To distinguish this research and still pay homage to the original work, GNOLL is a recursive acronym expanded from "GNOLL's Not *OLL". The reason for its distinction is that `GNOLL`'s notation is focused on real-world gaming usage of dice notation, whereas `ROLL` and `TROLL` are notations targetted at statistics research. +Few publications specifically discuss dice notation. The most prominent papers are named `ROLL` [@ROLL] and `TROLL` [@TROLL]. GNOLL is a recursive acronym expanded from "GNOLL's Not *OLL" to distinguish this research and still pay homage to the original work. The reason for its distinction is that `GNOLL`'s notation is focused on real-world gaming usage of dice notation, whereas `ROLL` and `TROLL` are notations targeted at statistics research. # Example Notation -There are too many different operations and combinations of dice notation to fit into the space constraints of this paper, but are discussed at length in the project's [documentation](https://ianhunter.ie/GNOLL/), including rationale for each of the notation choices. A sample of the base dice notation has been described below. - +There are too many different operations and combinations of dice notation to describe within the space constraints of this paper but they are discussed at length in the project's [documentation](https://ianhunter.ie/GNOLL/), including the rationale for each of the notation choices. We describe a sample of the base dice notation below. The most basic dice roll in dice notation can be expressed as $$ x\textbf{d}y,\ where\ x,y\ \in{} \mathbb{Z}^{+}. $$ `x` dice are rolled with values from 1 to `y`. Where `x` is not specified, it is assumed that its value is 1. the exclusion of `y` produces an error (It is ambiguous to have a dice with no sides). # Performance -`GNOLL` performs well against other dice rolling libraries available online both in terms of performance and functional coverage. In the Figures section below we show some simple benchmarking results against the `TROLL` system and a C++ parser and also test `GNOLL`'s Python interface against popular python packages and find that `GNOLL` is generally more performant. - -# Citations +`GNOLL` performs well against other dice-rolling libraries available online both in terms of performance and functional coverage. In the Figures section below, we show some simple benchmarking results against the `TROLL` system and a C++ parser and also test `GNOLL`'s Python interface against popular Python packages, and find that `GNOLL` is generally more performant. # Figures ## Figure 1 - GNOLL Performance (C) @@ -67,9 +64,9 @@ Comparison of `GNOLL`'s performance against other C/C++/SmallTalk dice notation ## Figure 2 - GNOLL Performance (Python) -Comparison of `GNOLL`'s performance against other Python dice notation parsers. (Chosen from popular dice rollers on PyPi[@pypi] - ["Dice"](https://pypi.org/project/dice/), ["RPG Dice"](https://pypi.org/project/rpg-dice/), ["Python Dice"](https://pypi.org/project/python-dice/), ["d20"](https://pypi.org/project/d20/)). Unplotted points either were non-functional or exceeded a set timeout. +Comparison of `GNOLL`'s performance against other Python dice notation parsers. (Chosen from popular dice rollers on PyPi [@pep301] - ["Dice"](https://pypi.org/project/dice/), ["RPG Dice"](https://pypi.org/project/rpg-dice/), ["Python Dice"](https://pypi.org/project/python-dice/), ["d20"](https://pypi.org/project/d20/)). Unplotted points either were non-functional or exceeded a set timeout. -![A graph showing GNOLL's performance (via Python binding) on different sizes of dice rolls. It slightly underperforms for small sizes, but is faster than other python libraries for large sizes.](py.PNG) +![A graph showing GNOLL's performance (via Python binding) on different sizes of dice rolls. It slightly underperforms for small sizes, but is faster than other Python libraries for large sizes.](py.PNG) # Acknowledgments Thanks to my dog for the desk-side companionship and the demand for healthy stick-fetching breaks. diff --git a/doc/gh-pages/developers/FAQ.md b/doc/gh-pages/developers/FAQ.md index 1542ebcae..899059e4e 100644 --- a/doc/gh-pages/developers/FAQ.md +++ b/doc/gh-pages/developers/FAQ.md @@ -17,21 +17,46 @@ GNOLL does not support special characters natively, due to limitations of depend {: .question } > How can I use GNOLL to draw cards out of a deck? -GNOLL does not support tracking the state of internal counters between executions and/or statements, so cannot remove cards from a pool. We recommend doing passing reduced options to GNOLL from your script as cards/dice sides are revealed. +GNOLL does not support tracking the state of internal counters between executions and/or statements, so cannot remove cards from a pool. We recommend passing reduced options to GNOLL from your script as cards/dice sides are revealed. + +e.g. +> roll("d{TEN, JACK, QUEEN, KING, ACE}") +> result: JACK +> +> roll("d{TEN, QUEEN, KING, ACE}") +> result: QUEEN {: .question } > GNOLL uses a slightly different syntax than what I would like -While we have put a lot of thought into GNOLL's choices of characters, You may change which characters are used for operations in the 'dice.lex' file. +While we have put a lot of thought into GNOLL's choices of characters, You may change which characters are used for operations in the [dice.lex](https://github.com/ianfhunter/GNOLL/blob/main/src/grammar/dice.lex) file. + +For example, to allow using the full word 'reroll' instead of the shorthand 'r' you would make this change: +```lex +// old +[r] { + return(REROLL); +} +// new +[r|reroll] { + return(REROLL); +} +``` +Note you will need to manage conflicts in grammar yourself. (e.g. if you wanted to use the 'x' symbol for multiplication, you would need to also find a new symbol for the `REPEAT` token. +For more complicated statements you will need to become familiar with [Regular Expressions](https://en.m.wikipedia.org/wiki/Regular_expression) + {: .question } > GNOLL produces an overall result, but I need to know what each dice value was! This is possible! Just enable introspection or "dice breakdown" and you'll get the individual results as well as the final ones. +This can be enabled via a parameter to the [main GNOLL roll function](https://www.ianhunter.ie/GNOLL/developers/installation.html). + + {: .question } > I wish to cite GNOLL. What is the most appropriate item to use? -Please cite one of our publications. +Please cite one of our publications. Feel free to [raise an issue](https://github.com/ianfhunter/GNOLL/issues) if you are not sure. - [GNOLL: Efficient Multi-Lingual Software for Real-World Dice Notation and Extensions](https://joss.theoj.org/papers/c704c5148e622d32403948320c5e96a1) - [Application of the Central Limit Theorem to dice notation parsing](https://beta.briefideas.org/ideas/fc25de499b44d47685188df4d09e144f) diff --git a/doc/gh-pages/developers/installation.md b/doc/gh-pages/developers/installation.md index e22c726d4..62b200ed9 100644 --- a/doc/gh-pages/developers/installation.md +++ b/doc/gh-pages/developers/installation.md @@ -6,7 +6,48 @@ nav_order: 0 --- # Setup -## OS Information + +## Rolling with GNOLL +### The core function + +Each language binding example below will call the `roll_full_options` function, though some older examples may call deprecated functions which call that function internally. +If you are creating your own binding to GNOLL, use this function + +```c +int roll_full_options( + char* roll_request, + char* log_file, + int enable_verbosity, + int enable_introspection, + int enable_mocking, + int enable_builtins, + int mocking_type, + int mocking_seed +); +``` +The first parameter is the dice notation to be understood and computed. The result of which will be written into a file in the location `log_file`. +Then follows various optional values, which you should set to False (0) unless you wish to take advantage of the feature. + +- `verbosity` is mostly useful for debugging a dice notation statement or for information during development. It will cause the execution to be slightly slower and is not recommended for release environments. +- `introspection` provides a per-dice breakdown of values rolled during the dice notation parsing. This is useful if you wish to display individual dice results and not just the final total. These values (if enabled) are added to the output file before the total. +- `mocking` is a feature allowing developers to generate predictable dice rolls for reproducible tests. This should only ever be used in a testing scenario. +- `builtins` are a collection of predefined [Macros](bad link) provided by GNOLL for ease of use. This comes with a performance trade-off of loading these macros prior to the given dice notation. + +`mocking_type` is [an enum defining the behaviour of the Mocking logic](https://github.com/ianfhunter/GNOLL/blob/22b2f9248417cb756818cb5850dc20c4f77fde0e/src/grammar/util/mocking.h#L6). The `mocking_seed` provides the initial value to this logic, whether it be a random seed or an initialization of a predictably modified variable. + +The return value of this function is one of the defined GNOLL [error codes](developers/errors.html). + +### The output file + +The output of GNOLL is a file which can be injected by a subsequent program, wether that be within the language binding itself to bind to more suitable structures or direct usage in downstream applications. +It is recommended that developers using GNOLL delete output files after they no longer need them + +The file consists of two parts: + +- Dice introspection results (if enabled). The value for different dice are separated by commas and grouped by newlines (e.g. 3d6+2d6 would have 3 comma-seperated value on the first line, 2 comma-seperated value on the second line, followed by the final result) +- The final result of the dice notation, where discrete results are separated by a semicolon (e.g. 5d6;d6) + +## OS Support | OS | Version | Tested (From Source) | Tested (PyPi) | | -- | ------- | -------------------- | ------------- | @@ -15,81 +56,177 @@ nav_order: 0 | Windows | Win11 | No | No | | MacOS | 12 | Yes | Yes | -## Common Pre-requisites +## Common System Pre-requisites ```bash sudo apt-get install bison flex make python3-pip -y ``` +## Commandline Installation + +GNOLL can be installed into your user path for convenience. +This will allow parsing of dice notation from the command line. + +```bash +make install +dice "1d20" +``` + +**Note:** Some command line syntax may conflict with tokens used in Dice Notation (e.g. >). Please escape or quote your notation to avoid this issue. + +There is at present no capacity for enabling or disabling optional features via the command line app. See Issue [#431](https://github.com/ianfhunter/GNOLL/issues/431). All options are disabled apart from builtins which are available for use. + ## Language Bindings -We have tested several language bindings to GNOLL. -The intention is not to be exhaustively compatible with every version, but a useful reference to help you set up GNOLL for your own software. +We have tested binding GNOLL for use in several programming languages. + +The following documentation intention is not to be exhaustively compatible with every revision of a programming language (as that is a large overhead to maintain), but to be used as a helpful reference for setting up GNOLL for your own software. ### C -This is the default build target. -Tested with GCC and Clang Compilers and is C99 compliant. +C is the default build target and what GNOLL is written in. + +Tested with GCC and Clang Compilers and C99 compliant. + +- Your application's build scripts should be modified to link against the shared object created with `make all` and to include the path to the folder containing `shared_header.h` +- In your C code, include the "shared_header.h" file and the `roll_full_options` function will be available to use as described above. +To make the example C application: ```bash make all -./a.out "1d20 +./build/dice "1d20" +> Result: 14; ``` +The sourcecode for the C application is in the [grammar folder under src](https://github.com/ianfhunter/GNOLL/tree/main/src/grammar). It contains the core GNOLL logic as well as the application logic. + ### C++ -Tested with Clang Compiler. +Tested with Clang Compiler, Ubuntu 22.04. + +- Your application's build scripts should be modified to link against the shared object created with `make all` and to include the path to the folder containing `shared_header.h` +- In your C++ code, include the "shared_header.h" file and the `roll_full_options` function will be available to use as described above. +[An example application](https://github.com/ianfhunter/GNOLL/tree/main/src/C%2B%2B) is available (hardcoded to roll a d20): ```bash make cpp +... +> 19 ``` ### CSharp Tested with Mono Compiler, Ubuntu 22.04 + +The [C# example](https://github.com/ianfhunter/GNOLL/tree/main/src/CSharp) expects that the shared object libdice.so is generated under `build/`. +The `build/` directory may need to be added to the environment variable `LD_LIBRARY_PATH`. + +The library is imported and the `char *`s of the C function are managed to consume C# Strings ([via marshalling Unmanaged LPStrs](https://github.com/ianfhunter/GNOLL/blob/22b2f9248417cb756818cb5850dc20c4f77fde0e/src/CSharp/main.cs#L7)) + +The example application creates a RollWithGNOLL function which handles all the file parsing. The application calls this function with a hardcoded '1d20'. + ```bash make cs +... +> 4 +``` + +Function example: +```cs +RollWithGNOLL("1d20") +... +5 ``` ### Go Tested with Golang 1.18, Ubuntu 22.04 + +The Go setup is very similar to the C# example, in that we must ensure the build directory has the libdice.so file and that the build directory is in `LD_LIBRARY_PATH`. +Apart from this, the steps to execute your go application (e.g. `go build` and `go run`) should remain unchanged. +The application is hardcoded to parse a d20 roll. ```bash make go +... +> 17 ``` +The code itself creates "CStrings" which the developer must be careful to free after use. + ### Haskell Tested with ghc 9.4.3, cabal 3.0.0.0-3build1.1, Ubuntu 22.04 + +In [this example](https://github.com/ianfhunter/GNOLL/tree/main/src/haskell), libdice.so is installed to /usr/lib/ and the cabal build configuration points to it's location via the 'extra-libraries' field. +In the main application, GNOLL is imported via the Foreign Function Interface (`foreign import`). You must use the CStrings structures rather than native haskell strings. + ``` make haskell +cabal run src [dice roll e.g. d20] ``` ### Python Available from [PyPi](https://pypi.org/project/gnoll/) Tested with Python3.10, Ubuntu 22.04 + +Install via Pip: ```bash pip install gnoll ``` +Then you can import the `roll` function and the custom Exceptions `GNOLLExceptions` which the function can raise. +```python +from gnoll import roll +roll("2d100") +> (0, [[108]] ,None) +``` +The roll function takes the form: +```python +def roll(s, + verbose=False, + mock=None, + mock_const=3, + breakdown=False, + builtins=False): +``` +Where `s` is the string containing the dice notation and the other optional fields correspond with the optional features as mentioned above. +File management is handled internally and is hidden from the caller. + +Exceptions are all of the Exception type `GNOLLException` and follow the [list of errors](developers/errors.html) + If you are running from sourcecode: ```bash make python ``` +will build the application to expose the same interface. ### Perl Tested on Perl 5.30, Ubuntu 20.04 + +The [Perl example](https://github.com/ianfhunter/GNOLL/tree/main/src/perl) uses the SWIG framework to create its bindings. +The Perl libraries must be linked at build time ($PERL_VERSION can be set to configure this (default: 5.30)). +GNOLL's notation parsing is then exposed via a `gnoll::roll_and_write` function (a wrapper around roll_full_options which has all options set to False). + ```bash make perl +... +> 20 ``` -To make for another version, $PERL_VERSION must be set (default: 5.30) + ### Java Tested with openjdk-8, Ubuntu 22.04 + +Java, like Perl, must have GNOLL compiled with its language headers. + +This can be done with the make command: ```bash make java ``` +Afterwards, GNOLL'S functionality is available via a `DiceNotationParser.roll` function which takes a dice notation string and a filename and performs as usual. + ### Julia Tested on Ubuntu 20.04, Julia 1.4.1 -Available from [JuliaHub](https://juliahub.com/ui/Packages/GnollDiceNotation/WetJc/) +Whether installing from [JuliaHub](https://juliahub.com/ui/Packages/GnollDiceNotation/WetJc/) or from source with ``` make julia ``` +The function `GnollDiceNotation.roll` is available to use, which simply takes in a dice notation string. ### JavaScript Setup @@ -112,17 +249,50 @@ make javascript node a.out.js ``` +As the c code is being compiled to JavaScript, it operates in the same way + ### PHP +Tested on PHP 8.1.13, Ubuntu 22.04 + +As long as the shared object is in LD_LIBRARY_PATH, you can use the functions from c with PHP's cdef +``` +FFI::cdef( + "int roll_and_write(char * roll, char *fn );", + "libdice.so" +); +``` + +The [PHP Example] (https://github.com/ianfhunter/GNOLL/blob/main/src/PHP/index.php) parses a hardcoded 3d6 roll. ```bash make php ``` ### R +Tested on R 4.2.2, Ubuntu 22.04 + + +#### Setup +Generate the shared object file that R can consume. ```bash make r ``` +Inside `main.r` we show an example of GNOLL usage. + +- Load in the shared object +- Delete the temporary file that contains GNOLL output +- Call GNOLL +- Parse result + +#### Notes +This example uses the .C function which is usually not recommended. The recommended way to call C code in R is through the .Call() function. See [#368](https://github.com/ianfhunter/GNOLL/issues/368) + + ### Ruby +Tested on Ruby 3.0, Ubuntu 22.04 + +Using Ruby's native FFI code, the [demo application](https://github.com/ianfhunter/GNOLL/blob/main/src/ruby/main.rb) creates a namespaces function for usage. `DiceNotation.roll([dice notation string])` + ```bash make ruby ``` @@ -133,4 +303,4 @@ The swig bindings are already generated for you in `src/swig/gnoll.i`. Follow th Note: The current bindings do not return a result directly - You should read from the file that is generated. ### Shared Library -Many languages allow importing of code via shared object. You can find this .o file in the build folder after running `make all` +Many languages allow importing of code via shared object. You can find this .so file in the build folder after running `make all` diff --git a/doc/gh-pages/etc/supported_rpgs.md b/doc/gh-pages/etc/supported_rpgs.md index e81268cc8..0ba48cb4a 100644 --- a/doc/gh-pages/etc/supported_rpgs.md +++ b/doc/gh-pages/etc/supported_rpgs.md @@ -13,13 +13,6 @@ This is an analysis of all 'notable' TTRPG games on [Wikipedia](https://en.m.wik There are a lot of systems here, we cannot guarantee this list is free of error. -## Status - -This list is currently **Under Construction**. - -**Letters Done:** `0ABCDEFGHIJKOQUXYZ` - -**Remaining Letters:** `LMNPRSTVW` ## Legend @@ -300,7 +293,7 @@ This list is currently **Under Construction**. | Fifth Cycle | | ๐Ÿ”ผ| | Fighting Fantasy: The Introductory Role-Playing Game | |โœ”๏ธ | | Fireborn | | ๐Ÿ”ผ| -| Firefly | Cortex | ๐Ÿ”ผ| +| Firefly | Cortex | โœ”๏ธ | | Flash Gordon & the Warriors of Mongo | | ๐Ÿ”ผ| | Flashing Blades | | ๐Ÿ”ผ| | Forbidden Kingdoms | d20 |โœ”๏ธ | @@ -398,6 +391,101 @@ This list is currently **Under Construction**. | Kobolds Ate My Baby! | | โœ”๏ธ | | Krysztaly Czasu | d100 |โœ”๏ธ | | Kult | PBtA | โœ”๏ธ | +|Labyrinth Lord | d20 | โœ”๏ธ| +|Lace and Steel |Cards | ใ€ฐ๏ธ| +|Land of the Rising Sun | Chivalry & Sorcery | ๐Ÿ”ผ | +|Lands of Adventure | | ๐Ÿ”ผ| +|The Laundry | Basic Roleplaying| โœ”๏ธ| +|Legacy | | โ“ | +|Legend of the Five Rings | AD&D | โœ”๏ธ | +|Legends of Anglerre | FATE | โœ”๏ธ | +|Legionnaire | |โ“ | +|Lejendary Adventure | | ๐Ÿ”ผ | +|Leverage | Cortex+ System |โœ”๏ธ | +|Little Fears | d6 |โœ”๏ธ | +| Living Steel | Phoenix Command |โœ”๏ธ | +| Lone Wolf | d20 | ๐Ÿ”ผ | +| Lord of the Rings Adventure Game | 2d6 | โœ”๏ธ| +| The Lord of the Rings Roleplaying Game | Coda | โœ”๏ธ | +| Lords of Creation | |โœ”๏ธ | +| Mach: The First Colony | | โ“| +| Macho Women with Guns | | ๐Ÿ”ผ| +| Macross II | Megaversal | โœ”๏ธ | +| Maelstrom | Maelstrom | โ“ | +|Mage: The Ascension | Storyteller | โœ”๏ธ | +|Mage: The Awakening | Storyteller | โœ”๏ธ | +|Mage: The Sorcerer's Crusade | Storyteller | โœ”๏ธ | +|Malefices | | ๐Ÿ”ฝ | +|Man, Myth & Magic | | โ“ | +|Manhunter | Megaversal | โœ”๏ธ | +|Marvel Heroic Roleplaying |Cortex+ | โœ”๏ธ| +|Marvel Super Heroes Adventure Game | SAGA | ใ€ฐ๏ธ | +|Marvel Super Heroes | | โ“ | +|Marvel Universe Roleplaying Game | | ๐Ÿ”ผ* | +|Masterbook | | ๐Ÿ”ผ* | +|The Masters of the Universe | | โ“ | +|Mechamorphosis | d20 | โœ”๏ธ | +|Mechanical Dream | | โ“ | +|The Mechanoid Invasion | | โ“ | +|MechWarrior | |โ“ | +|MEGA Role-Playing System | |โ“ | +|Megaverse | | โ“ | +|Mekton | | ๐Ÿ”ผ | +|MelandMystery | | ๐Ÿ”ผ | +|Mena: Land of In Black | d6 | โœ”๏ธ | +|MERC | | โ“ | +|Merc 2000 | Twilight 2000 | โ“ | +|Mercenaries, Spies and Private Eyes | Tunnels & Trolls|๐Ÿ”ผ | +|The Metabarons Roleplaying Game | d6 | โœ”๏ธ | +|Metamorphosis Alpha | Amazing Engine, 3d6 | โœ”๏ธ | +|Michtim: Fluffy Adventures | | โœ”๏ธ| +|Middle Earth Role Play | Rolemaster |โœ”๏ธ| +|Midgard | |โœ”๏ธ| +|Midnight | D&D | โœ”๏ธ| +|Midnight at the Well of Souls | | โ“| +|Millennium's End | | โ“ | +|M.I.S.S.I.O.N. | | โ“ | +|Mistborn Adventure Game | d6 | โœ”๏ธ | +|Mojo | Polymancer | ใ€ฐ๏ธ | +|Monastyr | 3d20 | | +|Monsterhearts | PbtA| โœ”๏ธ | +|Monsters! Monsters! | d20 | โœ”๏ธ | +|Monsters and Other Childish Things | One-Roll Engine | โœ”๏ธ | +|Mordheim | | | +|Morpheus | | | +|The Morrow Project | | | +|Morton's List | | | +|Mouse Guard | The Burning Wheel | | +|Multiverser | | | +|Mummy: The Resurrection | WoD | โœ”๏ธ | +|Murphy's World | | | +|Mutant Target Games | | | +|Mutant Chronicles | | | +|Mutant City Blues | GUMSHOE | โœ”๏ธ | +|Mutant Future | | | +|Mutant RYMD | | | +|Mutant: Undergรฅngens Arvtagare | | | | | | +| Mutants & Masterminds | | | +| Mutazoids | | | +|My Life with Master | | | +|Myrskyn aika | | | +|Mythworld | | | +| Neighborhood | | | +| Nephilim | | | +| Neuroshima| | | +| Nexus: The Infinite City | | | +| Nicotine Girls | | | +| Nightbane Palladium Books | | | +| Nightlife Stellar Games | | | +| Night of the Ninja | | | +| Night's Black Agents | | | +| Night Wizard! | | | +| Ninja Burger | | | +| Ninjas and Superspies | | | +| Nobilis | | | +| Noctum | | | +| Northern Crown | d20 | โœ”๏ธ | +| Numenera | Cypher | โœ”๏ธ | | Odysseus | |โ“ | | The Official Superhero Adventure Game | |โ“ | | Omnigon | |โ“ | @@ -410,6 +498,180 @@ This list is currently **Under Construction**. | Other Suns | | โ“| | Outime | | โ“| | Over the Edge | | โœ”๏ธ | +| Palladium Fantasy Role-Playing Game | | | +| Pandemonium! | | | +| Pantheon | | | +| Paranoia | | | +| Pathfinder | | | +| Pax Draconis | | | +| Passion Play | | | +| Fading Suns | | | +| Pendragon (or King Arthur Pendragon) | | | +| A Penny for My Thoughts | | | +| Phantasia | | | +| Phantasy Conclave | | | +| Phase VII | | | +| Phoenix Command | | โœ”๏ธ | +| Pirates and Plunder | | | +| Polaris (1997) | | | +| Polaris | | | +| Powers and Perils | | | +| Praedor | | | +| The Price of Freedom West | | | +| The Primal Order | | | +| Prime Directive | | | +| Primetime Adventures | | | +| Prince Valiant: The Story-Telling Game | | | +| Privateers and Gentlemen | | | +| Project A-ko: The Roleplaying Game | | | +| Promethean: The Created | WoD | โœ”๏ธ | +| Psiworld Fantasy Games Unlimited | | | +| Puppetland | | | +| Rapture: The Second Coming | | | +| The Realm of Yolmi| | | +| Recon| | | +| Red Dragon | | | +| Reich Star | | | +| Rรชve: the Dream Ouroboros| | | +| The Riddle of Steel | | | +| Rifts Palladium Games | Crosssover | | +| Rifts Chaos Earth | | | +| Ringworld Chaosium | | | +| Risus | | | +| Robot Warriors | | | +| Robotech | | | +| Robotech: The Shadow Chronicles| | | +| Rogue Trader | | | +| RMSS | | | +| Rune | | | +| RuneQuest| | | +| RuneSlayers | | | +| SAGA System | SAGA | ใ€ฐ๏ธ | +| Sandman: Map of Halaal| | | +| Savage Worlds | | | +| Scion: Hero | | | +| Second Dawn| | | +| Sengoku | | | +| The Shab-al-Hiri Roach| | | +| Shadow Lords | | | +| Shadow of the Demon Lord | | | +| The Shadow of Yesterday | | | +| Shadowrun | | | +| Shatterzone | | | +| Shock: Social Science Fiction | | | +| Silver Age Sentinels | | | +| Simian Conquest| | | +| Sine Requie | | | +| Skulduggery | | | +| Skull and Crossbones | | | +| Skyrealms of Jorune | | | +| SLA Industries | | | +| Slรกine | | | +| S/lay w/Me | | | +| The Slayers | d20 | โœ”๏ธ | +| Smallville | Cortex Classic | | +| A Song of Ice and Fire | | | +| Sorcerer | | | +| Sovereign Stone | | | +| Space 1889 | | | +| Space Infantry | | | +| Space Opera | | | +| Spacemaster | Rolemaster | | +| Space Quest | | | +| Spaceship Zero | | | +| SpaceTime | | | +| The Spawn of Fashan | | | +| Spione: Story Now in Cold War Berlin | | | +| Spirit of the Century Evil Hat Productions | FATE | โœ”๏ธ | +| Splicers | | | +| Splittermond | | | +| Spycraft | | | +| Stalking the Night Fantastic | | | +| Standard RPG System | | | +| Star Ace| | | +| Starblazer Adventures | | | +| StarCraft Adventures | | | +| Star*Drive | | | +| Starfaring | | | +| Starfinder Roleplaying Game | d20 | โœ”๏ธ | +| Starfleet Voyages| | | +| Star Frontiers | | | +| Stargate SG-1 | Spycraft/d20 | | | +| Star Hero Fantasy Hero | Hero System | | +| Star Patrol| | | +| Star Riders | | | +| Star Rovers | | | +| Starship Troopers | | | +| Starships & Spacemen | | | +| Star Trek: Adventure Gaming in the Final Frontier | | | +| Star Trek: Deep Space Nine Role Playing Game | | | +| Star Trek Role Playing Game| | | +| Star Trek Roleplaying Game | | | +| Star Trek: The Next Generation | | | +| Star Trek| | | +| Star Wars| d6 | โœ”๏ธ | +| Star Wars (Fantasy Flight) | | | +| Star Wars (WoTC) | d20| โœ”๏ธ | +| Star Wreck | | | +| Steve Perrin's Quest Rules | | | +| Stormbringer | | | +| Storytelling System | Storyteller | โœ”๏ธ | +| The Strange | Cypher | โœ”๏ธ | +| Street Fighter | Storyteller | โœ”๏ธ | +| Strontium Dog | | | +| Super Squadron | | | +| Supergame | | | +| Superhero 2044 | | | +| Supernatural | Cortex Classic | โœ”๏ธ | +| Supervillains | | | +| Superworld | | | +| Swashbucklers of the 7 Skies | | | +| Swordbearer | | | +| Swords & Wizardry | d20 | โœ”๏ธ | +| Sword World RPG| | | +| Synnibarr | | | +| Systems Failure| | | +| Tales from the Floating Vagabond | | | +| Talislanta | Omni | | +| Taste My Steel! | | | +| Teenage Mutant Ninja Turtles and Other Strangeness| | | +| Teenagers from Outer Space | | | +| Tรฉkumel: Empire of the Petal Throne | | | +| Tenchi Muyo! Guardians of Order | | | +| Tenra War Enterbrain | | | +| Tephra: The Steampunk RPG | Clockwork | | +| Terra Incognita | | | +| Terra Primate | | | +| Theatrix | | | +| Thieves' Guild | | | +| Thieves' World | | | +| Tibet: The Roleplaying Game | | | +| Time & Time Again | | | +| Time Lord โ€” Adventures through Time and Space | | | +| Timelords Blacksburg Tactical Research Center | | | +| Timemaster | | | +| Timeship | | | +| To Challenge | | | +| Tokyo NOVA | | | +| Toon | | | +| Top Secret| | | +| TORG | | | +| Total Eclipse | | | +| Trail of Cthulhu | | | +| Transhuman Space | | | +| Traveller GDW | | | +| Traveller: 2300 | | | +| Tri-Stat dX | TriStat|โœ”๏ธ | +| Tribe 8 | | | +| Thrilling Tales | | | +| Trinity | | | +| Trollbabe | | | +| True20 | True20 | | +| Truth & Justice | | | +| Tunnels and Trolls | | | +| TWERPS | | | +| Twilight 2000 GDW | | | +| Twilight Imperium | | | | Stormbringer | Basic Roleplaying | โœ”๏ธ | | Unisystem | Unisystem | โœ”๏ธ | | Universalis | | โœ”๏ธ | @@ -417,9 +679,52 @@ This list is currently **Under Construction**. | Unknown Armies | | โŒ [#202](https://github.com/ianfhunter/GNOLL/issues/202) | | Usagi Yojimbo [[1]](https://www.rpg.net/reviews/archive/11/11664.phtml) | Modified Ironclaw | โœ”๏ธ | | Uuhraah! | | ๐Ÿ”ผ | +|Valley of the Pharaohs | |โ“ | +|Vampire: The Dark Ages | StoryTeller | โœ”๏ธ| +|Vampire: The Masquerade | WoD | โœ”๏ธ | +|Vampire: The Requiem | Storyteller | โœ”๏ธ | +|Victorian Adventure | |โ“ | +|Victorian Age: Vampire | Storyteller | โœ”๏ธ | +|Victoriana | | โ“| +|Victorian Gothic | Epic Dice | ๐Ÿ”ผ | +|Villains and Vigilantes | | ๐Ÿ”ผ | +|Violence | |โ“ | | QAGS | | โœ”๏ธ | | Qin | | โœ”๏ธ | | Quest of the Ancients | | ๐Ÿ”ผ | +| Warcraft | | | +| Warhammer 40,000 | | | +| Warhammer Fantasy | | | +| Warheads: Medieval Tales | | | +| WARS Roleplaying Game | | | +| Wayfarers | | | +| Weapons of the Gods | | | +| Weird Wars | d20 | โœ”๏ธ | +| Werewolf: The Apocalypse | | | +| Werewolf: The Forsaken | StoryTeller | โœ”๏ธ | +| Werewolf: The Wild West | StoryTeller | โœ”๏ธ | +| What Price Glory?! | | | +| The Wheel of Time | | | +| When Worlds Collide | | | +| The Whispering Vault | | | +| Wild Talents | | | +| Wild West | | | +| Witchcraft | | | +| Witch Hunt | | | +| Witch Hunter: | | | +| With Great Power... | | | +| Wizards' Realm | | | +| Wizards' World | | | +| Woof meow | | | +| World Action And Adventure | | | +| World of Darkness | WoD | โœ”๏ธ | +| The World of Indiana Jones| | | +| World of Synnibarr | | | +| World Tree | | | +| Worlds Beyond | | | +| Worlds of Wonder | Basic Roleplaying | โœ”๏ธ | +| Wraith: The Great War | WoD | โœ”๏ธ | +| Wraith: The Oblivion | WoD | โœ”๏ธ | | Year of the Phoenix | | ๐Ÿ”ผ | | Ysgarth | | ๐Ÿ”ผ | | The Zorcerer of Zo | PDQ | โœ”๏ธ | diff --git a/doc/gh-pages/index.md b/doc/gh-pages/index.md index 82ea11ea9..7a3a6efbe 100644 --- a/doc/gh-pages/index.md +++ b/doc/gh-pages/index.md @@ -14,7 +14,7 @@ The aim of GNOLL is to provide the world with a dice parsing library that: - Supports a wide range of dice notation formats and operations. - Can be used as a reference implementation for others trying to understand dice notation. -If you're looking to set up GNOLL, read our [Technical Setup](setup/installation.md) +If you're looking to set up GNOLL, read our [Technical Setup](developers/installation.html) Want to understand dice notation? You can start by understanding the basic types of dice and following [Grindon's Tale](notation/numeric_dice.md), or click into any interesting topic on the sidebar. diff --git a/scripts/benchmark/benchmark_cmd.py b/scripts/benchmark/benchmark_cmd.py index 2987878cc..d3e6224b7 100644 --- a/scripts/benchmark/benchmark_cmd.py +++ b/scripts/benchmark/benchmark_cmd.py @@ -7,9 +7,8 @@ # ======= Benchmark Imports ========== SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -22,7 +21,8 @@ def troll_roll(_): # test.t is generated inside the benchmark core v = subprocess.run([troll_exec, "0", "test.t"], - capture_output=True, check=True) + capture_output=True, + check=True) if v.returncode: raise ValueError diff --git a/scripts/benchmark/benchmark_core.py b/scripts/benchmark/benchmark_core.py index 570f1d9af..060602cce 100644 --- a/scripts/benchmark/benchmark_core.py +++ b/scripts/benchmark/benchmark_core.py @@ -6,7 +6,6 @@ class BenchMarker: - TIMEOUT_MINUTES = 1 TIMEOUT_SECONDS = TIMEOUT_MINUTES * 60 AVERAGING_RUNS = 50 @@ -16,9 +15,13 @@ def __init__(self, start_range=0, end_range=10): self.range = range(start_range, end_range) self.plt = plt - def add_function( - self, name, f, color="r", marker="o", hard_limit=None, override=None - ): + def add_function(self, + name, + f, + color="r", + marker="o", + hard_limit=None, + override=None): """Adds a function to the list of functions to benchmark. @name - Human Readable name @f - function @@ -27,16 +30,14 @@ def add_function( @hard_limit - don't execute benchmarks above this tolerance @override - Use values from a provided array """ - self.competitors.append( - { - "name": name, - "fn": f, - "color": color, - "marker": marker, - "hard_limit": hard_limit, - "override": override, - } - ) + self.competitors.append({ + "name": name, + "fn": f, + "color": color, + "marker": marker, + "hard_limit": hard_limit, + "override": override, + }) def benchmark(self, title): self.title = title @@ -80,9 +81,9 @@ def benchmark(self, title): # ------ BENCHMARK ------ time1 = time.time() try: - func_timeout.func_timeout( - self.TIMEOUT_SECONDS, roll_fn, args=[r] - ) + func_timeout.func_timeout(self.TIMEOUT_SECONDS, + roll_fn, + args=[r]) except (Exception, func_timeout.FunctionTimedOut) as e: print(f"Err: {c['name']}:{r}") print("\t", e) @@ -101,8 +102,10 @@ def benchmark(self, title): y.append(tt * 1000) if y: - plt.plot(shared_x[0: len(y)], y, - color=c["color"], marker=c["marker"]) + plt.plot(shared_x[0:len(y)], + y, + color=c["color"], + marker=c["marker"]) print("Result:", y) # Configuration and Output @@ -114,11 +117,9 @@ def benchmark(self, title): ax = plt.gca() ax.get_yaxis().set_major_formatter( - matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ",")) - ) + matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ","))) ax.get_xaxis().set_major_formatter( - matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ",")) - ) + matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ","))) legend_labels = [c["name"] for c in self.competitors] plt.legend(legend_labels) diff --git a/scripts/benchmark/benchmark_gnoll_versions.py b/scripts/benchmark/benchmark_gnoll_versions.py index 42b6337b9..b8c0b3046 100644 --- a/scripts/benchmark/benchmark_gnoll_versions.py +++ b/scripts/benchmark/benchmark_gnoll_versions.py @@ -7,9 +7,8 @@ print("======= Roll Wrappers ==========") SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -47,9 +46,11 @@ def stored_measurements(): print("======= Benchmark Begins ==========") bm = BenchMarker(end_range=11) -bm.add_function( - "GNOLL Before", None, override=stored_measurements, color="r", marker="x" -) +bm.add_function("GNOLL Before", + None, + override=stored_measurements, + color="r", + marker="x") bm.add_function("GNOLL After", gnoll_roll, color="b", marker="s") bm.benchmark("Feature comparison") diff --git a/scripts/benchmark/benchmark_python.py b/scripts/benchmark/benchmark_python.py index 660abd87c..e3a40b569 100644 --- a/scripts/benchmark/benchmark_python.py +++ b/scripts/benchmark/benchmark_python.py @@ -9,9 +9,8 @@ print("======= Roll Wrappers ==========") SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -31,9 +30,11 @@ def pythondice_roll(s): bm.add_function("GNOLL", gnoll_roll, color="b", marker="o") bm.add_function("RPG Dice", rpgdice_roll, color="g", marker="^") bm.add_function("Dice", dice_roll, color="r", marker="x") -bm.add_function( - "PythonDice", pythondice_roll, color="c", marker="s", hard_limit=100000000 -) +bm.add_function("PythonDice", + pythondice_roll, + color="c", + marker="s", + hard_limit=100000000) bm.add_function("d20", d20_roll, color="y", marker="1") bm.benchmark("Python Library comparison") diff --git a/scripts/demo/critical_highlights.py b/scripts/demo/critical_highlights.py index 22aa8e027..fe4ea3b53 100644 --- a/scripts/demo/critical_highlights.py +++ b/scripts/demo/critical_highlights.py @@ -4,9 +4,8 @@ # Copy-Pasted from test/util.py. Real app would just import gnoll from pypi SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -35,7 +34,6 @@ def main(): """Format a Dice Roll""" # Roll 1. for _ in range(100): - format_roll("d20+d20") diff --git a/scripts/demo/yatzy.py b/scripts/demo/yatzy.py index abd39ffa9..94eb5df50 100644 --- a/scripts/demo/yatzy.py +++ b/scripts/demo/yatzy.py @@ -5,9 +5,8 @@ # Copy-Pasted from test/util.py. Real app would just import gnoll from pypi SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -27,15 +26,13 @@ def is_high_straight(dice): def show_individual_dice_score(top): """Print out pip scores""" - print( - f""" + print(f""" [1]: {top[0]} [2]: {top[1]} [3]: {top[2]} [4]: {top[3]} [5]: {top[4]} -[6]: {top[5]}""" - ) +[6]: {top[5]}""") def tot_sides(y, dice): @@ -99,26 +96,23 @@ def scorecard(dice): else: chance = sum(dice) - total = sum( - [ - top_bonus, - one_pair_sum, - two_pair_sum, - three_oak_sum, - four_oak_sum, - low_straight, - high_straight, - full_house, - chance, - five_oak, - ] - ) + total = sum([ + top_bonus, + one_pair_sum, + two_pair_sum, + three_oak_sum, + four_oak_sum, + low_straight, + high_straight, + full_house, + chance, + five_oak, + ]) print(f"Dice: {dice}") show_individual_dice_score(top) - print( - f""" + print(f""" Top Bonus: {top_bonus} @@ -133,8 +127,7 @@ def scorecard(dice): Yatzy: {five_oak} Total: {total} - """ - ) + """) return total @@ -153,15 +146,13 @@ def yatzy_round(dice, first=False): sys.exit(0) # Roll 2 - print( - f""" + print(f""" 1: {dice[0]} 2: {dice[1]} 3: {dice[2]} 4: {dice[3]} 5: {dice[4]} - """ - ) + """) # TODO: Check valid input choice = input( "Please enter the numbers you wish to swap (space seperated)") diff --git a/scripts/histogram/histogram.py b/scripts/histogram/histogram.py index 6aa6add29..b32248e60 100644 --- a/scripts/histogram/histogram.py +++ b/scripts/histogram/histogram.py @@ -7,9 +7,8 @@ # Copy-Pasted from test/util.py SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) -m = os.path.join(SRC_DIR, "parser.py") + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) +m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -43,8 +42,8 @@ def main(): var = np.var(results) # From that, we know the shape of the fitted Gaussian. pdf_x = np.linspace(np.min(results), np.max(results), 100) - pdf_y = 1.0 / np.sqrt(2 * np.pi * var) * \ - np.exp(-0.5 * (pdf_x - avg) ** 2 / var) + pdf_y = 1.0 / np.sqrt(2 * np.pi * var) * np.exp(-0.5 * + (pdf_x - avg)**2 / var) plt.plot(pdf_x, pdf_y, "k--") diff --git a/scripts/histogram/requirements.txt b/scripts/histogram/requirements.txt index 581266d7d..7e0f694ea 100644 --- a/scripts/histogram/requirements.txt +++ b/scripts/histogram/requirements.txt @@ -1,3 +1,4 @@ matplotlib scipy -numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability +pillow>=10.0.0 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file diff --git a/src/CSharp/main.cs b/src/CSharp/main.cs index a57f5901b..cc8695514 100644 --- a/src/CSharp/main.cs +++ b/src/CSharp/main.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Diagnostics; [DllImport ("libdice.so", CharSet = CharSet.Ansi)] static extern int roll_and_write ( @@ -20,7 +21,9 @@ static void RollWithGNOLL (string roll_request) } // Create - roll_and_write(roll, fn); + int err_code = roll_and_write(roll, fn); + + Debug.Assert(err_code == 0, "GNOLL errored. Check error code"); // Read using (StreamReader sr = File.OpenText(fn)) diff --git a/src/PHP/index.php b/src/PHP/index.php index 0e4202b10..447344dcb 100644 --- a/src/PHP/index.php +++ b/src/PHP/index.php @@ -12,5 +12,7 @@ $err = $gnoll->roll_and_write("3d6", $fn); -$info = file_get_contents($fn); -print_r($info); +$myfile = fopen($fn, "r") or die("Unable to open file!"); +echo fread($myfile, filesize($fn)); +fclose($myfile); + diff --git a/src/PHP/target.mk b/src/PHP/target.mk index c88b909f8..88dfa5158 100644 --- a/src/PHP/target.mk +++ b/src/PHP/target.mk @@ -1,7 +1,7 @@ PHP_FOLDER:= /usr/lib/php/20210902/ .PHONY: php -php: clean yacc lex compile shared +php: all cp build/dice.so src/PHP/dice.so echo ${PHP_FOLDER} sudo cp build/dice.so ${PHP_FOLDER}/dice.so diff --git a/src/go/c_build b/src/go/c_build deleted file mode 120000 index d286311b7..000000000 --- a/src/go/c_build +++ /dev/null @@ -1 +0,0 @@ -../../build/ \ No newline at end of file diff --git a/src/go/c_includes b/src/go/c_includes deleted file mode 120000 index b497d9333..000000000 --- a/src/go/c_includes +++ /dev/null @@ -1 +0,0 @@ -../../src/grammar/ \ No newline at end of file diff --git a/src/go/target.mk b/src/go/target.mk index e01547df4..d778996ef 100644 --- a/src/go/target.mk +++ b/src/go/target.mk @@ -2,4 +2,6 @@ .PHONY: go go: all cp build/dice.so build/libdice.so + cd src/go && ln -s ../../build c_build + cd src/go && ln -s ../../src/grammar c_includes cd src/go && LD_LIBRARY_PATH="${PWD}/build:${LD_LIBRARY_PATH}" go build main.go && LD_LIBRARY_PATH="${PWD}/build:${LD_LIBRARY_PATH}" go run main.go && cd ../.. diff --git a/src/grammar/dice.yacc b/src/grammar/dice.yacc index 419e687cc..76c1dac3e 100644 --- a/src/grammar/dice.yacc +++ b/src/grammar/dice.yacc @@ -31,7 +31,7 @@ int yylex(void); int yyerror(const char* s); -int yywrap(); +int yywrap(void); //TODO: move to external file @@ -50,10 +50,10 @@ extern struct macro_struct *macros; pcg32_random_t rng; // Function Signatures for this file -int initialize(); +int initialize(void); // Functions -int initialize(){ +int initialize(void){ if (!seeded){ unsigned long int tick = (unsigned long)time(0)+(unsigned long)clock(); pcg32_srandom_r( @@ -1752,6 +1752,20 @@ int mock_roll(char * s, char * f, int mock_value, int mock_const){ } int main(int argc, char **str){ + + for(int a = 1; a != argc; a++){ + if(strcmp(str[a], "--help")==0){ + printf("GNOLL Dice Notation Parser\n"); + printf("Usage: ./executable [dice notation]\n"); + printf("Executable is non configurable. Use functions directly for advanced features.\n"); + return 0; + } + if(strcmp(str[a], "--version")==0){ + printf("GNOLL 4.3.0\n"); + return 0; + } + } + // Join arguments if they came in as seperate strings char * s = concat_strings(&str[1], (unsigned int)(argc - 1)); @@ -1780,8 +1794,7 @@ int main(int argc, char **str){ free(macros); } -int yyerror(s) -const char *s; +int yyerror(const char *s) { fprintf(stderr, "%s\n", s); @@ -1795,7 +1808,7 @@ const char *s; } -int yywrap(){ +int yywrap(void){ return (1); } diff --git a/src/grammar/external/pcg_basic.c b/src/grammar/external/pcg_basic.c index 211a6e586..cba5bb68e 100644 --- a/src/grammar/external/pcg_basic.c +++ b/src/grammar/external/pcg_basic.c @@ -64,7 +64,7 @@ uint32_t pcg32_random_r(pcg32_random_t* rng) { return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } -uint32_t pcg32_random() { return pcg32_random_r(&pcg32_global); } +uint32_t pcg32_random(void) { return pcg32_random_r(&pcg32_global); } // pcg32_boundedrand(bound): // pcg32_boundedrand_r(rng, bound): diff --git a/src/grammar/operations/macros.c b/src/grammar/operations/macros.c index 11b5d509c..18e8bb905 100644 --- a/src/grammar/operations/macros.c +++ b/src/grammar/operations/macros.c @@ -31,7 +31,7 @@ unsigned long hash_function(unsigned char *str) { return hash; } -void delete_all_macros() { +void delete_all_macros(void) { struct macro_struct *current_macro, *tmp; HASH_ITER(hh, macros, current_macro, tmp) { diff --git a/src/grammar/operations/macros.h b/src/grammar/operations/macros.h index f09c3d12b..97a6138d7 100644 --- a/src/grammar/operations/macros.h +++ b/src/grammar/operations/macros.h @@ -14,7 +14,7 @@ struct macro_struct { UT_hash_handle hh; /* makes this structure hashable */ }; -void delete_all_macros(); +void delete_all_macros(void); void register_macro(vec *macro_name, roll_params *to_store); diff --git a/src/grammar/rolls/dice_core.c b/src/grammar/rolls/dice_core.c index 7d4e3af12..2377dac2c 100644 --- a/src/grammar/rolls/dice_core.c +++ b/src/grammar/rolls/dice_core.c @@ -98,7 +98,7 @@ int* perform_roll(unsigned int number_of_dice, unsigned int die_sides, * PRO: We do not have to calculate all the dice rolled * CON: We lose per-dice information */ - all_dice_roll = (int*)safe_calloc(1, sizeof(int)); + all_dice_roll = (int*)safe_calloc(1, sizeof(int)); float midpoint = ((float)(end_value - start_value))/2 ; float val = get_random_normally(0, 1); val += 3; diff --git a/src/grammar/rolls/randomness.c b/src/grammar/rolls/randomness.c index 2ac009bd8..ae411f6ff 100644 --- a/src/grammar/rolls/randomness.c +++ b/src/grammar/rolls/randomness.c @@ -11,7 +11,7 @@ extern pcg32_random_t rng; -int get_random_uniformly(){ +int get_random_uniformly(void){ int value; #if USE_SECURE_RANDOM == 1 value = (int)arc4random_uniform(INT_MAX); diff --git a/src/grammar/rolls/randomness.h b/src/grammar/rolls/randomness.h index f5107e9db..0bf8a3854 100644 --- a/src/grammar/rolls/randomness.h +++ b/src/grammar/rolls/randomness.h @@ -1,7 +1,7 @@ #ifndef __RANDOMNESS_H__ #define __RANDOMNESS_H__ -int get_random_uniformly(); +int get_random_uniformly(void); double get_random_normally(double mean, double std); #endif diff --git a/src/grammar/util/mocking.c b/src/grammar/util/mocking.c index 5103de908..05d09d132 100644 --- a/src/grammar/util/mocking.c +++ b/src/grammar/util/mocking.c @@ -6,7 +6,7 @@ int secondary_mock_value = 0; MOCK_METHOD global_mock_style = NO_MOCK; -void reset_mocking() { +void reset_mocking(void) { /** * @brief Resets various globals for test mocking */ @@ -26,7 +26,7 @@ void init_mocking(MOCK_METHOD mock_style, int starting_value) { global_mock_style = mock_style; } -void mocking_tick() { +void mocking_tick(void) { /** * @brief Every time a dice is rolled, this function is called so that the * mocking logic can update diff --git a/src/grammar/util/mocking.h b/src/grammar/util/mocking.h index f9e80c79e..6f1b93f1b 100644 --- a/src/grammar/util/mocking.h +++ b/src/grammar/util/mocking.h @@ -1,3 +1,5 @@ +#ifndef __MOCKING_H__ +#define __MOCKING_H__ #include "constructs/dice_enums.h" #include "shared_header.h" @@ -12,6 +14,8 @@ typedef enum { } MOCK_METHOD; // Mocking Util -void reset_mocking(); +void reset_mocking(void); void init_mocking(MOCK_METHOD mock_style, int starting_value); -void mocking_tick(); +void mocking_tick(void); + +#endif diff --git a/src/grammar/util/safe_functions.c b/src/grammar/util/safe_functions.c index 381041785..1d8feb972 100644 --- a/src/grammar/util/safe_functions.c +++ b/src/grammar/util/safe_functions.c @@ -17,7 +17,7 @@ extern int verbose; #define ANSI_COLOR_RESET "\x1b[0m" -void print_gnoll_errors(){ +void print_gnoll_errors(void){ /** * @brief A human-readable translation of the gnoll error codes * @@ -213,7 +213,7 @@ char *safe_strdup(const char *str1) { return NULL; } char *result; - unsigned int l = strlen(str1) + 1; //+1 for \0 + // unsigned int l = strlen(str1) + 1; //+1 for \0 result = (char*) safe_calloc(sizeof(char), MAX_SYMBOL_LENGTH); // result = (char*) safe_calloc(sizeof(char), l); result = strcpy(result, str1); diff --git a/src/grammar/util/safe_functions.h b/src/grammar/util/safe_functions.h index b967e5874..7fef7cfe8 100644 --- a/src/grammar/util/safe_functions.h +++ b/src/grammar/util/safe_functions.h @@ -22,7 +22,7 @@ typedef enum { UNDEFINED_MACRO = 12 } ERROR_CODES; -void print_gnoll_errors(); +void print_gnoll_errors(void); void *safe_malloc(size_t size); void *safe_calloc(size_t nitems, size_t size); FILE *safe_fopen(const char *filename, const char *mode); diff --git a/src/julia/GNOLL/Project.toml b/src/julia/GNOLL/Project.toml index 7d3d67ef9..d2def8c20 100644 --- a/src/julia/GNOLL/Project.toml +++ b/src/julia/GNOLL/Project.toml @@ -1,7 +1,7 @@ name = "GnollDiceNotation" uuid = "6d20aa67-345f-4b27-9ab4-1a86910b1003" authors = ["Ian-Hunter"] -version = "4.2.0" +version = "4.4.0" [deps] Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" diff --git a/src/julia/GNOLL/README.md b/src/julia/GNOLL/README.md index 286e40e2d..1692cb18c 100644 --- a/src/julia/GNOLL/README.md +++ b/src/julia/GNOLL/README.md @@ -13,7 +13,7 @@ ![GitHub last commit](https://img.shields.io/github/last-commit/ianfhunter/GNOLL.svg) [![Donate](https://img.shields.io/badge/Donate-Paypal-yellow.svg)](https://paypal.me/ianfhunter)

- +

An easy to integrate [dice notation](https://en.wikipedia.org/wiki/Dice_notation) library for multiple programming languages. diff --git a/src/julia/target.mk b/src/julia/target.mk index 2ba9fd216..eede84d81 100644 --- a/src/julia/target.mk +++ b/src/julia/target.mk @@ -1,3 +1,3 @@ .PHONY: julia -julia: clean yacc lex compile shared +julia: all julia src/julia/GNOLL/test/runtests.jl diff --git a/src/python/README.md b/src/python/README.md index 286e40e2d..e3e3583db 100644 --- a/src/python/README.md +++ b/src/python/README.md @@ -13,7 +13,7 @@ ![GitHub last commit](https://img.shields.io/github/last-commit/ianfhunter/GNOLL.svg) [![Donate](https://img.shields.io/badge/Donate-Paypal-yellow.svg)](https://paypal.me/ianfhunter)

- +

An easy to integrate [dice notation](https://en.wikipedia.org/wiki/Dice_notation) library for multiple programming languages. @@ -46,6 +46,7 @@ GNOLL was written to be the definitive solution to dice notation. The core has b ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E) ![Julia](https://img.shields.io/badge/-Julia-9558B2?style=for-the-badge&logo=julia&logoColor=white) ![Perl](https://img.shields.io/badge/perl-%2339457E.svg?style=for-the-badge&logo=perl&logoColor=white) +![PHP](https://img.shields.io/badge/php-%23777BB4.svg?style=for-the-badge&logo=php&logoColor=white) ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) ![R](https://img.shields.io/badge/r-%23276DC3.svg?style=for-the-badge&logo=r&logoColor=white) ![Ruby](https://img.shields.io/badge/ruby-%23CC342D.svg?style=for-the-badge&logo=ruby&logoColor=white) @@ -76,9 +77,10 @@ pip3 install GNOLL Then, in your code: ```python -from gnoll.parser import roll +from gnoll import roll roll("1d20") ->> 7 +>> (0, [[12]], None) +# (return code, final result, dice breakdown (if enabled)) ``` ### ๐Ÿ› ๏ธ Installing From Source @@ -96,9 +98,11 @@ make test Or, just try some commands yourself! ```bash -$ ./dice 1d20 +$ ./build/dice 1d20 20 ``` +If you would like to run the 'dice' command from anywhere, use `make install` to add the executable to your path. + (Note that not all commands may not be able to be used this way as some symbols are reserved for use by different terminal interfaces (e.g. bash uses ! and #)) For languages other than Python/C/C++ call the corresponding make target after the commands above. @@ -130,6 +134,7 @@ Individual licensing arrangements can be made if this is an issue for your proje ## ๐Ÿ‘ Acknowledgments - **Billie Thompson** - *README & Contribution Templates* - [PurpleBooth](https://github.com/PurpleBooth) + - [Markdown Badges](https://github.com/Ileriayo/markdown-badges) ## ๐Ÿ—๏ธ Built With diff --git a/src/python/code/gnoll/__init__.py b/src/python/code/gnoll/__init__.py index e69de29bb..16afc4f91 100644 --- a/src/python/code/gnoll/__init__.py +++ b/src/python/code/gnoll/__init__.py @@ -0,0 +1,141 @@ +import os +import sys +import tempfile +from ctypes import cdll + +BUILD_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "c_build")) +C_SHARED_LIB = os.path.join(BUILD_DIR, "dice.so") + +libc = cdll.LoadLibrary(C_SHARED_LIB) + + +class GNOLLException(Exception): + """A custom exception to capture + the specific types of errors raised by GNOLL + """ + + def __init__(self, v): + Exception.__init__(self, v) + + +def raise_gnoll_error(value): + """Translates a GNOLL return code into a python + Exception, which is then raised + @value return code of GNOLL + """ + d = [ + None, + GNOLLException("BAD_ALLOC"), + GNOLLException("BAD_FILE"), + GNOLLException("NOT_IMPLEMENTED"), + GNOLLException("INTERNAL_ASSERT"), + GNOLLException("UNDEFINED_BEHAVIOUR"), + GNOLLException("BAD_STRING"), + GNOLLException("OUT_OF_RANGE"), + GNOLLException("IO_ERROR"), + GNOLLException("MAX_LOOP_LIMIT_HIT"), + GNOLLException("SYNTAX_ERROR"), + GNOLLException("DIVIDE_BY_ZERO"), + GNOLLException("UNDEFINED_MACRO"), + ] + err = d[value] + if err is not None: + raise err + + +def roll(s, + verbose=False, + mock=None, + mock_const=3, + breakdown=False, + builtins=False, + keep_temp_file=False): + """Parse some dice notation with GNOLL. + @param s the string to parse + @param verbose whether to enable verbosity (primarily for debug) + @param mock override the internal random number generator (for testing). + @param mock_const the seed value for overriding with mocks + @param breakdown get the details of each dice rolled, not just the final result + @param keep_temp_file don't delete the temporary file + @param force_dll_reload destroy the dll/shared object and reload (inadvisable) + @return return code, final result, dice breakdown (None if disabled) + """ + def make_native_type(v): + """ + Change a string to a more appropriate type if possible. + Number -> int + Word -> String + """ + if v == "0": + return 0 + if v == "": + return "NULL" + try: + return int(v) + except ValueError: + return v + + def extract_from_dice_file(lines, seperator): + """ + Parse GNOLL's file output + @param lines array of file readlines() + @param seperator value seperating terms in the file + """ + v = [x.split(seperator)[:-1] for x in lines if seperator in x] + v = [list(map(make_native_type, x)) for x in v] + return v + + try: + temp = tempfile.NamedTemporaryFile(prefix="gnoll_roll_", + suffix=".die", + delete=False) + temp.close() + + die_file = temp.name + + out_file = str(die_file).encode("ascii") + if verbose: + print("Rolling: ", s) + print("Output in:", out_file) + + s = s.encode("ascii") + + return_code = libc.roll_full_options( + s, + out_file, + verbose, # enable_verbose + breakdown, # enable_introspect + mock is not None, # enable_mock + builtins, # enable_builtins + mock, + mock_const, + ) + if return_code != 0: + raise_gnoll_error(return_code) + + with open(out_file, encoding="utf-8") as f: + lines = f.readlines() + + dice_breakdown = extract_from_dice_file(lines, ",") + result = extract_from_dice_file(lines, ";") + + return int(return_code), result, dice_breakdown + + finally: + if not keep_temp_file: + if verbose: + print("Deleting:", out_file) + + os.remove(die_file) + + +if __name__ == "__main__": + arg = "".join(sys.argv[1:]) + arg = arg if arg != "" else "1d20" + code, r, detailed_r = roll(arg, verbose=False) + print(f""" +[[GNOLL Results]] +Dice Roll: {arg} +Result: {r} +Exit Code: {code}, +Dice Breakdown: {detailed_r}""") diff --git a/src/python/code/gnoll/__main__.py b/src/python/code/gnoll/__main__.py new file mode 100644 index 000000000..c63ac4dc7 --- /dev/null +++ b/src/python/code/gnoll/__main__.py @@ -0,0 +1,93 @@ +"""Roll some dice with GNOLL.""" + +import argparse +import sys + +import gnoll + + +def parse_cmdline_args(args): + """Extract values from the commandline + @param args - the arguments from the commandline (excluding the python3 call) + """ + p = argparse.ArgumentParser(description=__doc__, + usage="python3 -m gnoll [options] EXPR", + add_help=False) + + p.add_argument( + "EXPR", + nargs="+", + help="a dice expression to evaluate" + "(multiple arguments will be joined with spaces)", + ) + + g = p.add_argument_group("main options") + g.add_argument("-h", + "--help", + action="help", + help="show this help message and exit") + g.add_argument( + "-b", + "--breakdown", + action="store_true", + help="show a breakdown into individual dice", + ) + g.add_argument( + "-n", + "--times", + metavar="N", + type=int, + default=1, + help="execute the entire expression N times", + ) + g.add_argument("--no-builtins", + action="store_true", + help="disable built-in macros") + + g = p.add_argument_group("debugging options") + g.add_argument("-v", + "--verbose", + action="store_true", + help="enable verbosity") + g.add_argument( + "--keep-temp-file", + action="store_true", + help="don't delete the created temporary file", + ) + g.add_argument("--mock", metavar="TYPE", type=int, help="mocking type") + g.add_argument("--mock-const", + metavar="N", + type=int, + default=3, + help="mocking constant") + + a = p.parse_args(args) + a.EXPR = " ".join(a.EXPR) + return a + + +def main(EXPR, times, no_builtins, **kwargs): + """ + The entry point for gnoll when called via `python -m gnoll` + @param EXPR - the expression + @param times - number of times to execute + @param no_builtins - a flag to disable builtins + @param **kwargs - other key word arguments to be passed to gnoll.roll + """ + for _ in range(times): + _, [[result]], breakdown = gnoll.roll(EXPR, + builtins=not no_builtins, + **kwargs) + yield (breakdown[0], "-->", result) if breakdown else (result, ) + + +def main_with_args(args): + """Parse the commandline args and then run `main` + @param args - the arguments from the commandline (excluding the python3 call) + """ + yield from main(**vars(parse_cmdline_args(args))) + + +if __name__ == "__main__": + for line in main_with_args(sys.argv[1:]): + print(*line) diff --git a/src/python/code/gnoll/parser.py b/src/python/code/gnoll/parser.py deleted file mode 100644 index f5dad0537..000000000 --- a/src/python/code/gnoll/parser.py +++ /dev/null @@ -1,128 +0,0 @@ -import os -import sys -import tempfile -from ctypes import cdll - -BUILD_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "c_build")) -C_SHARED_LIB = os.path.join(BUILD_DIR, "dice.so") - -libc = cdll.LoadLibrary(C_SHARED_LIB) - - -class GNOLLException(Exception): - """A custom exception to capture - the specific types of errors raised by GNOLL - """ - - def __init__(self, v): - Exception.__init__(self, v) - - -def raise_gnoll_error(value): - """Translates a GNOLL return code into a python - Exception, which is then raised - @value return code of GNOLL - """ - d = [ - None, - GNOLLException("BAD_ALLOC"), - GNOLLException("BAD_FILE"), - GNOLLException("NOT_IMPLEMENTED"), - GNOLLException("INTERNAL_ASSERT"), - GNOLLException("UNDEFINED_BEHAVIOUR"), - GNOLLException("BAD_STRING"), - GNOLLException("OUT_OF_RANGE"), - GNOLLException("IO_ERROR"), - GNOLLException("MAX_LOOP_LIMIT_HIT"), - GNOLLException("SYNTAX_ERROR"), - GNOLLException("DIVIDE_BY_ZERO"), - GNOLLException("UNDEFINED_MACRO"), - ] - err = d[value] - if err is not None: - raise err - - -def roll(s, verbose=False, mock=None, mock_const=3, breakdown=False, builtins=False): - """Parse some dice notation with GNOLL. - @param s the string to parse - @param verbose whether to enable verbosity (primarily for debug) - @param mock override the internal random number generator (for testing). - @param mock_const the seed value for overriding with mocks - @param breakdown get the details of each dice rolled, not just the final result - @param force_dll_reload destroy the dll/shared object and reload (inadvisable) - @return return code, final result, dice breakdown (None if disabled) - """ - temp = tempfile.NamedTemporaryFile( - prefix="gnoll_roll_", suffix=".die", delete=False - ) - - def make_native_type(v): - """ - Change a string to a more appropriate type if possible. - Number -> int - Word -> String - """ - if v == "0": - return 0 - if v == "": - return "NULL" - try: - return int(v) - except ValueError: - return v - - def extract_from_dice_file(lines, seperator): - """ - Parse GNOLL's file output - @param lines array of file readlines() - @param seperator value seperating terms in the file - """ - v = [x.split(seperator)[:-1] for x in lines if seperator in x] - v = [list(map(make_native_type, x)) for x in v] - return v - - die_file = temp.name - os.remove(die_file) - - out_file = str(die_file).encode("ascii") - if verbose: - print("Rolling: ", s) - print("Output in:", out_file) - - s = s.encode("ascii") - - return_code = libc.roll_full_options( - s, - out_file, - verbose, # enable_verbose - breakdown, # enable_introspect - mock is not None, # enable_mock - builtins, # enable_builtins - mock, - mock_const, - ) - if return_code != 0: - raise_gnoll_error(return_code) - - with open(out_file, encoding="utf-8") as f: - lines = f.readlines() - - dice_breakdown = extract_from_dice_file(lines, ",") - result = extract_from_dice_file(lines, ";") - - return int(return_code), result, dice_breakdown - - -if __name__ == "__main__": - arg = "".join(sys.argv[1:]) - arg = arg if arg != "" else "1d20" - code, r, detailed_r = roll(arg, verbose=False) - print( - f""" -[[GNOLL Results]] -Dice Roll: {arg} -Result: {r} -Exit Code: {code}, -Dice Breakdown: {detailed_r}""" - ) diff --git a/src/python/setup.cfg b/src/python/setup.cfg index a971d1bb0..03a37aa4e 100644 --- a/src/python/setup.cfg +++ b/src/python/setup.cfg @@ -2,7 +2,7 @@ name = gnoll author = Ian Frederick Vigogne Goodbody Hunter author_email = ianfhunter@gmail.com -version = 4.2.5 +version = 4.4.0 description = An efficient dice notation parser with extended notation long_description = file: README.md long_description_content_type = text/markdown diff --git a/src/python/target.mk b/src/python/target.mk index dc50d01ce..b1f68fcdb 100644 --- a/src/python/target.mk +++ b/src/python/target.mk @@ -12,7 +12,7 @@ python: all pip : python cd src/python/ ; python3 -m build python3 -m pip install -vvv --user --find-links=src/python/dist/ --force-reinstall --ignore-installed gnoll - python3 -c "from gnoll import parser as dt; dt.roll('2')" + python3 -c "from gnoll import roll; roll('2')" publish: test #twine upload --repository-url https://test.pypi.org/legacy/ src/python/dist/* diff --git a/tests/python/test_cli.py b/tests/python/test_cli.py new file mode 100644 index 000000000..5833c3863 --- /dev/null +++ b/tests/python/test_cli.py @@ -0,0 +1,19 @@ +import gnoll.__main__ + +m = lambda *x: list(gnoll.__main__.main_with_args(x)) + + +def test_cli(): + [[r]] = m("1d4") + assert isinstance(r, int) + + [[(die1, die2), a, r]] = m("2d4", "--breakdown") + assert all((isinstance(x, int) for x in [die1, die2, r])) + assert a == "-->" + + executions = m("4d6kh3", "+", "1", "--breakdown", "--times", "6", + "--no-builtins") + assert len(executions) == 6 + for (die1, die2, die3, die4), a, r in executions: + assert all((isinstance(x, int) for x in [die1, die2, die3, die4, r])) + assert a == "-->" diff --git a/tests/python/test_explosions.py b/tests/python/test_explosions.py index 46f9d49e1..d4949deab 100644 --- a/tests/python/test_explosions.py +++ b/tests/python/test_explosions.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import pytest -from util import Mock, roll +from util import Mock, error_handled_by_gnoll, roll # Should roll like: {3},{3},{1} @@ -19,9 +19,9 @@ def test_explosion(r, out): # Should roll like: {3,3},{1,1} # {3,3} @pytest.mark.parametrize("r,out", [("2d3!", 8), ("2d5!", 6)]) def test_multi_dice_explosion(r, out): - result, _ = roll( - r, mock_mode=Mock.RETURN_CONSTANT_TWICE_ELSE_CONSTANT_ONE, mock_const=3 - ) + result, _ = roll(r, + mock_mode=Mock.RETURN_CONSTANT_TWICE_ELSE_CONSTANT_ONE, + mock_const=3) assert result == out @@ -32,9 +32,9 @@ def test_multi_dice_explosion(r, out): ], ) def test_explosion_only_once(r, out): - result, _ = roll( - r, mock_mode=Mock.RETURN_CONSTANT_TWICE_ELSE_CONSTANT_ONE, mock_const=3 - ) + result, _ = roll(r, + mock_mode=Mock.RETURN_CONSTANT_TWICE_ELSE_CONSTANT_ONE, + mock_const=3) assert result == out @@ -49,5 +49,8 @@ def test_explosion_penetrate(r, out): assert result == out -if __name__ == "__main__": - test_explosion("d3!", 7) +def test_symbolic_explosions(): + try: + roll("df!") + except Exception as e: + error_handled_by_gnoll(e) diff --git a/tests/python/test_filtering.py b/tests/python/test_filtering.py index f81a33747..dd91522d4 100644 --- a/tests/python/test_filtering.py +++ b/tests/python/test_filtering.py @@ -11,7 +11,8 @@ ("10d10f>8", 19, Mock.RETURN_INCREMENTING, 1), # greater than ("10d10f!=1", 54, Mock.RETURN_INCREMENTING, 1), # is not ("10d10f==1", 1, Mock.RETURN_INCREMENTING, 1), # is equal - ("10d10f>=8", 27, Mock.RETURN_INCREMENTING, 1), # equal or greater than + ("10d10f>=8", 27, Mock.RETURN_INCREMENTING, + 1), # equal or greater than ("10d10f<=3", 6, Mock.RETURN_INCREMENTING, 1), # equal or less than ("10d10fis_even", 30, Mock.RETURN_INCREMENTING, 1), # even ("10d10fis_odd", 25, Mock.RETURN_INCREMENTING, 1), # odd @@ -19,7 +20,6 @@ ], ) def test_filter(r, out, mock, mock_const): - # https://github.com/ianfhunter/GNOLL/issues/216 result, _ = roll(r, mock_mode=mock, mock_const=mock_const, verbose=True) assert result == out diff --git a/tests/python/test_macros.py b/tests/python/test_macros.py index 9c481998f..8e869d4c5 100644 --- a/tests/python/test_macros.py +++ b/tests/python/test_macros.py @@ -19,13 +19,15 @@ def test_macro_storage(r, out, mock): assert result == out -@pytest.mark.parametrize("r,out,mock", [("#MY_DIE=d{A};@MY_DIE", "A", Mock.NO_MOCK)]) +@pytest.mark.parametrize("r,out,mock", + [("#MY_DIE=d{A};@MY_DIE", "A", Mock.NO_MOCK)]) def test_macro_usage(r, out, mock): result, _ = roll(r, mock_mode=mock) assert result == out -@pytest.mark.skip("Currently no support for rerolling operations like Addition") +@pytest.mark.skip( + "Currently no support for rerolling operations like Addition") def test_d66(): r = "#DSIXTYSIX=(d6*10)+d6;@DSIXTYSIX" result, _ = roll(r, mock_mode=Mock.RETURN_CONSTANT, mock_const=3) diff --git a/tests/python/test_package.py b/tests/python/test_package.py index 29ec1b0f5..5aa711cdc 100644 --- a/tests/python/test_package.py +++ b/tests/python/test_package.py @@ -3,7 +3,7 @@ @pytest.mark.skip("only to be tested manually") def test_pip_package(): - from gnoll.parser import roll + from gnoll import roll err_code, result, _ = roll("1d4") assert err_code == 0 diff --git a/tests/python/test_simple_sided_dice.py b/tests/python/test_simple_sided_dice.py index 1ace8abab..11b0a7b66 100644 --- a/tests/python/test_simple_sided_dice.py +++ b/tests/python/test_simple_sided_dice.py @@ -75,9 +75,8 @@ def test_non_rolling_roll(): assert result == 0 -@pytest.mark.parametrize( - "r", [("d"), ("d2d"), ("2d2d2"), ("2d2d2d"), ("1d"), ("d-1"), ("-1d-1")] -) +@pytest.mark.parametrize("r", [("d"), ("d2d"), ("2d2d2"), ("2d2d2d"), ("1d"), + ("d-1"), ("-1d-1")]) def test_bad_simple_rolls(r): try: roll(r) diff --git a/tests/python/test_symbolic_dice.py b/tests/python/test_symbolic_dice.py index 983aacc83..f8e3a6191 100644 --- a/tests/python/test_symbolic_dice.py +++ b/tests/python/test_symbolic_dice.py @@ -16,9 +16,8 @@ def test_symbolic_dice(r, out, mock): assert result == out -@pytest.mark.parametrize( - "r,out,mock", [("2d{A,B,C,D}", ["D", "D"], Mock.RETURN_CONSTANT)] -) +@pytest.mark.parametrize("r,out,mock", + [("2d{A,B,C,D}", ["D", "D"], Mock.RETURN_CONSTANT)]) def test_multiple_symbolic_dice(r, out, mock): result, _ = roll(r, mock_mode=mock) assert result == out @@ -41,9 +40,8 @@ def test_long_string(r, out, mock): assert result == out -@pytest.mark.parametrize( - "r,out,mock", [("2d{2,2,2,2,3}", [2, 2], Mock.RETURN_CONSTANT)] -) +@pytest.mark.parametrize("r,out,mock", + [("2d{2,2,2,2,3}", [2, 2], Mock.RETURN_CONSTANT)]) def test_multiple_numeric_dice(r, out, mock): result, _ = roll(r, mock_mode=mock) assert result == out diff --git a/tests/python/util.py b/tests/python/util.py index d530892be..6f1b08ff8 100644 --- a/tests/python/util.py +++ b/tests/python/util.py @@ -6,11 +6,9 @@ import numpy as np GRAMMAR_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/grammar") -) + os.path.join(os.path.dirname(__file__), "../../src/grammar")) SRC_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/") -) + os.path.join(os.path.dirname(__file__), "../../src/python/code/gnoll/")) MK_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) first_run = True @@ -32,11 +30,10 @@ def error_handled_by_gnoll(e): def get_roll(): - # We are explicitly using the local module here as we modify the yacc in order to mock our tests. # This ugly logic is to bypass the fact that you might have the pip package installed # and thus a name conflict - m = os.path.join(SRC_DIR, "parser.py") + m = os.path.join(SRC_DIR, "__init__.py") spec = iu.spec_from_file_location("dt", m) dt = iu.module_from_spec(spec) spec.loader.exec_module(dt) @@ -54,9 +51,12 @@ def make_all(): raise ValueError -def roll( - s, mock_mode=Mock.NO_MOCK, mock_const=3, verbose=False, squeeze=True, builtins=False -): +def roll(s, + mock_mode=Mock.NO_MOCK, + mock_const=3, + verbose=False, + squeeze=True, + builtins=False): global first_run if first_run: