Browse Source

Add pyngspice Python bindings with targeted capacitor probing

Initial commit of the pyngspice layer on top of ngspice:
- pybind11 C++ bindings (src/cpp/, src/bindings/)
- Python package with SpiceRunner interface, netlist preprocessor
- Targeted capacitor probing via .save i(C_name) directives for
  fast simulation when only specific cap currents are needed
- Build system (CMakeLists.txt, pyproject.toml, build_mingw.py)
- Test suite with 70 tests covering all preprocessor features
- Examples, winflexbison tooling, CLAUDE.md project docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pre-master-46
Joe DiPrima 1 month ago
parent
commit
6eeaaa7ca7
  1. 16
      .gitignore
  2. 241
      CLAUDE.md
  3. 436
      CMakeLists.txt
  4. 210
      build_mingw.py
  5. 93
      examples/python/ac_analysis.py
  6. 94
      examples/python/simple_rc.py
  7. 85
      pyngspice/__init__.py
  8. 518
      pyngspice/netlist.py
  9. 444
      pyngspice/runner.py
  10. 79
      pyproject.toml
  11. 340
      src/bindings/module.cpp
  12. 101
      src/cpp/callbacks.cpp
  13. 100
      src/cpp/callbacks.h
  14. 341
      src/cpp/raw_read.cpp
  15. 183
      src/cpp/raw_read.h
  16. 286
      src/cpp/sim_runner.cpp
  17. 219
      src/cpp/sim_runner.h
  18. 433
      src/cpp/simulator.cpp
  19. 298
      src/cpp/simulator.h
  20. 130
      src/cpp/trace.cpp
  21. 129
      src/cpp/trace.h
  22. 15
      src/maths/cmaths/cmath1.c
  23. 0
      tests/__init__.py
  24. 69
      tests/conftest.py
  25. 817
      tests/test_netlist.py
  26. 127
      tests/test_runner.py
  27. 69
      tests/test_sim.py
  28. 220
      tools/winflexbison/FlexLexer.h
  29. 34
      tools/winflexbison/README.md
  30. 166
      tools/winflexbison/changelog.md
  31. 173
      tools/winflexbison/custom_build_rules/README.md
  32. BIN
      tools/winflexbison/custom_build_rules/docs/1.png
  33. BIN
      tools/winflexbison/custom_build_rules/docs/2.png
  34. BIN
      tools/winflexbison/custom_build_rules/docs/3.png
  35. BIN
      tools/winflexbison/custom_build_rules/docs/4.png
  36. BIN
      tools/winflexbison/custom_build_rules/docs/5.png
  37. BIN
      tools/winflexbison/custom_build_rules/docs/6.png
  38. BIN
      tools/winflexbison/custom_build_rules/docs/BisonProperties.png
  39. BIN
      tools/winflexbison/custom_build_rules/docs/FlexProperties.png
  40. BIN
      tools/winflexbison/custom_build_rules/docs/Flex_debuging.png
  41. BIN
      tools/winflexbison/custom_build_rules/docs/Properties.png
  42. BIN
      tools/winflexbison/custom_build_rules/docs/Verbosity.png
  43. 23
      tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.props
  44. 91
      tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.targets
  45. 281
      tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.xml
  46. 43
      tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.props
  47. 178
      tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.targets
  48. 521
      tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.xml
  49. 23
      tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.props
  50. 94
      tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.targets
  51. 243
      tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.xml
  52. 227
      tools/winflexbison/data/README.md
  53. 61
      tools/winflexbison/data/bison-default.css
  54. 58
      tools/winflexbison/data/local.mk
  55. 362
      tools/winflexbison/data/m4sugar/foreach.m4
  56. 3329
      tools/winflexbison/data/m4sugar/m4sugar.m4
  57. 1241
      tools/winflexbison/data/skeletons/bison.m4
  58. 27
      tools/winflexbison/data/skeletons/c++-skel.m4
  59. 778
      tools/winflexbison/data/skeletons/c++.m4
  60. 72
      tools/winflexbison/data/skeletons/c-like.m4
  61. 27
      tools/winflexbison/data/skeletons/c-skel.m4
  62. 1125
      tools/winflexbison/data/skeletons/c.m4
  63. 26
      tools/winflexbison/data/skeletons/d-skel.m4
  64. 628
      tools/winflexbison/data/skeletons/d.m4
  65. 2763
      tools/winflexbison/data/skeletons/glr.c
  66. 397
      tools/winflexbison/data/skeletons/glr.cc
  67. 3533
      tools/winflexbison/data/skeletons/glr2.cc
  68. 27
      tools/winflexbison/data/skeletons/java-skel.m4
  69. 502
      tools/winflexbison/data/skeletons/java.m4
  70. 1633
      tools/winflexbison/data/skeletons/lalr1.cc
  71. 1326
      tools/winflexbison/data/skeletons/lalr1.d
  72. 1303
      tools/winflexbison/data/skeletons/lalr1.java
  73. 380
      tools/winflexbison/data/skeletons/location.cc
  74. 157
      tools/winflexbison/data/skeletons/stack.hh
  75. 2
      tools/winflexbison/data/skeletons/traceon.m4
  76. 525
      tools/winflexbison/data/skeletons/variant.hh
  77. 2209
      tools/winflexbison/data/skeletons/yacc.c
  78. 105
      tools/winflexbison/data/xslt/bison.xsl
  79. 401
      tools/winflexbison/data/xslt/xml2dot.xsl
  80. 572
      tools/winflexbison/data/xslt/xml2text.xsl
  81. 765
      tools/winflexbison/data/xslt/xml2xhtml.xsl
  82. 4
      visualc/src/include/ngspice/config.h

16
.gitignore

@ -94,3 +94,19 @@ test_cases/diode/test_built_in/*
build*/ build*/
prof/ prof/
# pyngspice build artifacts
CMakeCache.txt
CMakeFiles/
CMakeInit.txt
cmake_install.cmake
.cmake/
.skbuild-info.json
*.a
*.pyd
__pycache__/
*.pyc
*.zip
nul
build_log.txt
.claude/

241
CLAUDE.md

@ -0,0 +1,241 @@
# CLAUDE.md - pyngspice Build Guide
pyngspice provides native Python bindings for the ngspice circuit simulator using pybind11.
It serves as the ngspice backend for pyTesla (Tesla coil simulator), providing a SpiceRunner
interface that's a drop-in alternative to LTspice.
## Building and Installation
### Quick Start
```bash
python build_mingw.py
```
### What this does
1. Sets MinGW environment (PATH, CC, CXX) to avoid Git MSYS conflicts
2. Builds C++ extension via scikit-build-core + CMake + pybind11
3. Installs in editable mode (Python changes = instant, C++ changes = rebuild)
4. Syncs to configured venvs (if SYNC_VENVS is set in build_mingw.py)
5. Verifies the build by importing pyngspice
### Requirements
- MinGW-w64 GCC 15.x at `C:\mingw64`
- CMake 3.18+ (at `C:\Program Files\CMake\bin`)
- Python 3.9+ with development headers
- NumPy 1.20+
### After C++ changes: `python build_mingw.py`
### After Python changes: nothing (editable install)
### Build Variants
```bash
python build_mingw.py # Standard build
python build_mingw.py --clean # Clean + rebuild from scratch
python build_mingw.py --debug # Debug configuration
python build_mingw.py --sync # Sync .pth files to other venvs
python build_mingw.py --verify # Just check if install works
```
## How It Compiles
Three layers turn C++ into Python:
```
pyproject.toml scikit-build-core (PEP 517 build backend)
CMakeLists.txt CMake (finds Python, pybind11, compiles ~2000 ngspice C sources + wrapper C++)
src/bindings/module.cpp pybind11 (generates _pyngspice.pyd that Python can import)
```
`pip install -e .` triggers the whole chain. The `editable.mode = "inplace"` setting
in pyproject.toml is critical — it makes CMake build the .pyd directly into `pyngspice/`,
next to `__init__.py`. Without this, scikit-build-core uses fragile import hooks.
## Architecture
### Directory Structure
```
pyngspice/ # Python package (importable, no rebuild needed)
├── __init__.py # Package entry point, imports _pyngspice C++ extension
├── runner.py # SpiceRunner interface + NgspiceRunner + SubprocessRunner
├── netlist.py # Netlist pre-processor (Rser= translation for ngspice)
└── _pyngspice.*.pyd # Compiled C++ extension (built in-place, rebuild required)
src/cpp/ # C++ wrapper code (rebuild required after changes)
├── simulator.cpp/h # Low-level ngspice API wrapper (ngSpice_Init, etc.)
├── callbacks.cpp/h # Callback routing for ngspice events
├── sim_runner.cpp/h # PyLTSpice-compatible simulation runner
├── raw_read.cpp/h # .raw file parser (binary + ASCII, complex data, step detection)
└── trace.cpp/h # Vector/trace handling (numpy array conversion)
src/bindings/ # pybind11 bindings (rebuild required after changes)
└── module.cpp # PYBIND11_MODULE(_pyngspice, m) — the C++/Python bridge
```
### Key Files
| File | Purpose |
|------|---------|
| `CMakeLists.txt` | Main build: compiles ngspice_core static lib + _pyngspice extension |
| `pyproject.toml` | scikit-build-core config, package metadata |
| `build_mingw.py` | One-command build script (sets PATH, syncs venvs) |
| `src/bindings/module.cpp` | pybind11 module definition — all C++/Python type bindings |
| `src/cpp/sim_runner.cpp` | C++ SimRunner — core simulation engine that NgspiceRunner wraps |
| `src/cpp/raw_read.cpp` | .raw file parser — handles both binary and ASCII formats |
| `pyngspice/runner.py` | SpiceRunner interface — what pyTesla consumes |
| `pyngspice/netlist.py` | Rser= pre-processor — critical for LTspice netlist compatibility |
| `visualc/src/include/ngspice/config.h` | Build feature flags (XSPICE, OSDI, CIDER, KLU) |
## pyTesla Integration
### SpiceRunner Interface (pyTesla's swap point)
```python
from pyngspice import NgspiceRunner
# Create runner — replaces PyLTSpice's get_configured_sim_runner()
runner = NgspiceRunner(working_directory="./output")
# Run simulation — handles Rser= translation automatically
raw_file, log_file = runner.run("tesla_coil.net")
# Parse results — PyLTSpice's RawRead also works on ngspice .raw files
from pyngspice import RawRead
raw = RawRead(raw_file)
trace = raw.get_trace("V(out)")
data = trace.get_wave(0) # numpy array
```
### Netlist Pre-processing
pyngspice automatically translates LTspice syntax to ngspice before simulation:
- `L1 p1 0 8.5u Rser=0.012``L1 p1 _rser_L1 8.5u` + `R_L1_ser _rser_L1 0 0.012`
- `.backanno` directives are stripped (LTspice-specific, benign)
- `Rpar=` and `Cpar=` on inductors are stripped (could expand to parallel elements later)
- Standard SPICE constructs (behavioral sources, subcircuits, etc.) pass through unchanged
### Auto-detection Factory
```python
from pyngspice.runner import get_runner
# "auto" tries embedded C++ first, falls back to subprocess
runner = get_runner("./output", backend="auto") # or "embedded" or "subprocess"
```
## Build Configuration
### Enabled Features
- **XSPICE** - Code model support (ON by default)
- **OSDI** - Verilog-A support (ON by default)
- **HICUM2** - High-current BJT model with cppduals autodiff library
### Disabled Features
- **CIDER** - Numerical device models (requires complex KLU setup)
- **KLU** - Sparse matrix solver (requires SuiteSparse build)
To re-enable, modify `CMakeLists.txt`:
```cmake
option(ENABLE_CIDER "Enable CIDER numerical device models" ON)
option(ENABLE_KLU "Enable KLU sparse matrix solver" ON)
```
And ensure `visualc/src/include/ngspice/config.h` matches (e.g., `#define CIDER`).
## Testing
```bash
pytest tests/ -v # All tests
pytest tests/test_netlist.py -v # Netlist pre-processor only (no build needed)
pytest tests/test_runner.py tests/test_sim.py -v # Integration tests (need built extension)
```
### Requirements
- **Every new feature or bug fix must include tests.** Add test methods to the
relevant test class in `tests/`, or create a new class if the feature is distinct.
- **Always run the full test suite** (`pytest tests/test_netlist.py -v` at minimum)
after changes to verify nothing is broken (regression testing).
- Do not consider a change complete until all tests pass.
## Troubleshooting
### "DLL load failed" or "ImportError: _pyngspice"
Re-run `python build_mingw.py --clean`. If that doesn't work, check that MinGW-w64
is at `C:\mingw64\bin` and that `_pyngspice.*.pyd` exists in `pyngspice/`.
### "No module named pyngspice"
Run `pip install -e .` or `python build_mingw.py` from the repo root.
### "gcc not found" during build
Install MinGW-w64 to `C:\mingw64`. The build script overrides PATH to avoid Git's
built-in MSYS tools which conflict with MinGW. Run from cmd.exe, not Git Bash.
### "undefined reference to SIMinfo"
`ngspice.c` was excluded from the build. It contains `SIMinfo` but has no `main()`.
Ensure it's NOT in the exclusion list in `CMakeLists.txt`.
### "undefined reference to get_nbjt_info, get_numd_info..."
CIDER is enabled in `config.h` but CIDER sources are not compiled.
Ensure `config.h` has `/* #undef CIDER */` when ENABLE_CIDER=OFF.
### "undefined reference to HICUMload, HICUMtemp"
HICUM2 init is compiled but .cpp implementation files are excluded.
Ensure HICUM2 .cpp files are in the source list and `cppduals` include path is set.
### Module not found after editable install
The .pyd was not built into `pyngspice/`. Check that `LIBRARY_OUTPUT_DIRECTORY` is
set in CMakeLists.txt. Rebuild or manually copy:
```bash
copy build\cp311-cp311-win_amd64\_pyngspice.cp311-win_amd64.pyd pyngspice\
```
## DO NOT MODIFY (hard-won lessons)
### ngspice.c must stay in the build
`src/ngspice.c` is NOT the main entry point — it contains `SIMinfo` (device info table).
`src/main.c` is the actual entry point and IS excluded. Do not confuse the two.
### config.h feature flags must match CMakeLists.txt
`visualc/src/include/ngspice/config.h` and CMakeLists.txt options must agree.
If config.h has `#define CIDER` but ENABLE_CIDER=OFF, you get linker errors for
`get_nbjt_info` etc. If config.h undefs CIDER but ENABLE_CIDER=ON, CIDER sources
compile but the device models aren't registered.
### The Rser= regex in netlist.py handles the common case
The inductor Rser= pattern handles: bare numbers, engineering suffixes (u, m, n, p, k),
and scientific notation. It does NOT handle parameter references like `Rser={Rprimary}`
(curly brace expressions). If pyTesla generates parameterized Rser values, the pre-processor
will need to be extended to also expand `.param` definitions.
### MinGW static linking is intentional
We statically link libgcc, libstdc++, and libwinpthread so the .pyd ships zero DLLs.
Without this, users would need `libgcc_s_seh-1.dll`, `libstdc++-6.dll`, and
`libwinpthread-1.dll` in their PATH. Do not remove the static link flags.
### Raw file format differences
Both LTspice and ngspice produce .raw files, but there are subtle differences:
- NGspice may use different variable naming conventions (lowercase vs mixed case)
- Step detection works via scale vector value resets (handled in raw_read.cpp)
- PyLTSpice's RawRead CAN parse ngspice .raw files (community-verified)
## Reference Implementation
- **Authoritative ngspice source**: This repo (`C:\git\ngspice`) tracks upstream ngspice
- **ngspice docs**: https://ngspice.sourceforge.io/docs.html
- **Shared library API**: `src/include/ngspice/sharedspice.h` — the C API we wrap
- **pyfemm-xplat** (sister project): Follow its architecture patterns for build system,
pybind11 bindings, and Python package structure
## Development Notes
### Adding New Python Bindings
1. Add C++ wrapper function/class in `src/cpp/`
2. Register with pybind11 in `src/bindings/module.cpp`
3. Export from `pyngspice/__init__.py` if part of public API
### Adding Netlist Translations
Add new regex patterns or transformations in `pyngspice/netlist.py`.
Add corresponding tests in `tests/test_netlist.py`.
### Syncing to pyTesla's venv
Edit `SYNC_VENVS` in `build_mingw.py` to include pyTesla's venv path, then:
```bash
python build_mingw.py --sync
```
This writes a `.pth` file into that venv's site-packages so `import pyngspice` works there.

436
CMakeLists.txt

@ -0,0 +1,436 @@
cmake_minimum_required(VERSION 3.18)
project(pyngspice VERSION 43.0.0 LANGUAGES C CXX)
# ============================================================================
# Options
# ============================================================================
option(BUILD_PYTHON_BINDINGS "Build Python bindings" ON)
option(ENABLE_XSPICE "Enable XSPICE code models" ON)
option(ENABLE_OSDI "Enable OSDI (Verilog-A) support" ON)
option(ENABLE_CIDER "Enable CIDER numerical device models" OFF) # Disabled - needs special KLU setup
option(ENABLE_KLU "Enable KLU sparse matrix solver" OFF) # Disabled - needs complex build setup
# ============================================================================
# Compiler settings
# ============================================================================
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_EXTENSIONS ON) # Use gnu11 instead of c11 for better complex support
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Suppress warnings
if(MSVC)
add_compile_options(/W3 /wd4996 /wd4267 /wd4244 /wd4018 /wd4090 /wd4101 /wd4146)
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS)
add_definitions(-DWIN32 -D_WINDOWS)
else()
add_compile_options(-Wall -Wno-unused-variable -Wno-unused-function -Wno-sign-compare -Wno-format)
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
add_compile_options(-Wno-maybe-uninitialized -Wno-unused-but-set-variable)
endif()
endif()
# ============================================================================
# Platform detection
# ============================================================================
if(WIN32)
set(PLATFORM_WINDOWS TRUE)
elseif(APPLE)
set(PLATFORM_MACOS TRUE)
elseif(UNIX)
set(PLATFORM_LINUX TRUE)
endif()
# ============================================================================
# Find dependencies
# ============================================================================
find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module NumPy)
# Find pybind11 try system install first, then fall back to pip-installed
find_package(pybind11 CONFIG QUIET)
if(NOT pybind11_FOUND)
# pybind11 is a build dependency in pyproject.toml, so pip has already
# installed it into the build environment. Find it via the Python package.
execute_process(
COMMAND ${Python3_EXECUTABLE} -c
"import pybind11; print(pybind11.get_cmake_dir())"
OUTPUT_VARIABLE PYBIND11_CMAKE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE PYBIND11_RESULT
)
if(PYBIND11_RESULT EQUAL 0)
list(APPEND CMAKE_PREFIX_PATH ${PYBIND11_CMAKE_DIR})
find_package(pybind11 CONFIG REQUIRED)
else()
message(FATAL_ERROR
"pybind11 not found. Install it with: pip install pybind11>=2.11")
endif()
endif()
# Math library on Unix
if(UNIX AND NOT APPLE)
find_library(M_LIBRARY m REQUIRED)
endif()
find_package(Threads REQUIRED)
# ============================================================================
# Source directories
# ============================================================================
set(NGSPICE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src)
# ============================================================================
# Include directories
# ============================================================================
set(NGSPICE_INCLUDE_DIRS
${CMAKE_CURRENT_SOURCE_DIR}/visualc/src/include # Windows config.h
${NGSPICE_SRC}
${NGSPICE_SRC}/include
${NGSPICE_SRC}/include/cppduals # For HICUM2 duals library
${NGSPICE_SRC}/include/ngspice
${NGSPICE_SRC}/spicelib/devices
${NGSPICE_SRC}/spicelib/analysis
${NGSPICE_SRC}/spicelib/parser
${NGSPICE_SRC}/maths/dense
${NGSPICE_SRC}/maths/sparse
${NGSPICE_SRC}/maths/ni
${NGSPICE_SRC}/maths/deriv
${NGSPICE_SRC}/maths/cmaths
${NGSPICE_SRC}/maths/misc
${NGSPICE_SRC}/maths/poly
${NGSPICE_SRC}/maths/fft
${NGSPICE_SRC}/frontend
${NGSPICE_SRC}/frontend/plotting
${NGSPICE_SRC}/frontend/parser
${NGSPICE_SRC}/frontend/help
${NGSPICE_SRC}/frontend/numparam
${NGSPICE_SRC}/frontend/trannoise
${NGSPICE_SRC}/misc
${NGSPICE_SRC}/ciderlib/input
${NGSPICE_SRC}/ciderlib/support
${NGSPICE_SRC}/ciderlib/oned
${NGSPICE_SRC}/ciderlib/twod
${NGSPICE_SRC}/xspice/cm
${NGSPICE_SRC}/xspice/cmpp
${NGSPICE_SRC}/xspice/evt
${NGSPICE_SRC}/xspice/enh
${NGSPICE_SRC}/xspice/ipc
${NGSPICE_SRC}/xspice/idn
${NGSPICE_SRC}/xspice/mif
${NGSPICE_SRC}/xspice/icm
${NGSPICE_SRC}/osdi
)
# ============================================================================
# Build date/version info
# ============================================================================
string(TIMESTAMP NGSPICE_BUILD_DATE "%Y-%m-%d")
# ============================================================================
# Compile definitions
# ============================================================================
set(NGSPICE_DEFINITIONS
-DNGSPICEDLL
-DSHARED_MODULE
-DSIMULATOR
-DHAS_PROGREP
-DNG_SHARED_BUILD
-DNGSPICEBUILDDATE="${NGSPICE_BUILD_DATE}"
)
# MinGW-specific: nothing special needed now since we patched cmath1.c
if(ENABLE_XSPICE)
list(APPEND NGSPICE_DEFINITIONS -DXSPICE)
endif()
if(ENABLE_OSDI)
list(APPEND NGSPICE_DEFINITIONS -DOSDI)
endif()
if(ENABLE_CIDER)
list(APPEND NGSPICE_DEFINITIONS -DCIDER)
endif()
if(ENABLE_KLU)
list(APPEND NGSPICE_DEFINITIONS -DKLU)
endif()
# ============================================================================
# Collect ngspice source files
# ============================================================================
# Frontend sources
file(GLOB FRONTEND_SOURCES
${NGSPICE_SRC}/frontend/*.c
)
# Frontend subdirectories
file(GLOB FRONTEND_PARSER_SOURCES ${NGSPICE_SRC}/frontend/parser/*.c)
file(GLOB FRONTEND_NUMPARAM_SOURCES ${NGSPICE_SRC}/frontend/numparam/*.c)
file(GLOB FRONTEND_TRANNOISE_SOURCES ${NGSPICE_SRC}/frontend/trannoise/*.c)
file(GLOB FRONTEND_HELP_SOURCES ${NGSPICE_SRC}/frontend/help/*.c)
file(GLOB FRONTEND_PLOTTING_SOURCES ${NGSPICE_SRC}/frontend/plotting/*.c)
# Math sources
file(GLOB MATHS_SPARSE_SOURCES ${NGSPICE_SRC}/maths/sparse/*.c)
file(GLOB MATHS_DENSE_SOURCES ${NGSPICE_SRC}/maths/dense/*.c)
file(GLOB MATHS_NI_SOURCES ${NGSPICE_SRC}/maths/ni/*.c)
file(GLOB MATHS_DERIV_SOURCES ${NGSPICE_SRC}/maths/deriv/*.c)
file(GLOB MATHS_CMATHS_SOURCES ${NGSPICE_SRC}/maths/cmaths/*.c)
file(GLOB MATHS_MISC_SOURCES ${NGSPICE_SRC}/maths/misc/*.c)
file(GLOB MATHS_POLY_SOURCES ${NGSPICE_SRC}/maths/poly/*.c)
file(GLOB MATHS_FFT_SOURCES ${NGSPICE_SRC}/maths/fft/*.c)
# Spicelib sources
file(GLOB SPICELIB_ANALYSIS_SOURCES ${NGSPICE_SRC}/spicelib/analysis/*.c)
file(GLOB SPICELIB_PARSER_SOURCES ${NGSPICE_SRC}/spicelib/parser/*.c)
# Device sources - collect all device model subdirectories
file(GLOB_RECURSE SPICELIB_DEVICES_C_SOURCES ${NGSPICE_SRC}/spicelib/devices/*.c)
# HICUM2 .cpp files use the "duals" library for automatic differentiation (header-only, included in ngspice)
file(GLOB_RECURSE SPICELIB_DEVICES_CPP_SOURCES ${NGSPICE_SRC}/spicelib/devices/*.cpp)
set(SPICELIB_DEVICES_SOURCES ${SPICELIB_DEVICES_C_SOURCES} ${SPICELIB_DEVICES_CPP_SOURCES})
# Exclude KLU-specific binding files when KLU is disabled
if(NOT ENABLE_KLU)
list(FILTER SPICELIB_DEVICES_SOURCES EXCLUDE REGEX "bindCSC\\.c$")
endif()
# Misc sources
file(GLOB MISC_SOURCES ${NGSPICE_SRC}/misc/*.c)
# CIDER sources (if enabled)
if(ENABLE_CIDER)
file(GLOB CIDER_INPUT_SOURCES ${NGSPICE_SRC}/ciderlib/input/*.c)
file(GLOB CIDER_SUPPORT_SOURCES ${NGSPICE_SRC}/ciderlib/support/*.c)
file(GLOB CIDER_ONED_SOURCES ${NGSPICE_SRC}/ciderlib/oned/*.c)
file(GLOB CIDER_TWOD_SOURCES ${NGSPICE_SRC}/ciderlib/twod/*.c)
set(CIDER_SOURCES
${CIDER_INPUT_SOURCES}
${CIDER_SUPPORT_SOURCES}
${CIDER_ONED_SOURCES}
${CIDER_TWOD_SOURCES}
)
endif()
# XSPICE sources (if enabled)
if(ENABLE_XSPICE)
file(GLOB XSPICE_CM_SOURCES ${NGSPICE_SRC}/xspice/cm/*.c)
file(GLOB XSPICE_EVT_SOURCES ${NGSPICE_SRC}/xspice/evt/*.c)
file(GLOB XSPICE_ENH_SOURCES ${NGSPICE_SRC}/xspice/enh/*.c)
file(GLOB XSPICE_IPC_SOURCES ${NGSPICE_SRC}/xspice/ipc/*.c)
file(GLOB XSPICE_IDN_SOURCES ${NGSPICE_SRC}/xspice/idn/*.c)
file(GLOB XSPICE_MIF_SOURCES ${NGSPICE_SRC}/xspice/mif/*.c)
# ICM (code models) built separately
set(XSPICE_SOURCES
${XSPICE_CM_SOURCES}
${XSPICE_EVT_SOURCES}
${XSPICE_ENH_SOURCES}
${XSPICE_IPC_SOURCES}
${XSPICE_IDN_SOURCES}
${XSPICE_MIF_SOURCES}
)
# Exclude KLU-specific binding files when KLU is disabled
if(NOT ENABLE_KLU)
list(FILTER XSPICE_SOURCES EXCLUDE REGEX "bindCSC\\.c$")
endif()
endif()
# OSDI sources (if enabled)
if(ENABLE_OSDI)
file(GLOB OSDI_SOURCES ${NGSPICE_SRC}/osdi/*.c)
endif()
# KLU sources (if enabled)
if(ENABLE_KLU)
# KLU sources are in src/maths/KLU directory
file(GLOB KLU_SOURCES ${NGSPICE_SRC}/maths/KLU/*.c)
list(APPEND NGSPICE_INCLUDE_DIRS ${NGSPICE_SRC}/maths/KLU)
endif()
# Main ngspice files
set(NGSPICE_MAIN_SOURCES
${NGSPICE_SRC}/sharedspice.c
${NGSPICE_SRC}/conf.c
${NGSPICE_SRC}/ngspice.c # Contains SIMinfo definition
)
# MSVC compatibility
if(MSVC AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/visualc/src/msvc-compat.c)
list(APPEND NGSPICE_MAIN_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/visualc/src/msvc-compat.c
)
endif()
# ============================================================================
# Combine all ngspice sources
# ============================================================================
set(ALL_NGSPICE_SOURCES
${NGSPICE_MAIN_SOURCES}
${FRONTEND_SOURCES}
${FRONTEND_PARSER_SOURCES}
${FRONTEND_NUMPARAM_SOURCES}
${FRONTEND_TRANNOISE_SOURCES}
${FRONTEND_HELP_SOURCES}
${FRONTEND_PLOTTING_SOURCES}
${MATHS_SPARSE_SOURCES}
${MATHS_DENSE_SOURCES}
${MATHS_NI_SOURCES}
${MATHS_DERIV_SOURCES}
${MATHS_CMATHS_SOURCES}
${MATHS_MISC_SOURCES}
${MATHS_POLY_SOURCES}
${MATHS_FFT_SOURCES}
${SPICELIB_ANALYSIS_SOURCES}
${SPICELIB_PARSER_SOURCES}
${SPICELIB_DEVICES_SOURCES}
${MISC_SOURCES}
)
if(ENABLE_CIDER)
list(APPEND ALL_NGSPICE_SOURCES ${CIDER_SOURCES})
endif()
if(ENABLE_XSPICE)
list(APPEND ALL_NGSPICE_SOURCES ${XSPICE_SOURCES})
endif()
if(ENABLE_OSDI)
list(APPEND ALL_NGSPICE_SOURCES ${OSDI_SOURCES})
endif()
if(ENABLE_KLU AND DEFINED KLU_SOURCES)
list(APPEND ALL_NGSPICE_SOURCES ${KLU_SOURCES})
endif()
# Remove files that cause conflicts or aren't needed for shared library
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "main\\.c$")
# Note: ngspice.c is INCLUDED - it contains SIMinfo but no main()
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "winmain\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "tclspice\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "ngnutmeg\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "nghelp\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "ngsconvert\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "ngmultidec\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "ngproc2mod\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "makeidx\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "hist_info\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "testcommands\\.c$")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "test_accuracy\\.c$")
# Exclude ndev (network device) - uses Unix socket headers
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/ndev/")
# Exclude CIDER-dependent numerical device models when CIDER is disabled
if(NOT ENABLE_CIDER)
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/nbjt/")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/nbjt2/")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/numd/")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/numd2/")
list(FILTER ALL_NGSPICE_SOURCES EXCLUDE REGEX "/numos/")
endif()
# ============================================================================
# Create ngspice static library
# ============================================================================
add_library(ngspice_core STATIC ${ALL_NGSPICE_SOURCES})
target_include_directories(ngspice_core PUBLIC ${NGSPICE_INCLUDE_DIRS})
target_compile_definitions(ngspice_core PUBLIC ${NGSPICE_DEFINITIONS})
if(UNIX AND NOT APPLE)
target_link_libraries(ngspice_core PUBLIC ${M_LIBRARY})
endif()
target_link_libraries(ngspice_core PUBLIC Threads::Threads)
# Windows-specific libraries
if(WIN32)
target_link_libraries(ngspice_core PUBLIC shlwapi)
endif()
# ============================================================================
# Python extension sources
# ============================================================================
set(PYTHON_EXT_SOURCES
${NGSPICE_SRC}/bindings/module.cpp
${NGSPICE_SRC}/cpp/simulator.cpp
${NGSPICE_SRC}/cpp/callbacks.cpp
${NGSPICE_SRC}/cpp/sim_runner.cpp
${NGSPICE_SRC}/cpp/raw_read.cpp
${NGSPICE_SRC}/cpp/trace.cpp
)
# ============================================================================
# Create Python module
# ============================================================================
pybind11_add_module(_pyngspice MODULE ${PYTHON_EXT_SOURCES})
target_include_directories(_pyngspice PRIVATE
${NGSPICE_INCLUDE_DIRS}
${NGSPICE_SRC}/cpp
${Python3_INCLUDE_DIRS}
${Python3_NumPy_INCLUDE_DIRS}
)
target_compile_definitions(_pyngspice PRIVATE
NGSPICE_PYTHON_MODULE
VERSION_STR="${PROJECT_VERSION}"
${NGSPICE_DEFINITIONS}
)
# Link against ngspice core and Python
target_link_libraries(_pyngspice PRIVATE
ngspice_core
Python3::NumPy
)
if(APPLE)
target_link_libraries(_pyngspice PRIVATE "-framework Accelerate")
endif()
# Output .pyd directly into pyngspice/ for inplace editable install
set_target_properties(_pyngspice PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/pyngspice
)
# MinGW: statically link all runtime DLLs (ship zero DLLs)
if(MINGW)
target_link_options(_pyngspice PRIVATE -static-libgcc -static-libstdc++)
target_link_libraries(_pyngspice PRIVATE
-Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive,-Bdynamic
)
endif()
# ============================================================================
# Install (for wheel builds)
# ============================================================================
install(TARGETS _pyngspice
LIBRARY DESTINATION pyngspice
RUNTIME DESTINATION pyngspice
)
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/pyngspice/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/pyngspice/runner.py
${CMAKE_CURRENT_SOURCE_DIR}/pyngspice/netlist.py
DESTINATION pyngspice
)
# ============================================================================
# Summary
# ============================================================================
message(STATUS "")
message(STATUS "========================================")
message(STATUS "pyngspice Configuration")
message(STATUS "========================================")
message(STATUS " Version: ${PROJECT_VERSION}")
message(STATUS " Python: ${Python3_EXECUTABLE}")
message(STATUS " NumPy: ${Python3_NumPy_VERSION}")
message(STATUS " pybind11: ${pybind11_VERSION}")
message(STATUS " XSPICE: ${ENABLE_XSPICE}")
message(STATUS " OSDI: ${ENABLE_OSDI}")
message(STATUS " CIDER: ${ENABLE_CIDER}")
message(STATUS " KLU: ${ENABLE_KLU}")
message(STATUS " Source files: ${CMAKE_CURRENT_LIST_DIR}")
message(STATUS "========================================")
message(STATUS "")

210
build_mingw.py

@ -0,0 +1,210 @@
#!/usr/bin/env python
"""
Build script for pyngspice using MinGW-w64.
Single-command build that handles PATH setup, editable install,
and optional venv synchronization for pyTesla integration.
Usage:
python build_mingw.py # Build and install (editable)
python build_mingw.py --clean # Clean build directory first
python build_mingw.py --sync # Sync .pth files to other venvs
python build_mingw.py --verify # Just verify the install works
python build_mingw.py --debug # Build with debug logging
Run from cmd.exe, NOT Git Bash (to avoid MSYS path mangling).
"""
import argparse
import glob
import os
import shutil
import subprocess
import sys
from pathlib import Path
# ============================================================================
# Configuration
# ============================================================================
REPO_ROOT = Path(__file__).parent.resolve()
MINGW_BIN = r"C:\mingw64\bin"
CMAKE_BIN = r"C:\Program Files\CMake\bin"
PACKAGE_DIR = REPO_ROOT / "pyngspice"
BUILD_DIR = REPO_ROOT / "build"
# Other venvs that need access to pyngspice (e.g., pyTesla's venv)
# Add paths here as needed:
SYNC_VENVS = [
# r"C:\git\pytesla\.venv",
]
def get_clean_path():
"""Build a clean PATH that avoids Git MSYS conflicts."""
python_dir = Path(sys.executable).parent
python_scripts = python_dir / "Scripts"
path_parts = [
str(MINGW_BIN),
str(python_dir),
str(python_scripts),
str(CMAKE_BIN),
r"C:\Windows\System32",
r"C:\Windows",
]
return ";".join(p for p in path_parts if os.path.isdir(p))
def get_build_env(**extra):
"""Get environment variables for the build."""
env = os.environ.copy()
env["PATH"] = get_clean_path()
env["CC"] = "gcc"
env["CXX"] = "g++"
env.update(extra)
return env
def clean():
"""Remove build directory and old .pyd files."""
if BUILD_DIR.exists():
print(f"Removing {BUILD_DIR}...")
shutil.rmtree(BUILD_DIR, ignore_errors=True)
# Remove old .pyd files from package directory
for pyd in PACKAGE_DIR.glob("*.pyd"):
print(f"Removing {pyd}...")
pyd.unlink()
# Uninstall any previous install
subprocess.run(
[sys.executable, "-m", "pip", "uninstall", "-y", "pyngspice"],
capture_output=True,
)
print("Clean complete.")
def build(debug=False):
"""Build and install pyngspice in editable mode."""
env = get_build_env()
# Verify MinGW is available
gcc = shutil.which("gcc", path=env["PATH"])
if not gcc:
print(f"ERROR: gcc not found. Ensure MinGW is installed at {MINGW_BIN}")
sys.exit(1)
result = subprocess.run(["gcc", "--version"], capture_output=True, text=True, env=env)
print(f"Using GCC: {result.stdout.splitlines()[0]}")
# Build command
cmd = [
sys.executable, "-m", "pip", "install",
"--no-cache-dir",
"-e", ".",
]
if debug:
# Pass debug flag via environment (picked up by scikit-build-core)
env["CMAKE_ARGS"] = "-DCMAKE_BUILD_TYPE=Debug"
print(f"\nBuilding pyngspice...")
print(f" Command: {' '.join(cmd)}")
print(f" Working dir: {REPO_ROOT}")
print()
result = subprocess.run(cmd, cwd=str(REPO_ROOT), env=env)
if result.returncode != 0:
print("\nBuild FAILED.")
sys.exit(result.returncode)
print("\nBuild complete!")
def sync_venvs():
"""Write .pth files to other venvs so they can import pyngspice."""
if not SYNC_VENVS:
print("No venvs configured for sync. Edit SYNC_VENVS in build_mingw.py.")
return
for venv_path in SYNC_VENVS:
venv = Path(venv_path)
if not venv.exists():
print(f" SKIP (not found): {venv}")
continue
# Find site-packages
if sys.platform == "win32":
site_packages = venv / "Lib" / "site-packages"
else:
py_ver = f"python{sys.version_info.major}.{sys.version_info.minor}"
site_packages = venv / "lib" / py_ver / "site-packages"
if not site_packages.exists():
print(f" SKIP (no site-packages): {venv}")
continue
pth_file = site_packages / "pyngspice.pth"
pth_file.write_text(str(REPO_ROOT) + "\n")
print(f" Synced: {pth_file}")
print("Venv sync complete.")
def verify():
"""Verify the pyngspice installation works."""
print("\nVerifying pyngspice installation...")
# Test basic import
result = subprocess.run(
[sys.executable, "-c", (
"from pyngspice import NgspiceRunner, _cpp_available; "
"print(f' C++ extension: {_cpp_available}'); "
"print(f' NgspiceRunner available: {NgspiceRunner.detect()}'); "
"from pyngspice import __version__; "
"print(f' Version: {__version__}'); "
"print(' OK!')"
)],
cwd=str(REPO_ROOT),
)
if result.returncode != 0:
print("\nVerification FAILED.")
return False
return True
def main():
parser = argparse.ArgumentParser(description="Build pyngspice with MinGW-w64")
parser.add_argument("--clean", action="store_true", help="Clean build artifacts first")
parser.add_argument("--sync", action="store_true", help="Sync .pth files to other venvs")
parser.add_argument("--verify", action="store_true", help="Verify installation only")
parser.add_argument("--debug", action="store_true", help="Build with debug configuration")
parser.add_argument("--no-verify", action="store_true", help="Skip post-build verification")
args = parser.parse_args()
if args.verify:
verify()
return
if args.clean:
clean()
if args.sync:
sync_venvs()
return
build(debug=args.debug)
if not args.no_verify:
verify()
if SYNC_VENVS:
sync_venvs()
if __name__ == "__main__":
main()

93
examples/python/ac_analysis.py

@ -0,0 +1,93 @@
"""
AC Analysis Example
This example demonstrates using the pyngspice Python extension
to perform AC analysis (frequency sweep) on an RC filter.
"""
try:
from pyngspice import SimRunner, RawRead
except ImportError:
print("pyngspice extension not installed. Run: pip install -e .")
exit(1)
import numpy as np
import os
# RC filter netlist with AC analysis
netlist_content = """
* RC Low-Pass Filter - AC Analysis
* Shows magnitude and phase response
V1 in 0 AC 1
R1 in out 1k
C1 out 0 100n
.ac dec 100 1 100k
.end
"""
def main():
output_dir = "./output"
os.makedirs(output_dir, exist_ok=True)
netlist_file = os.path.join(output_dir, "ac_filter.net")
with open(netlist_file, "w") as f:
f.write(netlist_content)
print("Running AC analysis...")
runner = SimRunner(output_folder=output_dir)
raw_file, log_file = runner.run_now(netlist_file)
raw = RawRead(raw_file)
print(f"Analysis type: {raw.analysis_type}")
print(f"Complex data: {raw.is_complex}")
print(f"\nTraces: {raw.get_trace_names()}")
# Get frequency and output voltage (complex)
freq = raw.get_trace("frequency").get_wave(0)
v_out_complex = raw.get_trace("V(out)").get_wave_complex(0)
# Calculate magnitude in dB and phase in degrees
magnitude_db = 20 * np.log10(np.abs(v_out_complex))
phase_deg = np.angle(v_out_complex, deg=True)
# Find -3dB cutoff frequency
idx_3db = np.argmin(np.abs(magnitude_db - (-3)))
f_cutoff = freq[idx_3db]
print(f"\n-3dB cutoff frequency: {f_cutoff:.2f} Hz")
print(f"Theoretical: {1/(2*np.pi*1e3*100e-9):.2f} Hz")
# Plot if matplotlib available
try:
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8), sharex=True)
ax1.semilogx(freq, magnitude_db)
ax1.axhline(-3, color='r', linestyle='--', alpha=0.5, label='-3dB')
ax1.axvline(f_cutoff, color='r', linestyle='--', alpha=0.5)
ax1.set_ylabel('Magnitude (dB)')
ax1.set_title('RC Low-Pass Filter Frequency Response')
ax1.legend()
ax1.grid(True, which='both', alpha=0.3)
ax2.semilogx(freq, phase_deg)
ax2.axhline(-45, color='r', linestyle='--', alpha=0.5, label='-45°')
ax2.set_xlabel('Frequency (Hz)')
ax2.set_ylabel('Phase (degrees)')
ax2.legend()
ax2.grid(True, which='both', alpha=0.3)
plt.tight_layout()
plot_file = os.path.join(output_dir, "ac_filter.png")
plt.savefig(plot_file, dpi=150)
print(f"\nPlot saved to: {plot_file}")
plt.show()
except ImportError:
print("\nInstall matplotlib for plotting: pip install matplotlib")
if __name__ == "__main__":
main()

94
examples/python/simple_rc.py

@ -0,0 +1,94 @@
"""
Simple RC Circuit Example
This example demonstrates using the pyngspice Python extension
to simulate a basic RC low-pass filter.
"""
# Note: This example requires the ngspice extension to be built and installed
# Run: pip install -e . from the ngspice root directory
try:
from pyngspice import SimRunner, RawRead
except ImportError:
print("pyngspice extension not installed. Run: pip install -e .")
print("This example shows the intended API usage.")
exit(1)
import numpy as np
import os
# Create a simple RC netlist
netlist_content = """
* Simple RC Low-Pass Filter
* Cutoff frequency: ~1.59 kHz
V1 in 0 AC 1 SIN(0 1 1k)
R1 in out 1k
C1 out 0 100n
.tran 10u 10m
.end
"""
def main():
# Create output directory
output_dir = "./output"
os.makedirs(output_dir, exist_ok=True)
# Write netlist to file
netlist_file = os.path.join(output_dir, "simple_rc.net")
with open(netlist_file, "w") as f:
f.write(netlist_content)
print(f"Created netlist: {netlist_file}")
# Create SimRunner and run simulation
print("Running simulation...")
runner = SimRunner(output_folder=output_dir)
raw_file, log_file = runner.run_now(netlist_file)
print(f"Raw file: {raw_file}")
print(f"Log file: {log_file}")
# Read results
raw = RawRead(raw_file)
print(f"\nAnalysis type: {raw.analysis_type}")
print(f"Number of traces: {raw.num_variables}")
print(f"Number of points: {raw.num_points}")
print(f"\nAvailable traces:")
for name in raw.get_trace_names():
print(f" - {name}")
# Get time and voltage data
time = raw.get_trace("time").get_wave(0)
v_in = raw.get_trace("V(in)").get_wave(0)
v_out = raw.get_trace("V(out)").get_wave(0)
print(f"\nTime range: {time[0]*1e3:.3f} ms to {time[-1]*1e3:.3f} ms")
print(f"V(in) range: {min(v_in):.3f} V to {max(v_in):.3f} V")
print(f"V(out) range: {min(v_out):.3f} V to {max(v_out):.3f} V")
# Optional: Plot results if matplotlib is available
try:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(time * 1e3, v_in, label='V(in)', alpha=0.7)
ax.plot(time * 1e3, v_out, label='V(out)', linewidth=2)
ax.set_xlabel('Time (ms)')
ax.set_ylabel('Voltage (V)')
ax.set_title('RC Low-Pass Filter Response')
ax.legend()
ax.grid(True, alpha=0.3)
plot_file = os.path.join(output_dir, "simple_rc.png")
plt.savefig(plot_file, dpi=150)
print(f"\nPlot saved to: {plot_file}")
plt.show()
except ImportError:
print("\nNote: Install matplotlib to generate plots")
if __name__ == "__main__":
main()

85
pyngspice/__init__.py

@ -0,0 +1,85 @@
"""
pyngspice - Python bindings for ngspice circuit simulator.
Provides embedded ngspice simulation with a SpiceRunner interface
compatible with pyTesla. Can serve as a drop-in alternative to LTspice
for cross-platform Tesla coil simulation.
Basic usage:
from pyngspice import NgspiceRunner, RawRead
runner = NgspiceRunner(working_directory="./output")
raw_file, log_file = runner.run("circuit.net")
raw = RawRead(raw_file)
trace = raw.get_trace("V(out)")
data = trace.get_wave(0) # numpy array
Low-level usage (PyLTSpice-compatible):
from pyngspice import SimRunner, RawRead
runner = SimRunner(output_folder="./output")
raw_file, log_file = runner.run_now("circuit.net")
"""
__version__ = "43.0.0"
__author__ = "ngspice team"
# On Windows with Python 3.8+, add MinGW DLL directory for dependent DLLs
import sys
import os
if sys.platform == 'win32' and hasattr(os, 'add_dll_directory'):
# MinGW runtime DLLs (libgcc_s_seh-1.dll, libstdc++-6.dll, libwinpthread-1.dll)
mingw_bin = r'C:\mingw64\bin'
if os.path.isdir(mingw_bin):
os.add_dll_directory(mingw_bin)
# Import the compiled C++ extension module
try:
from ._pyngspice import (
Simulator,
SimRunner,
RawRead,
Trace,
NgSpice,
)
_cpp_available = True
except ImportError as e:
_cpp_available = False
_cpp_error = str(e)
# Provide helpful error message
import warnings
warnings.warn(
f"Failed to import pyngspice C++ extension: {e}\n"
f"The embedded ngspice runner will not be available.\n"
f"Try: pip install -e . (from the ngspice source directory)",
ImportWarning,
stacklevel=2,
)
# Define stubs so imports don't fail
Simulator = None
SimRunner = None
RawRead = None
Trace = None
NgSpice = None
# Import Python-level API
from .runner import SpiceRunner, NgspiceRunner, SubprocessRunner, SimulationError
from .netlist import preprocess_netlist
__all__ = [
# High-level API (what pyTesla uses)
"SpiceRunner",
"NgspiceRunner",
"SubprocessRunner",
"SimulationError",
"preprocess_netlist",
# Low-level C++ bindings
"Simulator",
"SimRunner",
"RawRead",
"Trace",
"NgSpice",
# State
"_cpp_available",
]

518
pyngspice/netlist.py

@ -0,0 +1,518 @@
"""
Netlist pre-processor for ngspice compatibility.
Transforms LTspice-style netlist constructs into ngspice-compatible
equivalents. The primary transformation is converting Rser= parameters
on inductor lines into separate series resistor elements.
Transformations applied:
- Inductor Rser=: L1 p1 0 8.5u Rser=0.012 -> L1 + R_L1_ser
- Strip .backanno directives (LTspice-specific, benign)
Usage:
from pyngspice.netlist import preprocess_netlist
with open("circuit.net") as f:
original = f.read()
processed = preprocess_netlist(original)
"""
import re
from typing import List, Tuple
def preprocess_netlist(content: str) -> str:
"""Pre-process a SPICE netlist for ngspice compatibility.
Applies all necessary transformations to convert an LTspice-style
netlist into one that ngspice can parse correctly.
Args:
content: The raw netlist string (LTspice format)
Returns:
Processed netlist string ready for ngspice
"""
lines = content.splitlines()
output_lines = []
extra_components = [] # Series resistors to insert before .end
has_savecurrents = False
targeted_cap_names = set() # Capacitor names from .save i(C_*) directives
for line in lines:
stripped = line.strip()
# Skip empty lines (preserve them)
if not stripped:
output_lines.append(line)
continue
# Strip .backanno directives (LTspice-specific, ignored by ngspice)
if stripped.lower().startswith('.backanno'):
continue
# Detect and strip .options savecurrents (doesn't work in embedded mode)
if _is_savecurrents_option(stripped):
has_savecurrents = True
continue
# Handle continuation lines (start with +) — pass through
if stripped.startswith('+'):
output_lines.append(line)
continue
# Detect .save i(C_*) directives for targeted capacitor probing
if stripped.lower().startswith('.save'):
cap_names = _parse_save_cap_currents(stripped)
if cap_names:
targeted_cap_names.update(name.upper() for name in cap_names)
output_lines.append(line)
continue
# Handle inductor lines with Rser= parameter
if stripped[0].upper() == 'L' and 'rser' in stripped.lower():
processed_line, extras = _process_inductor_rser(stripped)
if extras:
output_lines.append(processed_line)
extra_components.extend(extras)
continue
output_lines.append(line)
# Insert extra components (series resistors) just before .end
if extra_components:
result = []
for line in output_lines:
if line.strip().lower() == '.end':
result.append('* --- pyngspice: inductor series resistance expansion ---')
result.extend(extra_components)
result.append(line)
output_lines = result
# Insert 0V voltage source probes for capacitor current measurement
if has_savecurrents:
output_lines = _insert_capacitor_probes(output_lines)
# Expand .options savecurrents into explicit .save directives
if has_savecurrents:
output_lines = _expand_savecurrents(output_lines)
# Targeted capacitor probing from .save i(C_*) directives
# Only active when savecurrents is NOT present (savecurrents probes everything already)
if targeted_cap_names and not has_savecurrents:
output_lines = _insert_targeted_capacitor_probes(output_lines, targeted_cap_names)
output_lines = _rewrite_save_directives(output_lines, targeted_cap_names)
return '\n'.join(output_lines)
# Pattern for inductor lines with Rser= parameter
# Matches: L<name> <node+> <node-> <value> Rser=<value> [other params...]
# Value can be: number, number with suffix (8.5u, 100n, 1.2k), parameter ref ({Lpri}),
# or scientific notation (1.5e-6)
_INDUCTOR_RSER_PATTERN = re.compile(
r'^(L\w+)' # Group 1: Inductor name (L1, Lprimary, etc.)
r'\s+'
r'(\S+)' # Group 2: Positive node
r'\s+'
r'(\S+)' # Group 3: Negative node
r'\s+'
r'(\S+)' # Group 4: Inductance value
r'\s+'
r'Rser\s*=\s*' # Rser= keyword
r'(\S+)' # Group 5: Series resistance value
r'(.*)', # Group 6: Remaining parameters (Rpar=, Cpar=, etc.)
re.IGNORECASE
)
def _process_inductor_rser(line: str) -> Tuple[str, List[str]]:
"""Process Rser= parameter on an inductor line.
Transforms:
L1 p1 0 8.5u Rser=0.012
Into:
L1 p1 _rser_L1 8.5u
Plus extra line:
R_L1_ser _rser_L1 0 0.012
The intermediate node name uses _rser_<name> prefix to avoid
collisions with user-defined node names.
Args:
line: The inductor line to process
Returns:
Tuple of (modified_line, list_of_extra_lines).
If no Rser= found, returns (original_line, []).
"""
match = _INDUCTOR_RSER_PATTERN.match(line.strip())
if not match:
return line, []
name = match.group(1) # e.g., L1
node_p = match.group(2) # e.g., p1
node_n = match.group(3) # e.g., 0
inductance = match.group(4) # e.g., 8.5u
rser_val = match.group(5) # e.g., 0.012
remaining = match.group(6).strip() # e.g., Cpar=10p
# Create unique intermediate node name
int_node = f"_rser_{name}"
# Build modified inductor line (inductor connects to intermediate node)
new_inductor = f"{name} {node_p} {int_node} {inductance}"
# Preserve any remaining parameters (Rpar=, Cpar=, etc.)
# Strip any additional Rser-like params that ngspice doesn't support
if remaining:
# Remove Rpar= and Cpar= as well if present (ngspice doesn't support these either)
cleaned = _strip_ltspice_inductor_params(remaining)
if cleaned:
new_inductor += f" {cleaned}"
# Build series resistor line
resistor = f"R_{name}_ser {int_node} {node_n} {rser_val}"
return new_inductor, [resistor]
# SPICE component prefixes whose current can be saved with i(name)
# K (coupling) is excluded — it has no "through" current
_COMPONENT_PREFIXES = set('RCLVIDEFJMQBXGHrclvidefjmqbxgh')
def _is_savecurrents_option(line: str) -> bool:
"""Check if a line is .options savecurrents (any case, any spacing)."""
lowered = line.strip().lower()
if not lowered.startswith('.options') and not lowered.startswith('.option'):
return False
return 'savecurrents' in lowered
# Pattern to extract i(name) references from .save directives
_SAVE_CURRENT_PATTERN = re.compile(r'i\((\w+)\)', re.IGNORECASE)
def _parse_save_cap_currents(line: str) -> list:
"""Extract capacitor names from .save i(C_name) directives.
Parses a .save directive line and returns names of capacitors
whose currents are being saved. Non-capacitor names (V1, R1, L1)
are ignored.
Args:
line: A .save directive line (e.g., ".save i(C_mmc) i(V1)")
Returns:
List of capacitor names found (e.g., ["C_mmc"])
"""
if not line.strip().lower().startswith('.save'):
return []
return [m.group(1) for m in _SAVE_CURRENT_PATTERN.finditer(line)
if m.group(1)[0].upper() == 'C']
def _expand_savecurrents(lines: List[str]) -> List[str]:
"""Replace .options savecurrents with explicit .save directives.
Scans the netlist for all component names and generates:
.save all
.save i(V1) i(C1) i(R1) ...
This is needed because .options savecurrents doesn't work reliably
in ngspice's embedded (shared library) mode — the write command
silently fails to produce a .raw file.
Args:
lines: Processed netlist lines (Rser already expanded, savecurrents already stripped)
Returns:
Modified lines with .save directives inserted before .end
"""
component_names = _collect_component_names(lines)
if not component_names:
return lines
# Build .save directives
save_lines = [
'* --- pyngspice: explicit current saves (expanded from .options directive) ---',
'.save all',
]
# Group i(name) saves into lines of reasonable length
current_saves = [f'i({name})' for name in component_names]
# Put them all on one .save line (ngspice handles long lines fine)
save_lines.append('.save ' + ' '.join(current_saves))
# Insert before .end
result = []
for line in lines:
if line.strip().lower() == '.end':
result.extend(save_lines)
result.append(line)
return result
def _collect_component_names(lines: List[str]) -> List[str]:
"""Extract component names from netlist lines.
Returns names of components whose current can be saved with i(name).
Skips: comments, directives, continuations, K elements, blank lines.
Args:
lines: Netlist lines to scan
Returns:
List of component names in order of appearance
"""
names = []
for line in lines:
stripped = line.strip()
if not stripped:
continue
first_char = stripped[0]
# Skip comments, directives, continuations
if first_char in ('*', '.', '+'):
continue
# Skip K elements (coupling coefficients)
if first_char in ('K', 'k'):
continue
# Check if it's a component line
if first_char in _COMPONENT_PREFIXES:
# Component name is the first token
tokens = stripped.split()
if tokens:
names.append(tokens[0])
return names
def _insert_capacitor_probes(lines: List[str]) -> List[str]:
"""Insert 0V voltage source probes in series with capacitors.
ngspice embedded mode silently ignores .save i(capacitor) directives.
The workaround is to insert a 0V voltage source in series with each
capacitor V-source currents are always saved. The trace is later
renamed from i(v_probe_Cname) back to i(Cname) in raw file post-processing.
Only processes top-level capacitors (skips those inside .subckt blocks).
Transforms:
C_mmc vin p1 0.03u
Into:
C_mmc _probe_C_mmc p1 0.03u
Plus extra line before .end:
V_probe_C_mmc vin _probe_C_mmc 0
Args:
lines: Processed netlist lines (Rser already expanded, savecurrents stripped)
Returns:
Modified lines with capacitor probes inserted
"""
modified_lines = []
probe_lines = []
subckt_depth = 0
for line in lines:
stripped = line.strip()
# Track .subckt nesting
if stripped.lower().startswith('.subckt'):
subckt_depth += 1
modified_lines.append(line)
continue
elif stripped.lower().startswith('.ends'):
subckt_depth -= 1
modified_lines.append(line)
continue
# Only process top-level capacitors
if subckt_depth == 0 and stripped and stripped[0].upper() == 'C':
mod_line, probe = _process_capacitor_probe(stripped)
if probe:
modified_lines.append(mod_line)
probe_lines.append(probe)
continue
modified_lines.append(line)
# Insert probe V sources before .end
if probe_lines:
result = []
for line in modified_lines:
if line.strip().lower() == '.end':
result.append('* --- pyngspice: capacitor current probes ---')
result.extend(probe_lines)
result.append(line)
return result
return modified_lines
def _insert_targeted_capacitor_probes(lines: List[str], target_caps: set) -> List[str]:
"""Insert 0V voltage source probes for specific capacitors only.
Like _insert_capacitor_probes(), but only processes capacitors whose
names (case-insensitive) are in target_caps. This avoids the performance
penalty of probing all capacitors when only a few currents are needed.
Args:
lines: Processed netlist lines
target_caps: Set of capacitor names to probe (uppercase for comparison)
Returns:
Modified lines with targeted capacitor probes inserted
"""
modified_lines = []
probe_lines = []
subckt_depth = 0
for line in lines:
stripped = line.strip()
# Track .subckt nesting
if stripped.lower().startswith('.subckt'):
subckt_depth += 1
modified_lines.append(line)
continue
elif stripped.lower().startswith('.ends'):
subckt_depth -= 1
modified_lines.append(line)
continue
# Only process top-level capacitors that are in the target set
if subckt_depth == 0 and stripped and stripped[0].upper() == 'C':
tokens = stripped.split()
if tokens and tokens[0].upper() in target_caps:
mod_line, probe = _process_capacitor_probe(stripped)
if probe:
modified_lines.append(mod_line)
probe_lines.append(probe)
continue
modified_lines.append(line)
# Insert probe V sources before .end
if probe_lines:
result = []
for line in modified_lines:
if line.strip().lower() == '.end':
result.append('* --- pyngspice: targeted capacitor current probes ---')
result.extend(probe_lines)
result.append(line)
return result
return modified_lines
def _process_capacitor_probe(line: str) -> Tuple[str, str]:
"""Insert a 0V voltage source probe in series with a capacitor.
Transforms:
C_mmc vin p1 0.03u
Into modified line:
C_mmc _probe_C_mmc p1 0.03u
And probe line:
V_probe_C_mmc vin _probe_C_mmc 0
Args:
line: A capacitor line (must start with C)
Returns:
(modified_cap_line, probe_v_source_line).
If parsing fails, returns (original_line, "").
"""
tokens = line.strip().split()
if len(tokens) < 4:
return line, ""
name = tokens[0] # C_mmc
node1 = tokens[1] # vin (positive node)
node2 = tokens[2] # p1 (negative node)
rest = ' '.join(tokens[3:]) # 0.03u [params...]
int_node = f"_probe_{name}"
modified_cap = f"{name} {int_node} {node2} {rest}"
probe_source = f"V_probe_{name} {node1} {int_node} 0"
return modified_cap, probe_source
def _rewrite_save_directives(lines: List[str], probed_caps: set) -> List[str]:
"""Rewrite .save directives to reference probe V-sources instead of capacitors.
When targeted capacitor probing is active:
- .save i(C_mmc) -> .save i(V_probe_C_mmc)
- .save i(V1) i(C_mmc) -> .save i(V1) i(V_probe_C_mmc)
- .save v(out) -> .save v(out) (voltages unchanged)
- Ensures .save all is present (needed for voltage traces)
Args:
lines: Netlist lines (after probe insertion)
probed_caps: Set of capacitor names that were probed (uppercase for matching)
Returns:
Modified lines with .save directives rewritten
"""
has_save_all = False
result = []
for line in lines:
stripped = line.strip()
if stripped.lower().startswith('.save'):
# Check if this is .save all
if stripped.lower().split() == ['.save', 'all']:
has_save_all = True
result.append(line)
continue
# Rewrite i(C_name) references to i(V_probe_C_name)
def rewrite_cap_current(match):
name = match.group(1)
if name.upper() in probed_caps:
return f'i(V_probe_{name})'
return match.group(0)
rewritten = _SAVE_CURRENT_PATTERN.sub(rewrite_cap_current, stripped)
result.append(rewritten)
else:
result.append(line)
# Ensure .save all is present (user may have only .save i(C_mmc), but
# we still need voltage traces)
if not has_save_all:
final = []
for line in result:
if line.strip().lower() == '.end':
final.append('* --- pyngspice: auto-inserted .save all for voltage traces ---')
final.append('.save all')
final.append(line)
return final
return result
def _strip_ltspice_inductor_params(params: str) -> str:
"""Remove LTspice-specific inductor parameters that ngspice doesn't support.
LTspice supports Rser=, Rpar=, Cpar= on inductors. NGspice does not.
These need to be removed or converted to separate components.
For now, we just strip them. In the future, Rpar= and Cpar= could
be expanded into parallel R and C elements.
Args:
params: Remaining parameter string after the inductance value
Returns:
Cleaned parameter string with LTspice-specific params removed
"""
# Remove Rpar=<value> and Cpar=<value>
cleaned = re.sub(r'Rpar\s*=\s*\S+', '', params, flags=re.IGNORECASE)
cleaned = re.sub(r'Cpar\s*=\s*\S+', '', cleaned, flags=re.IGNORECASE)
return cleaned.strip()

444
pyngspice/runner.py

@ -0,0 +1,444 @@
"""
SpiceRunner interface and implementations for pyTesla integration.
Provides a common interface for running SPICE simulations with different
backends (embedded ngspice, subprocess ngspice, etc.). pyTesla uses
this interface to swap between LTspice and ngspice seamlessly.
Usage:
from pyngspice import NgspiceRunner
runner = NgspiceRunner(working_directory="./output")
raw_file, log_file = runner.run("circuit.net")
# Or with auto-detection:
from pyngspice.runner import get_runner
runner = get_runner("./output")
"""
import os
import shutil
import subprocess
import sys
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional, Tuple
from .netlist import preprocess_netlist
def _build_probe_mapping(processed_netlist_path: str) -> dict:
"""Build a mapping from probe trace names to original capacitor trace names.
Reads the processed netlist to find V_probe_* lines and builds:
{'i(v_probe_c_mmc)': 'i(c_mmc)', ...}
All names are lowercased to match ngspice's raw file convention.
Args:
processed_netlist_path: Path to the preprocessed netlist file
Returns:
Dict mapping probe trace names to original capacitor trace names
"""
mapping = {}
try:
with open(processed_netlist_path, 'r') as f:
for line in f:
tokens = line.strip().split()
if not tokens:
continue
name = tokens[0]
if name.upper().startswith('V_PROBE_'):
# V_probe_C_mmc -> C_mmc
cap_name = name[8:] # strip 'V_probe_'
old_trace = f'i({name.lower()})'
new_trace = f'i({cap_name.lower()})'
mapping[old_trace] = new_trace
except (OSError, IOError):
pass
return mapping
def _postprocess_raw_file(raw_file: str, processed_netlist_path: str) -> None:
"""Rename capacitor probe traces in raw file header.
After simulation, the raw file contains traces like i(v_probe_c_mmc).
This function renames them back to i(c_mmc) so pyTesla sees the
expected capacitor current names.
Only modifies the ASCII header; the binary data section is untouched.
Args:
raw_file: Path to the .raw file to post-process
processed_netlist_path: Path to the preprocessed netlist (to find probes)
"""
mapping = _build_probe_mapping(processed_netlist_path)
if not mapping:
return
try:
with open(raw_file, 'rb') as f:
content = f.read()
except (OSError, IOError):
return
# Find header/data boundary
header_end = -1
for marker in [b'Binary:\n', b'Binary:\r\n', b'Values:\n', b'Values:\r\n']:
pos = content.find(marker)
if pos >= 0:
header_end = pos + len(marker)
break
if header_end < 0:
return # Can't find boundary, skip
header = content[:header_end].decode('ascii', errors='replace')
data = content[header_end:]
# Apply renames in header
for old_name, new_name in mapping.items():
header = header.replace(old_name, new_name)
with open(raw_file, 'wb') as f:
f.write(header.encode('ascii'))
f.write(data)
class SimulationError(Exception):
"""Raised when a SPICE simulation fails."""
def __init__(self, message: str, log_content: str = None):
super().__init__(message)
self.log_content = log_content
class SpiceRunner(ABC):
"""Abstract base class for SPICE simulator runners.
This interface is used by pyTesla to run simulations with different
SPICE backends (LTspice, ngspice embedded, ngspice subprocess).
All implementations produce .raw and .log files that can be parsed
with PyLTSpice's RawRead or pyngspice's RawRead.
"""
def __init__(self, working_directory: str):
"""Initialize with working directory for netlist and output files.
Args:
working_directory: Directory where output files will be written.
Created if it doesn't exist.
"""
self.working_directory = os.path.abspath(working_directory)
os.makedirs(self.working_directory, exist_ok=True)
@abstractmethod
def run(self, netlist_path: str, timeout: int = None) -> Tuple[str, str]:
"""Execute SPICE simulation.
Args:
netlist_path: Absolute or relative path to .net/.cir file
timeout: Optional timeout in seconds
Returns:
(raw_file_path, log_file_path) - absolute paths to output files
Raises:
SimulationError: If simulation fails or times out
FileNotFoundError: If netlist file doesn't exist
"""
...
@staticmethod
@abstractmethod
def detect() -> bool:
"""Return True if this SPICE engine is available."""
...
@staticmethod
@abstractmethod
def get_executable_path() -> Optional[str]:
"""Auto-detect and return path to executable, or None."""
...
def _preprocess_netlist(self, netlist_path: str) -> str:
"""Pre-process netlist for ngspice compatibility.
If the netlist needs translation (Rser= on inductors, etc.),
writes a processed copy to the working directory and returns
its path. Otherwise returns the original path.
Args:
netlist_path: Path to the original netlist
Returns:
Path to the netlist to actually simulate (may be original or processed copy)
"""
netlist_path = os.path.abspath(netlist_path)
if not os.path.isfile(netlist_path):
raise FileNotFoundError(f"Netlist not found: {netlist_path}")
with open(netlist_path, 'r') as f:
original = f.read()
processed = preprocess_netlist(original)
if processed == original:
return netlist_path
# Write processed netlist to working directory
stem = Path(netlist_path).stem
out_path = os.path.join(self.working_directory, f"{stem}_ngspice.net")
with open(out_path, 'w') as f:
f.write(processed)
return out_path
class NgspiceRunner(SpiceRunner):
"""NgspiceRunner using embedded ngspice via pybind11 C++ extension.
This is the primary runner. It uses the statically-linked ngspice
library through pybind11 bindings no external ngspice installation
is needed.
The embedded approach is faster than subprocess invocation since there's
no process spawn overhead and the simulator is already loaded in memory.
"""
def __init__(self, working_directory: str = "."):
super().__init__(working_directory)
from . import _cpp_available
if not _cpp_available:
from . import _cpp_error
raise ImportError(
f"pyngspice C++ extension not available: {_cpp_error}\n"
f"Use SubprocessRunner as a fallback, or rebuild with: pip install -e ."
)
from ._pyngspice import SimRunner as _CppSimRunner
self._runner = _CppSimRunner(output_folder=self.working_directory)
def run(self, netlist_path: str, timeout: int = None) -> Tuple[str, str]:
"""Run simulation using embedded ngspice.
Args:
netlist_path: Path to .net/.cir netlist file
timeout: Optional timeout in seconds (not yet implemented for embedded mode)
Returns:
(raw_file_path, log_file_path) as absolute paths
Raises:
SimulationError: If simulation fails
"""
processed_path = self._preprocess_netlist(netlist_path)
try:
raw_file, log_file = self._runner.run_now(processed_path)
except Exception as e:
raise SimulationError(f"Simulation failed: {e}") from e
# Normalize to absolute paths
raw_file = os.path.abspath(raw_file)
log_file = os.path.abspath(log_file)
if not os.path.isfile(raw_file):
raise SimulationError(
f"Simulation completed but raw file not found: {raw_file}"
)
# Post-process: rename capacitor probe traces in raw file header
_postprocess_raw_file(raw_file, processed_path)
return raw_file, log_file
@staticmethod
def detect() -> bool:
"""Return True if the embedded C++ extension is available."""
try:
from . import _cpp_available
return _cpp_available
except ImportError:
return False
@staticmethod
def get_executable_path() -> Optional[str]:
"""Return None — embedded mode has no separate executable."""
return None
class SubprocessRunner(SpiceRunner):
"""Fallback runner using ngspice as a subprocess.
Invokes ngspice in batch mode via the command line. Requires ngspice
to be installed and available on PATH (or specified explicitly).
This is useful when:
- The C++ extension is not compiled
- You need to use a specific ngspice build
- Debugging simulation issues with ngspice's own output
"""
def __init__(self, working_directory: str = ".", executable: str = None):
"""Initialize with optional explicit path to ngspice executable.
Args:
working_directory: Directory for output files
executable: Path to ngspice executable. If None, auto-detects.
"""
super().__init__(working_directory)
self._executable = executable or self._find_ngspice()
if self._executable is None:
raise FileNotFoundError(
"ngspice executable not found. Install ngspice or specify "
"the path explicitly via the 'executable' parameter."
)
def run(self, netlist_path: str, timeout: int = None) -> Tuple[str, str]:
"""Run simulation using ngspice subprocess.
Invokes: ngspice -b -r <output.raw> -o <output.log> <netlist>
Args:
netlist_path: Path to .net/.cir netlist file
timeout: Optional timeout in seconds
Returns:
(raw_file_path, log_file_path) as absolute paths
Raises:
SimulationError: If simulation fails or times out
"""
processed_path = self._preprocess_netlist(netlist_path)
stem = Path(processed_path).stem
raw_file = os.path.join(self.working_directory, f"{stem}.raw")
log_file = os.path.join(self.working_directory, f"{stem}.log")
cmd = [
self._executable,
'-b', # Batch mode (no GUI)
'-r', raw_file, # Raw output file
'-o', log_file, # Log output file
processed_path, # Input netlist
]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
cwd=self.working_directory,
)
except subprocess.TimeoutExpired:
raise SimulationError(
f"Simulation timed out after {timeout} seconds"
)
except FileNotFoundError:
raise SimulationError(
f"ngspice executable not found: {self._executable}"
)
# Check for errors
if result.returncode != 0:
log_content = None
if os.path.isfile(log_file):
with open(log_file, 'r') as f:
log_content = f.read()
raise SimulationError(
f"ngspice exited with code {result.returncode}: {result.stderr}",
log_content=log_content,
)
if not os.path.isfile(raw_file):
raise SimulationError(
f"Simulation completed but raw file not found: {raw_file}\n"
f"stderr: {result.stderr}"
)
return os.path.abspath(raw_file), os.path.abspath(log_file)
@staticmethod
def detect() -> bool:
"""Return True if ngspice executable is found on PATH."""
return SubprocessRunner._find_ngspice() is not None
@staticmethod
def get_executable_path() -> Optional[str]:
"""Return path to ngspice executable, or None if not found."""
return SubprocessRunner._find_ngspice()
@staticmethod
def _find_ngspice() -> Optional[str]:
"""Search for ngspice executable.
Checks:
1. PATH (via shutil.which)
2. Common install locations on each platform
"""
# Check PATH first
found = shutil.which("ngspice")
if found:
return found
# Check common install locations
if sys.platform == 'win32':
candidates = [
r"C:\Program Files\ngspice\bin\ngspice.exe",
r"C:\Program Files (x86)\ngspice\bin\ngspice.exe",
os.path.expanduser(r"~\ngspice\bin\ngspice.exe"),
]
elif sys.platform == 'darwin':
candidates = [
"/usr/local/bin/ngspice",
"/opt/homebrew/bin/ngspice",
]
else: # Linux
candidates = [
"/usr/bin/ngspice",
"/usr/local/bin/ngspice",
"/snap/bin/ngspice",
]
for path in candidates:
if os.path.isfile(path):
return path
return None
def get_runner(working_directory: str = ".",
backend: str = "auto") -> SpiceRunner:
"""Factory function to get the best available SpiceRunner.
Args:
working_directory: Directory for simulation output files
backend: One of "auto", "embedded", "subprocess".
- "auto": try embedded first, fall back to subprocess
- "embedded": use NgspiceRunner (requires C++ extension)
- "subprocess": use SubprocessRunner (requires ngspice on PATH)
Returns:
A SpiceRunner instance
Raises:
RuntimeError: If no suitable backend is found
"""
if backend == "embedded":
return NgspiceRunner(working_directory)
elif backend == "subprocess":
return SubprocessRunner(working_directory)
elif backend == "auto":
# Try embedded first (faster, no external dependency)
if NgspiceRunner.detect():
return NgspiceRunner(working_directory)
# Fall back to subprocess
if SubprocessRunner.detect():
return SubprocessRunner(working_directory)
raise RuntimeError(
"No ngspice backend available. Either:\n"
" 1. Build the C++ extension: pip install -e .\n"
" 2. Install ngspice and add it to PATH"
)
else:
raise ValueError(f"Unknown backend: {backend!r}. Use 'auto', 'embedded', or 'subprocess'.")

79
pyproject.toml

@ -0,0 +1,79 @@
[build-system]
requires = [
"scikit-build-core>=0.8",
"pybind11>=2.11",
"numpy>=1.20",
]
build-backend = "scikit_build_core.build"
[project]
name = "pyngspice"
version = "43.0.0"
description = "Python bindings for ngspice circuit simulator (pyTesla backend)"
license = {text = "BSD-3-Clause"}
requires-python = ">=3.9"
authors = [
{name = "ngspice team", email = "ngspice-devel@lists.sourceforge.net"},
]
keywords = ["spice", "circuit", "simulation", "electronics", "EDA", "tesla-coil"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: BSD License",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Operating System :: MacOS",
"Programming Language :: C",
"Programming Language :: C++",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
]
dependencies = [
"numpy>=1.20",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-cov>=4.0",
]
[project.urls]
Homepage = "https://ngspice.sourceforge.io/"
Documentation = "https://ngspice.sourceforge.io/docs.html"
Repository = "https://sourceforge.net/p/ngspice/ngspice/"
Issues = "https://sourceforge.net/p/ngspice/bugs/"
[tool.scikit-build]
cmake.version = ">=3.18"
cmake.build-type = "Release"
cmake.args = ["-G", "MinGW Makefiles", "-DCMAKE_VERBOSE_MAKEFILE=ON"]
ninja.make-fallback = true
wheel.packages = ["pyngspice"]
wheel.install-dir = "pyngspice"
build-dir = "build/{wheel_tag}"
editable.mode = "inplace"
[tool.scikit-build.cmake.define]
BUILD_PYTHON_BINDINGS = "ON"
ENABLE_XSPICE = "ON"
ENABLE_OSDI = "ON"
[tool.cibuildwheel]
build = "cp39-* cp310-* cp311-* cp312-*"
skip = "*-musllinux_* *-manylinux_i686"
[tool.cibuildwheel.linux]
before-all = "yum install -y bison flex"
manylinux-x86_64-image = "manylinux2014"
[tool.cibuildwheel.macos]
before-all = "brew install bison flex"
[tool.cibuildwheel.windows]
before-all = "choco install winflexbison3 -y"

340
src/bindings/module.cpp

@ -0,0 +1,340 @@
/**
* @file module.cpp
* @brief Main pybind11 module definition for pyngspice Python extension
*
* This file defines the Python module interface using pybind11,
* exposing the ngspice simulation classes to Python.
*/
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <pybind11/functional.h>
#include "simulator.h"
#include "sim_runner.h"
#include "raw_read.h"
#include "trace.h"
namespace py = pybind11;
/**
* @brief Convert std::vector<double> to numpy array
*/
py::array_t<double> vector_to_numpy(const std::vector<double>& vec) {
return py::array_t<double>(vec.size(), vec.data());
}
/**
* @brief Convert std::vector<std::complex<double>> to numpy array
*/
py::array_t<std::complex<double>> complex_vector_to_numpy(
const std::vector<std::complex<double>>& vec) {
return py::array_t<std::complex<double>>(vec.size(), vec.data());
}
PYBIND11_MODULE(_pyngspice, m) {
m.doc() = R"pbdoc(
pyngspice Python Extension
--------------------------
Native Python bindings for the ngspice circuit simulator.
Provides a PyLTSpice-compatible API for running simulations
and accessing results. Designed as a backend for pyTesla.
Basic usage:
from pyngspice import SimRunner, RawRead
runner = SimRunner(output_folder="./output")
raw_file, log_file = runner.run_now("circuit.net")
raw = RawRead(raw_file)
trace = raw.get_trace("V(out)")
data = trace.get_wave(0) # numpy array
)pbdoc";
m.attr("__version__") = "43.0.0";
// =========================================================================
// Trace class
// =========================================================================
py::class_<ngspice::Trace, std::shared_ptr<ngspice::Trace>>(m, "Trace",
R"pbdoc(
Represents a single trace/waveform from simulation results.
Provides access to simulation data as numpy arrays.
Supports both real and complex data (for AC analysis).
)pbdoc")
.def_property_readonly("name", &ngspice::Trace::name,
"The trace name (e.g., 'V(out)')")
.def_property_readonly("is_complex", &ngspice::Trace::is_complex,
"True if trace contains complex data")
.def("get_wave", [](const ngspice::Trace& self, size_t step) {
return vector_to_numpy(self.get_wave(step));
},
py::arg("step") = 0,
R"pbdoc(
Get waveform data for a specific step.
Args:
step: Step index (0 for single-step simulations)
Returns:
numpy array of data points
)pbdoc")
.def("get_wave_complex", [](const ngspice::Trace& self, size_t step) {
return complex_vector_to_numpy(self.get_wave_complex(step));
},
py::arg("step") = 0,
"Get complex waveform data for a specific step")
.def("__len__", &ngspice::Trace::size)
.def("__repr__", [](const ngspice::Trace& self) {
return "<Trace '" + self.name() + "' (" +
std::to_string(self.size()) + " points)>";
});
// =========================================================================
// RawRead class
// =========================================================================
py::class_<ngspice::RawRead>(m, "RawRead",
R"pbdoc(
Parser for ngspice .raw simulation output files.
Provides PyLTSpice-compatible API for accessing trace data.
Supports both ASCII and binary raw file formats.
Example:
raw = RawRead("simulation.raw")
traces = raw.get_trace_names()
voltage = raw.get_trace("V(out)").get_wave(0)
)pbdoc")
.def(py::init<const std::string&>(),
py::arg("filepath"),
"Load and parse a .raw file")
.def(py::init<>(),
"Create an empty RawRead object")
.def_property_readonly("title", &ngspice::RawRead::title,
"The plot title from the raw file")
.def_property_readonly("date", &ngspice::RawRead::date,
"The simulation date")
.def_property_readonly("plot_name", &ngspice::RawRead::plot_name,
"The plot name")
.def_property_readonly("analysis_type", &ngspice::RawRead::analysis_type,
"The analysis type (tran, ac, dc, etc.)")
.def("get_trace_names", &ngspice::RawRead::get_trace_names,
"Get list of all trace names")
.def("get_trace", &ngspice::RawRead::get_trace,
py::arg("name"),
R"pbdoc(
Get a specific trace by name.
Args:
name: Trace name (e.g., "V(out)" or "time")
Returns:
Trace object
Raises:
KeyError: If trace not found
)pbdoc")
.def("has_trace", &ngspice::RawRead::has_trace,
py::arg("name"),
"Check if a trace exists")
.def("get_steps", &ngspice::RawRead::get_steps,
"Get list of step indices (for parametric sweeps)")
.def_property_readonly("steps", &ngspice::RawRead::steps,
"Step parameter values (for .step simulations)")
.def_property_readonly("num_points", &ngspice::RawRead::num_points,
"Number of data points")
.def_property_readonly("num_variables", &ngspice::RawRead::num_variables,
"Number of variables/traces")
.def_property_readonly("is_complex", &ngspice::RawRead::is_complex,
"True if data is complex (AC analysis)")
.def_property_readonly("is_real", &ngspice::RawRead::is_real,
"True if data is real")
.def("__repr__", [](const ngspice::RawRead& self) {
return "<RawRead '" + self.filepath() + "' (" +
std::to_string(self.num_variables()) + " traces, " +
std::to_string(self.num_points()) + " points)>";
})
.def("__contains__", &ngspice::RawRead::has_trace)
.def("__getitem__", &ngspice::RawRead::get_trace);
// =========================================================================
// SimRunner class
// =========================================================================
py::class_<ngspice::SimRunner>(m, "SimRunner",
R"pbdoc(
PyLTSpice-compatible simulation runner.
Provides methods for running ngspice simulations on netlists
and retrieving results.
Example:
runner = SimRunner(output_folder="./output")
raw_file, log_file = runner.run_now("circuit.net")
# Or async:
runner.run("circuit.net")
for raw_file, log_file in runner:
process_results(raw_file)
)pbdoc")
.def(py::init([](const std::string& output_folder, py::object /*simulator*/) {
// simulator argument is ignored - for PyLTSpice API compatibility
return std::make_unique<ngspice::SimRunner>(output_folder, nullptr);
}),
py::arg("output_folder") = ".",
py::arg("simulator") = py::none(),
R"pbdoc(
Create a SimRunner.
Args:
output_folder: Directory for output files
simulator: Simulator class (ignored, for API compatibility)
)pbdoc")
.def("run_now", &ngspice::SimRunner::run_now,
py::arg("netlist"),
R"pbdoc(
Run simulation synchronously (blocking).
Args:
netlist: Path to the netlist file
Returns:
Tuple of (raw_file_path, log_file_path)
)pbdoc")
.def("run", &ngspice::SimRunner::run,
py::arg("netlist"),
R"pbdoc(
Queue a simulation for async execution.
Args:
netlist: Path to the netlist file
Returns:
True if simulation was queued
)pbdoc")
.def("wait_completion", &ngspice::SimRunner::wait_completion,
"Wait for all queued simulations to complete")
.def_property_readonly("output_folder", &ngspice::SimRunner::output_folder,
"Output folder path")
.def_property("parallel_sims",
&ngspice::SimRunner::parallel_sims,
&ngspice::SimRunner::set_parallel_sims,
"Maximum parallel simulations")
.def("__iter__", [](ngspice::SimRunner& self) {
return py::make_iterator(self.begin(), self.end());
}, py::keep_alive<0, 1>());
// =========================================================================
// NgSpice class (compatibility)
// =========================================================================
py::class_<ngspice::NgSpice>(m, "NgSpice",
R"pbdoc(
NgSpice simulator class (for PyLTSpice compatibility).
This class exists for API compatibility with PyLTSpice.
Since we use the embedded ngspice library, most settings
are ignored.
)pbdoc")
.def_readwrite_static("spice_exe", &ngspice::NgSpice::spice_exe,
"Path to ngspice executable (ignored - using embedded ngspice)")
.def_static("version", &ngspice::NgSpice::version,
"Get the ngspice version string");
// =========================================================================
// Simulator class (advanced usage)
// =========================================================================
py::class_<ngspice::Simulator>(m, "Simulator",
R"pbdoc(
Low-level ngspice simulator wrapper.
For advanced usage when you need direct control over
the simulation engine. Most users should use SimRunner.
)pbdoc")
.def(py::init<int>(),
py::arg("instance_id") = 0,
"Create a Simulator instance")
.def("initialize", &ngspice::Simulator::initialize,
"Initialize the ngspice engine")
.def("is_initialized", &ngspice::Simulator::is_initialized,
"Check if simulator is initialized")
.def("load_netlist", &ngspice::Simulator::load_netlist,
py::arg("filepath"),
"Load a netlist from file")
.def("command", &ngspice::Simulator::command,
py::arg("command"),
"Execute a SPICE command")
.def("run", &ngspice::Simulator::run,
"Run the simulation (blocking)")
.def("run_async", &ngspice::Simulator::run_async,
"Run simulation in background thread")
.def("is_running", &ngspice::Simulator::is_running,
"Check if simulation is currently running")
.def("halt", &ngspice::Simulator::halt,
"Halt a running simulation")
.def("resume", &ngspice::Simulator::resume,
"Resume a halted simulation")
.def("reset", &ngspice::Simulator::reset,
"Reset the simulator state")
.def("current_plot", &ngspice::Simulator::current_plot,
"Get the current plot name")
.def("all_plots", &ngspice::Simulator::all_plots,
"Get all plot names")
.def("all_vectors", &ngspice::Simulator::all_vectors,
py::arg("plot_name") = "",
"Get all vector names in a plot")
.def("get_output", &ngspice::Simulator::get_output,
"Get accumulated output messages")
.def("clear_output", &ngspice::Simulator::clear_output,
"Clear accumulated output");
}

101
src/cpp/callbacks.cpp

@ -0,0 +1,101 @@
/**
* @file callbacks.cpp
* @brief Callback handler implementations
*/
#include "callbacks.h"
#include <regex>
#include <map>
namespace ngspice {
CallbackManager& CallbackManager::instance() {
static CallbackManager manager;
return manager;
}
void CallbackManager::register_callbacks(int instance_id,
OutputCallback output_cb,
StatusCallback status_cb,
ExitCallback exit_cb,
ThreadCallback thread_cb) {
std::lock_guard<std::mutex> lock(mutex_);
InstanceCallbacks& cbs = callbacks_[instance_id];
cbs.output = std::move(output_cb);
cbs.status = std::move(status_cb);
cbs.exit = std::move(exit_cb);
cbs.thread = std::move(thread_cb);
}
void CallbackManager::unregister_callbacks(int instance_id) {
std::lock_guard<std::mutex> lock(mutex_);
callbacks_.erase(instance_id);
}
void CallbackManager::on_output(int instance_id, const std::string& message) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = callbacks_.find(instance_id);
if (it != callbacks_.end() && it->second.output) {
it->second.output(message);
}
}
void CallbackManager::on_status(int instance_id, const std::string& status) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = callbacks_.find(instance_id);
if (it != callbacks_.end() && it->second.status) {
it->second.status(status);
}
}
void CallbackManager::on_exit(int instance_id, int status, bool immediate, bool quit) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = callbacks_.find(instance_id);
if (it != callbacks_.end() && it->second.exit) {
it->second.exit(status, immediate, quit);
}
}
void CallbackManager::on_thread_status(int instance_id, bool running) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = callbacks_.find(instance_id);
if (it != callbacks_.end() && it->second.thread) {
it->second.thread(running);
}
}
double parse_progress(const std::string& status) {
// Try to parse progress from various ngspice status formats
// Examples:
// "Reference value : 1.00000e-01"
// "Transient Analysis: 50% complete"
// "--dc: 1 of 10"
// Try percentage format
std::regex percent_re(R"((\d+(?:\.\d+)?)\s*%\s*complete)", std::regex::icase);
std::smatch match;
if (std::regex_search(status, match, percent_re)) {
return std::stod(match[1].str()) / 100.0;
}
// Try "X of Y" format
std::regex of_re(R"((\d+)\s+of\s+(\d+))");
if (std::regex_search(status, match, of_re)) {
double current = std::stod(match[1].str());
double total = std::stod(match[2].str());
if (total > 0) {
return current / total;
}
}
// Try reference value (for transient analysis)
std::regex ref_re(R"(Reference value\s*:\s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?))");
if (std::regex_search(status, match, ref_re)) {
// Can't determine progress without knowing the end time
return -1.0;
}
return -1.0; // Unknown progress
}
} // namespace ngspice

100
src/cpp/callbacks.h

@ -0,0 +1,100 @@
/**
* @file callbacks.h
* @brief Callback handler declarations for ngspice Python extension
*
* This file declares the callback functions that are registered with
* ngspice to receive simulation events, output, and data.
*/
#ifndef NGSPICE_PYTHON_CALLBACKS_H
#define NGSPICE_PYTHON_CALLBACKS_H
#include <string>
#include <functional>
#include <mutex>
#include <map>
namespace ngspice {
/**
* @brief Callback manager for routing ngspice callbacks
*
* Since ngspice uses C-style callbacks with a user data pointer,
* we need a mechanism to route callbacks to the correct Python objects.
*/
class CallbackManager {
public:
// Callback types
using OutputCallback = std::function<void(const std::string& message)>;
using StatusCallback = std::function<void(const std::string& status)>;
using ExitCallback = std::function<void(int status, bool immediate, bool quit)>;
using ProgressCallback = std::function<void(double progress)>;
using ThreadCallback = std::function<void(bool running)>;
/**
* @brief Get the singleton instance
*/
static CallbackManager& instance();
/**
* @brief Register callbacks for an instance
*/
void register_callbacks(int instance_id,
OutputCallback output_cb,
StatusCallback status_cb,
ExitCallback exit_cb,
ThreadCallback thread_cb);
/**
* @brief Unregister callbacks for an instance
*/
void unregister_callbacks(int instance_id);
/**
* @brief Route output message to registered callback
*/
void on_output(int instance_id, const std::string& message);
/**
* @brief Route status message to registered callback
*/
void on_status(int instance_id, const std::string& status);
/**
* @brief Route exit event to registered callback
*/
void on_exit(int instance_id, int status, bool immediate, bool quit);
/**
* @brief Route thread status to registered callback
*/
void on_thread_status(int instance_id, bool running);
private:
CallbackManager() = default;
~CallbackManager() = default;
CallbackManager(const CallbackManager&) = delete;
CallbackManager& operator=(const CallbackManager&) = delete;
struct InstanceCallbacks {
OutputCallback output;
StatusCallback status;
ExitCallback exit;
ThreadCallback thread;
};
std::mutex mutex_;
std::map<int, InstanceCallbacks> callbacks_;
};
/**
* @brief Parse ngspice status string for progress
* @param status Status string like "Reference value : 1.00000e-01"
* @return Progress value 0.0-1.0, or -1.0 if not parseable
*/
double parse_progress(const std::string& status);
} // namespace ngspice
#endif // NGSPICE_PYTHON_CALLBACKS_H

341
src/cpp/raw_read.cpp

@ -0,0 +1,341 @@
/**
* @file raw_read.cpp
* @brief RawRead class implementation - parses ngspice .raw files
*/
#include "raw_read.h"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <stdexcept>
namespace ngspice {
namespace {
// Trim whitespace from string
std::string trim(const std::string& str) {
size_t start = str.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "";
size_t end = str.find_last_not_of(" \t\r\n");
return str.substr(start, end - start + 1);
}
// Case-insensitive string compare
bool iequals(const std::string& a, const std::string& b) {
if (a.size() != b.size()) return false;
for (size_t i = 0; i < a.size(); ++i) {
if (std::tolower(a[i]) != std::tolower(b[i])) return false;
}
return true;
}
// Parse a header line "Key: Value"
std::pair<std::string, std::string> parse_header_line(const std::string& line) {
size_t colon = line.find(':');
if (colon == std::string::npos) {
return {"", ""};
}
return {trim(line.substr(0, colon)), trim(line.substr(colon + 1))};
}
} // anonymous namespace
RawRead::RawRead(const std::string& filepath) {
if (!parse(filepath)) {
throw std::runtime_error("Failed to parse raw file: " + filepath);
}
}
bool RawRead::parse(const std::string& filepath) {
filepath_ = filepath;
std::ifstream file(filepath, std::ios::binary);
if (!file.is_open()) {
throw std::runtime_error("Cannot open file: " + filepath);
}
// Parse header
if (!parse_header(file)) {
return false;
}
// Parse data section
if (is_binary_) {
if (!parse_binary_data(file)) {
return false;
}
} else {
if (!parse_ascii_data(file)) {
return false;
}
}
// Detect step boundaries
detect_steps();
return true;
}
bool RawRead::parse_header(std::istream& file) {
std::string line;
bool in_variables = false;
int var_index = 0;
while (std::getline(file, line)) {
line = trim(line);
if (line.empty()) continue;
// Check for end of header markers
if (iequals(line, "Binary:")) {
is_binary_ = true;
break;
}
if (iequals(line, "Values:")) {
is_binary_ = false;
break;
}
// Check for variables section
if (line.find("Variables:") == 0) {
in_variables = true;
var_index = 0;
continue;
}
if (in_variables) {
// Parse variable definition: "index name type"
std::istringstream iss(line);
int idx;
std::string name, type;
if (iss >> idx >> name >> type) {
VarInfo info;
info.index = idx;
info.name = name;
info.type = type;
var_info_.push_back(info);
// Determine if complex based on type
if (type == "voltage" || type == "current") {
// Will be set based on flags
}
}
continue;
}
// Parse header key-value pairs
auto [key, value] = parse_header_line(line);
if (key.empty()) continue;
if (iequals(key, "Title")) {
title_ = value;
} else if (iequals(key, "Date")) {
date_ = value;
} else if (iequals(key, "Plotname")) {
plot_name_ = value;
// Extract analysis type from plot name
if (plot_name_.find("Transient") != std::string::npos) {
analysis_type_ = "tran";
} else if (plot_name_.find("AC") != std::string::npos) {
analysis_type_ = "ac";
is_complex_ = true; // AC analysis has complex data
} else if (plot_name_.find("DC") != std::string::npos) {
analysis_type_ = "dc";
} else if (plot_name_.find("Operating Point") != std::string::npos) {
analysis_type_ = "op";
}
} else if (iequals(key, "Flags")) {
flags_ = value;
if (value.find("complex") != std::string::npos) {
is_complex_ = true;
}
} else if (iequals(key, "No. Variables")) {
num_variables_ = std::stoul(value);
} else if (iequals(key, "No. Points")) {
num_points_ = std::stoul(value);
}
}
// Create trace objects for each variable
for (const auto& var : var_info_) {
auto trace = std::make_shared<Trace>(var.name, is_complex_);
trace->reserve(num_points_);
traces_[var.name] = trace;
}
return true;
}
bool RawRead::parse_ascii_data(std::istream& file) {
std::string line;
size_t point_index = 0;
while (std::getline(file, line) && point_index < num_points_) {
line = trim(line);
if (line.empty()) continue;
// Check for step marker (ngspice uses point index 0 to mark steps)
size_t tab_pos = line.find('\t');
if (tab_pos != std::string::npos) {
// Format: "index\tvalue" or "index\treal,imag"
std::string index_str = line.substr(0, tab_pos);
std::string value_str = trim(line.substr(tab_pos + 1));
int idx = std::stoi(index_str);
// Mark step boundary if index is 0 and not first point
if (idx == 0 && point_index > 0) {
for (auto& [name, trace] : traces_) {
trace->mark_step_boundary();
}
}
// Parse value for first variable (usually time/frequency)
if (is_complex_) {
// Complex format: "real,imag"
size_t comma = value_str.find(',');
if (comma != std::string::npos) {
double real = std::stod(value_str.substr(0, comma));
double imag = std::stod(value_str.substr(comma + 1));
traces_[var_info_[0].name]->add_point(real, imag);
}
} else {
double value = std::stod(value_str);
traces_[var_info_[0].name]->add_point(value);
}
// Read remaining variables
for (size_t i = 1; i < var_info_.size(); ++i) {
if (!std::getline(file, line)) break;
line = trim(line);
if (is_complex_) {
size_t comma = line.find(',');
if (comma != std::string::npos) {
double real = std::stod(line.substr(0, comma));
double imag = std::stod(line.substr(comma + 1));
traces_[var_info_[i].name]->add_point(real, imag);
}
} else {
double value = std::stod(line);
traces_[var_info_[i].name]->add_point(value);
}
}
point_index++;
}
}
return true;
}
bool RawRead::parse_binary_data(std::istream& file) {
// ngspice binary format:
// - Real data: double values, interleaved by variable
// - Complex data: pairs of doubles (real, imag), interleaved
size_t value_size = sizeof(double);
size_t point_size = is_complex_
? num_variables_ * 2 * value_size // real + imag for each var
: num_variables_ * value_size; // just real for each var
std::vector<char> buffer(point_size);
for (size_t point = 0; point < num_points_; ++point) {
if (!file.read(buffer.data(), point_size)) {
// Partial read is OK - might have fewer points than declared
break;
}
// Check for step boundary (scale variable resets to initial value)
// This is a heuristic - ngspice may restart the scale var
if (is_complex_) {
// Complex data: read pairs of doubles
const double* data = reinterpret_cast<const double*>(buffer.data());
for (size_t i = 0; i < num_variables_; ++i) {
double real = data[i * 2];
double imag = data[i * 2 + 1];
traces_[var_info_[i].name]->add_point(real, imag);
}
} else {
// Real data: read doubles
const double* data = reinterpret_cast<const double*>(buffer.data());
for (size_t i = 0; i < num_variables_; ++i) {
traces_[var_info_[i].name]->add_point(data[i]);
}
}
}
return true;
}
void RawRead::detect_steps() {
// For stepped simulations (.step), detect where the scale variable
// (usually time or frequency) resets to a lower value
if (traces_.empty() || var_info_.empty()) return;
auto scale_trace = traces_[var_info_[0].name];
const auto& data = scale_trace->get_all_real_data();
if (data.size() < 2) return;
std::vector<size_t> boundaries;
boundaries.push_back(0); // First step starts at 0
// Look for decreases in the scale variable (indicates new step)
for (size_t i = 1; i < data.size(); ++i) {
if (data[i] < data[i - 1] * 0.9) { // 10% tolerance
boundaries.push_back(i);
}
}
if (boundaries.size() > 1) {
// Update all traces with step boundaries
for (auto& [name, trace] : traces_) {
trace->set_step_offsets(boundaries);
}
// Create step_params_ (we don't have parameter names from raw file)
for (size_t i = 0; i < boundaries.size(); ++i) {
step_params_.push_back({{"step", static_cast<double>(i)}});
}
}
}
std::vector<std::string> RawRead::get_trace_names() const {
std::vector<std::string> names;
names.reserve(var_info_.size());
for (const auto& var : var_info_) {
names.push_back(var.name);
}
return names;
}
std::shared_ptr<Trace> RawRead::get_trace(const std::string& name) const {
auto it = traces_.find(name);
if (it == traces_.end()) {
throw std::out_of_range("Trace not found: " + name);
}
return it->second;
}
bool RawRead::has_trace(const std::string& name) const {
return traces_.find(name) != traces_.end();
}
std::vector<int> RawRead::get_steps() const {
std::vector<int> steps;
size_t num = step_params_.empty() ? 1 : step_params_.size();
for (size_t i = 0; i < num; ++i) {
steps.push_back(static_cast<int>(i));
}
return steps;
}
} // namespace ngspice

183
src/cpp/raw_read.h

@ -0,0 +1,183 @@
/**
* @file raw_read.h
* @brief RawRead class for parsing ngspice .raw files
*
* This class provides PyLTSpice-compatible API for reading
* ngspice simulation result files.
*/
#ifndef NGSPICE_PYTHON_RAW_READ_H
#define NGSPICE_PYTHON_RAW_READ_H
#include <string>
#include <vector>
#include <map>
#include <memory>
#include "trace.h"
namespace ngspice {
/**
* @brief Parser for ngspice .raw simulation output files
*
* Supports both ASCII and binary raw file formats.
* Provides PyLTSpice-compatible API for accessing trace data.
*/
class RawRead {
public:
/**
* @brief Construct a RawRead and parse a file
* @param filepath Path to the .raw file
* @throws std::runtime_error if file cannot be parsed
*/
explicit RawRead(const std::string& filepath);
/**
* @brief Default constructor (creates empty object)
*/
RawRead() = default;
/**
* @brief Get the plot title
*/
const std::string& title() const { return title_; }
/**
* @brief Get the plot date
*/
const std::string& date() const { return date_; }
/**
* @brief Get the plot name (analysis type)
*/
const std::string& plot_name() const { return plot_name_; }
/**
* @brief Get the analysis type (tran, ac, dc, etc.)
*/
const std::string& analysis_type() const { return analysis_type_; }
/**
* @brief Get list of all trace names
* @return Vector of trace names
*/
std::vector<std::string> get_trace_names() const;
/**
* @brief Get a specific trace by name
* @param name Trace name (e.g., "V(out)" or "time")
* @return Shared pointer to Trace object
* @throws std::out_of_range if trace not found
*/
std::shared_ptr<Trace> get_trace(const std::string& name) const;
/**
* @brief Check if a trace exists
* @param name Trace name
*/
bool has_trace(const std::string& name) const;
/**
* @brief Get the number of steps (for parametric sweeps)
* @return List of step indices
*/
std::vector<int> get_steps() const;
/**
* @brief Get step parameter values (for .step simulations)
*
* This is the `steps` property in PyLTSpice.
* Returns a list of dictionaries with parameter names and values.
*/
const std::vector<std::map<std::string, double>>& steps() const { return step_params_; }
/**
* @brief Get the number of data points
*/
size_t num_points() const { return num_points_; }
/**
* @brief Get the number of variables/traces
*/
size_t num_variables() const { return traces_.size(); }
/**
* @brief Check if data is complex (AC analysis)
*/
bool is_complex() const { return is_complex_; }
/**
* @brief Check if data is real
*/
bool is_real() const { return !is_complex_; }
/**
* @brief Get the raw file path
*/
const std::string& filepath() const { return filepath_; }
/**
* @brief Parse a raw file
* @param filepath Path to the .raw file
* @return true if parsing succeeded
*/
bool parse(const std::string& filepath);
private:
/**
* @brief Parse the header section of the raw file
* @param file Input file stream
* @return true if header was parsed successfully
*/
bool parse_header(std::istream& file);
/**
* @brief Parse ASCII data section
* @param file Input file stream
* @return true if data was parsed successfully
*/
bool parse_ascii_data(std::istream& file);
/**
* @brief Parse binary data section
* @param file Input file stream
* @return true if data was parsed successfully
*/
bool parse_binary_data(std::istream& file);
/**
* @brief Detect step boundaries in the data
*/
void detect_steps();
std::string filepath_;
std::string title_;
std::string date_;
std::string plot_name_;
std::string analysis_type_;
std::string flags_;
size_t num_points_ = 0;
size_t num_variables_ = 0;
bool is_complex_ = false;
bool is_binary_ = false;
// Variable info from header
struct VarInfo {
int index;
std::string name;
std::string type; // voltage, current, time, frequency, etc.
};
std::vector<VarInfo> var_info_;
// Trace data
std::map<std::string, std::shared_ptr<Trace>> traces_;
// Step information (for .step simulations)
std::vector<std::map<std::string, double>> step_params_;
std::vector<size_t> step_boundaries_;
};
} // namespace ngspice
#endif // NGSPICE_PYTHON_RAW_READ_H

286
src/cpp/sim_runner.cpp

@ -0,0 +1,286 @@
/**
* @file sim_runner.cpp
* @brief SimRunner class implementation - PyLTSpice-compatible simulation runner
*/
#include "sim_runner.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <chrono>
#include <stdexcept>
namespace fs = std::filesystem;
namespace ngspice {
// NgSpice static member
std::vector<std::string> NgSpice::spice_exe;
std::string NgSpice::version() {
return "ngspice-43";
}
// SimRunner implementation
SimRunner::SimRunner(const std::string& output_folder, void* simulator)
: output_folder_(output_folder)
, simulator_(std::make_unique<Simulator>())
{
// Create output directory if it doesn't exist
if (!output_folder_.empty()) {
fs::create_directories(output_folder_);
}
// Initialize the simulator
simulator_->initialize();
simulator_->set_output_dir(output_folder_);
}
SimRunner::~SimRunner() {
// Signal workers to shutdown
shutdown_ = true;
// Wait for workers to finish
for (auto& worker : workers_) {
if (worker.joinable()) {
worker.join();
}
}
}
SimRunner::SimRunner(SimRunner&& other) noexcept
: output_folder_(std::move(other.output_folder_))
, max_parallel_(other.max_parallel_)
, pending_netlists_(std::move(other.pending_netlists_))
, completed_results_(std::move(other.completed_results_))
, simulator_(std::move(other.simulator_))
, run_counter_(other.run_counter_)
{
}
SimRunner& SimRunner::operator=(SimRunner&& other) noexcept {
if (this != &other) {
output_folder_ = std::move(other.output_folder_);
max_parallel_ = other.max_parallel_;
pending_netlists_ = std::move(other.pending_netlists_);
completed_results_ = std::move(other.completed_results_);
simulator_ = std::move(other.simulator_);
run_counter_ = other.run_counter_;
}
return *this;
}
std::pair<std::string, std::string> SimRunner::run_now(const std::string& netlist) {
SimulationResult result = run_simulation(netlist);
if (!result.success) {
throw std::runtime_error("Simulation failed: " + result.error_message);
}
return {result.raw_file, result.log_file};
}
bool SimRunner::run(const std::string& netlist) {
std::lock_guard<std::mutex> lock(queue_mutex_);
pending_netlists_.push(netlist);
// Start a worker if needed
// For simplicity, we run synchronously in the background
// A full implementation would use a thread pool
return true;
}
void SimRunner::wait_completion() {
// Process all pending netlists
while (has_pending()) {
std::string netlist;
{
std::lock_guard<std::mutex> lock(queue_mutex_);
if (pending_netlists_.empty()) break;
netlist = pending_netlists_.front();
pending_netlists_.pop();
}
SimulationResult result = run_simulation(netlist);
{
std::lock_guard<std::mutex> lock(queue_mutex_);
completed_results_.push(result);
}
}
}
bool SimRunner::has_pending() const {
std::lock_guard<std::mutex> lock(queue_mutex_);
return !pending_netlists_.empty();
}
size_t SimRunner::pending_count() const {
std::lock_guard<std::mutex> lock(queue_mutex_);
return pending_netlists_.size();
}
SimulationResult SimRunner::run_simulation(const std::string& netlist) {
SimulationResult result;
// Generate output paths
auto [raw_path, log_path] = generate_output_paths(netlist);
try {
// Reset simulator for new run
simulator_->reset();
simulator_->clear_output();
// Load netlist
if (!simulator_->load_netlist(netlist)) {
result.error_message = "Failed to load netlist: " + netlist;
return result;
}
// Set binary output format
simulator_->command("set filetype=binary");
// Run the simulation
if (!simulator_->run()) {
result.error_message = "Simulation failed to run";
return result;
}
// In shared library mode, ngspice does NOT automatically write a .raw file.
// We must explicitly use the "write" command to dump simulation results.
// Convert backslashes to forward slashes for ngspice's command parser.
std::string raw_path_fwd = raw_path;
std::replace(raw_path_fwd.begin(), raw_path_fwd.end(), '\\', '/');
std::string write_cmd = "write " + raw_path_fwd;
if (!simulator_->command(write_cmd)) {
result.error_message = "Failed to write raw file: " + raw_path;
return result;
}
if (!fs::exists(raw_path)) {
// Collect diagnostic info for debugging
std::string diag = "Raw output file not found after write: " + raw_path;
std::string plot = simulator_->current_plot();
diag += "\n Current plot: " + (plot.empty() ? "(none)" : plot);
auto vecs = simulator_->all_vectors(plot);
diag += "\n Vector count: " + std::to_string(vecs.size());
std::string output = simulator_->get_output();
if (!output.empty()) {
// Include last few lines of simulator output
diag += "\n Simulator output (last 500 chars): "
+ output.substr(output.size() > 500 ? output.size() - 500 : 0);
}
result.error_message = diag;
return result;
}
result.raw_file = raw_path;
// Look for log file (optional)
fs::path log_candidate = fs::path(result.raw_file).replace_extension(".log");
if (fs::exists(log_candidate)) {
result.log_file = log_candidate.string();
}
// Save simulator output as log if no log file exists
if (result.log_file.empty()) {
std::string output = simulator_->get_output();
if (!output.empty()) {
std::ofstream log_out(log_path);
if (log_out.is_open()) {
log_out << output;
result.log_file = log_path;
}
}
}
result.success = true;
} catch (const std::exception& e) {
result.error_message = e.what();
}
return result;
}
std::pair<std::string, std::string> SimRunner::generate_output_paths(
const std::string& netlist) {
fs::path netlist_path(netlist);
std::string base_name = netlist_path.stem().string();
// Add counter suffix to avoid overwriting
++run_counter_;
std::string suffix = "_" + std::to_string(run_counter_);
fs::path output_dir = output_folder_.empty()
? netlist_path.parent_path()
: fs::path(output_folder_);
std::string raw_path = (output_dir / (base_name + suffix + ".raw")).string();
std::string log_path = (output_dir / (base_name + suffix + ".log")).string();
return {raw_path, log_path};
}
// Iterator implementation
SimRunner::Iterator::Iterator(SimRunner* runner)
: runner_(runner)
, at_end_(false)
{
fetch_next();
}
void SimRunner::Iterator::fetch_next() {
if (!runner_) {
at_end_ = true;
return;
}
// Process pending netlists if any
{
std::lock_guard<std::mutex> lock(runner_->queue_mutex_);
// Check for completed results first
if (!runner_->completed_results_.empty()) {
auto result = runner_->completed_results_.front();
runner_->completed_results_.pop();
current_ = {result.raw_file, result.log_file};
return;
}
// Check for pending netlists
if (runner_->pending_netlists_.empty()) {
at_end_ = true;
return;
}
// Get next netlist to process
std::string netlist = runner_->pending_netlists_.front();
runner_->pending_netlists_.pop();
// Need to unlock before running simulation
lock.~lock_guard();
// Run simulation
SimulationResult result = runner_->run_simulation(netlist);
current_ = {result.raw_file, result.log_file};
}
}
SimRunner::Iterator::reference SimRunner::Iterator::operator*() {
return current_;
}
SimRunner::Iterator& SimRunner::Iterator::operator++() {
fetch_next();
return *this;
}
bool SimRunner::Iterator::operator==(const Iterator& other) const {
if (at_end_ && other.at_end_) return true;
if (at_end_ != other.at_end_) return false;
return runner_ == other.runner_;
}
} // namespace ngspice

219
src/cpp/sim_runner.h

@ -0,0 +1,219 @@
/**
* @file sim_runner.h
* @brief SimRunner class - PyLTSpice-compatible simulation runner
*
* This class provides a high-level API for running ngspice simulations
* that is compatible with PyLTSpice's SimRunner class.
*/
#ifndef NGSPICE_PYTHON_SIM_RUNNER_H
#define NGSPICE_PYTHON_SIM_RUNNER_H
#include <string>
#include <vector>
#include <memory>
#include <queue>
#include <mutex>
#include <thread>
#include <future>
#include "simulator.h"
namespace ngspice {
// Forward declaration
class NgSpiceSimulator;
/**
* @brief Result of a simulation run
*/
struct SimulationResult {
std::string raw_file; // Path to .raw output file
std::string log_file; // Path to .log output file (may be empty)
bool success = false;
std::string error_message;
};
/**
* @brief PyLTSpice-compatible simulation runner
*
* Provides methods for running ngspice simulations on netlists
* and retrieving results. Supports both synchronous and asynchronous
* execution patterns.
*
* Usage:
* SimRunner runner("./output");
* auto [raw, log] = runner.run_now("circuit.net");
*
* // Or async:
* runner.run("circuit.net");
* for (auto& result : runner) {
* // process result
* }
*/
class SimRunner {
public:
/**
* @brief Construct a SimRunner
* @param output_folder Directory for output files
* @param simulator Optional simulator instance (for API compat, ignored)
*/
explicit SimRunner(const std::string& output_folder = ".",
void* simulator = nullptr);
/**
* @brief Destructor - waits for any running simulations
*/
~SimRunner();
// Disable copy
SimRunner(const SimRunner&) = delete;
SimRunner& operator=(const SimRunner&) = delete;
// Allow move
SimRunner(SimRunner&&) noexcept;
SimRunner& operator=(SimRunner&&) noexcept;
/**
* @brief Run simulation synchronously (blocking)
* @param netlist Path to the netlist file
* @return Pair of (raw_file_path, log_file_path)
*
* This is the primary method for running simulations.
* Returns paths to the output files.
*/
std::pair<std::string, std::string> run_now(const std::string& netlist);
/**
* @brief Queue a simulation for async execution
* @param netlist Path to the netlist file
* @return true if simulation was queued
*
* Use the iterator interface to retrieve results.
*/
bool run(const std::string& netlist);
/**
* @brief Wait for all queued simulations to complete
*/
void wait_completion();
/**
* @brief Check if any simulations are pending
*/
bool has_pending() const;
/**
* @brief Get the number of pending simulations
*/
size_t pending_count() const;
/**
* @brief Get output folder path
*/
const std::string& output_folder() const { return output_folder_; }
/**
* @brief Set output folder path
*/
void set_output_folder(const std::string& folder) { output_folder_ = folder; }
/**
* @brief Set maximum parallel simulations (default: 1)
*/
void set_parallel_sims(int count) { max_parallel_ = count; }
/**
* @brief Get maximum parallel simulations
*/
int parallel_sims() const { return max_parallel_; }
// Iterator interface for retrieving async results
/**
* @brief Iterator for simulation results
*/
class Iterator {
public:
using value_type = std::pair<std::string, std::string>;
using reference = value_type;
using pointer = value_type*;
using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t;
Iterator() : runner_(nullptr), at_end_(true) {}
explicit Iterator(SimRunner* runner);
reference operator*();
Iterator& operator++();
bool operator==(const Iterator& other) const;
bool operator!=(const Iterator& other) const { return !(*this == other); }
private:
void fetch_next();
SimRunner* runner_;
bool at_end_;
std::pair<std::string, std::string> current_;
};
Iterator begin() { return Iterator(this); }
Iterator end() { return Iterator(); }
private:
/**
* @brief Internal method to run a single simulation
*/
SimulationResult run_simulation(const std::string& netlist);
/**
* @brief Generate output file paths for a netlist
*/
std::pair<std::string, std::string> generate_output_paths(const std::string& netlist);
/**
* @brief Worker thread function for async execution
*/
void worker_thread();
std::string output_folder_;
int max_parallel_ = 1;
// Async execution support
std::queue<std::string> pending_netlists_;
std::queue<SimulationResult> completed_results_;
mutable std::mutex queue_mutex_;
std::vector<std::thread> workers_;
std::atomic<bool> shutdown_{false};
// Simulator instance
std::unique_ptr<Simulator> simulator_;
// Counter for output file naming
int run_counter_ = 0;
};
/**
* @brief NgSpice simulator class (for PyLTSpice compatibility)
*
* This class exists primarily for API compatibility with PyLTSpice.
* It doesn't do anything special - SimRunner handles the actual simulation.
*/
class NgSpice {
public:
/**
* @brief Path to ngspice executable (ignored - we use embedded ngspice)
*
* This is provided for API compatibility with PyLTSpice.
* Since we use the embedded ngspice library, this value is ignored.
*/
static std::vector<std::string> spice_exe;
/**
* @brief Get the ngspice version string
*/
static std::string version();
};
} // namespace ngspice
#endif // NGSPICE_PYTHON_SIM_RUNNER_H

433
src/cpp/simulator.cpp

@ -0,0 +1,433 @@
/**
* @file simulator.cpp
* @brief Core ngspice simulator wrapper implementation
*/
#include "simulator.h"
#include "callbacks.h"
#include <fstream>
#include <sstream>
#include <algorithm>
namespace ngspice {
// Static member initialization
std::mutex Simulator::instances_mutex_;
std::vector<Simulator*> Simulator::instances_;
Simulator* Simulator::get_instance(int id) {
std::lock_guard<std::mutex> lock(instances_mutex_);
for (auto* sim : instances_) {
if (sim && sim->instance_id_ == id) {
return sim;
}
}
return nullptr;
}
void Simulator::register_instance(Simulator* sim) {
std::lock_guard<std::mutex> lock(instances_mutex_);
instances_.push_back(sim);
}
void Simulator::unregister_instance(Simulator* sim) {
std::lock_guard<std::mutex> lock(instances_mutex_);
instances_.erase(
std::remove(instances_.begin(), instances_.end(), sim),
instances_.end()
);
}
// Static callback functions
int Simulator::send_char_callback(char* output, int id, void* user_data) {
if (output) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim) {
sim->handle_output(output);
}
}
return 0;
}
int Simulator::send_stat_callback(char* status, int id, void* user_data) {
if (status) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim) {
sim->handle_status(status);
}
}
return 0;
}
int Simulator::controlled_exit_callback(int status, int immediate, int quit,
int id, void* user_data) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim) {
sim->handle_exit(status, immediate != 0, quit != 0);
}
return 0;
}
int Simulator::send_data_callback(pvecvaluesall data, int count, int id,
void* user_data) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim && data) {
sim->handle_data(data, count);
}
return 0;
}
int Simulator::send_init_data_callback(pvecinfoall data, int id, void* user_data) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim && data) {
sim->handle_init_data(data);
}
return 0;
}
int Simulator::bg_thread_running_callback(int running, int id, void* user_data) {
auto* sim = static_cast<Simulator*>(user_data);
if (sim) {
sim->handle_thread_status(running != 0);
}
return 0;
}
// Instance callback handlers
void Simulator::handle_output(const std::string& output) {
std::lock_guard<std::mutex> lock(mutex_);
accumulated_output_ += output;
if (output_callback_) {
output_callback_(output);
}
}
void Simulator::handle_status(const std::string& status) {
std::lock_guard<std::mutex> lock(mutex_);
status_.status_text = status;
double progress = parse_progress(status);
if (progress >= 0) {
status_.progress = progress;
}
if (status_callback_) {
status_callback_(status);
}
}
void Simulator::handle_exit(int status, bool immediate, bool quit) {
std::lock_guard<std::mutex> lock(mutex_);
running_ = false;
status_.running = false;
status_.completed = true;
if (status != 0) {
status_.error = true;
status_.error_message = "Simulation exited with status " + std::to_string(status);
}
}
void Simulator::handle_data(pvecvaluesall data, int count) {
// Called during simulation with vector values
// We don't store this - results are read from raw file
}
void Simulator::handle_init_data(pvecinfoall data) {
// Called before simulation with vector info
// We don't need this - info is in raw file
}
void Simulator::handle_thread_status(bool running) {
bg_thread_active_ = running;
if (!running) {
running_ = false;
status_.running = false;
}
}
// Constructor / Destructor
Simulator::Simulator(int instance_id)
: instance_id_(instance_id)
{
register_instance(this);
}
Simulator::~Simulator() {
unregister_instance(this);
CallbackManager::instance().unregister_callbacks(instance_id_);
}
Simulator::Simulator(Simulator&& other) noexcept
: instance_id_(other.instance_id_)
, initialized_(other.initialized_)
, running_(other.running_.load())
, bg_thread_active_(other.bg_thread_active_.load())
, output_dir_(std::move(other.output_dir_))
, accumulated_output_(std::move(other.accumulated_output_))
, status_(std::move(other.status_))
, output_callback_(std::move(other.output_callback_))
, status_callback_(std::move(other.status_callback_))
{
other.initialized_ = false;
unregister_instance(&other);
register_instance(this);
}
Simulator& Simulator::operator=(Simulator&& other) noexcept {
if (this != &other) {
unregister_instance(this);
instance_id_ = other.instance_id_;
initialized_ = other.initialized_;
running_ = other.running_.load();
bg_thread_active_ = other.bg_thread_active_.load();
output_dir_ = std::move(other.output_dir_);
accumulated_output_ = std::move(other.accumulated_output_);
status_ = std::move(other.status_);
output_callback_ = std::move(other.output_callback_);
status_callback_ = std::move(other.status_callback_);
other.initialized_ = false;
unregister_instance(&other);
register_instance(this);
}
return *this;
}
bool Simulator::initialize() {
if (initialized_) {
return true;
}
// Register callbacks with CallbackManager
CallbackManager::instance().register_callbacks(
instance_id_,
[this](const std::string& msg) { handle_output(msg); },
[this](const std::string& status) { handle_status(status); },
[this](int status, bool immediate, bool quit) {
handle_exit(status, immediate, quit);
},
[this](bool running) { handle_thread_status(running); }
);
// Initialize ngspice with callbacks
int ret = ngSpice_Init(
send_char_callback,
send_stat_callback,
controlled_exit_callback,
send_data_callback,
send_init_data_callback,
bg_thread_running_callback,
this
);
initialized_ = (ret == 0);
return initialized_;
}
bool Simulator::load_netlist(const std::string& filepath) {
if (!initialized_ && !initialize()) {
return false;
}
// Clear any previous state
status_ = SimulationStatus{};
accumulated_output_.clear();
// Use source command to load netlist
std::string cmd = "source " + filepath;
return command(cmd);
}
bool Simulator::load_netlist_string(const std::string& netlist_content) {
if (!initialized_ && !initialize()) {
return false;
}
// Split netlist into lines
std::vector<std::string> lines;
std::istringstream iss(netlist_content);
std::string line;
while (std::getline(iss, line)) {
lines.push_back(line);
}
// Create null-terminated array of C strings
std::vector<char*> c_lines;
for (auto& l : lines) {
c_lines.push_back(const_cast<char*>(l.c_str()));
}
c_lines.push_back(nullptr);
// Clear state
status_ = SimulationStatus{};
accumulated_output_.clear();
// Load circuit
int ret = ngSpice_Circ(c_lines.data());
return ret == 0;
}
bool Simulator::command(const std::string& cmd) {
if (!initialized_ && !initialize()) {
return false;
}
int ret = ngSpice_Command(const_cast<char*>(cmd.c_str()));
return ret == 0;
}
bool Simulator::run() {
if (!initialized_) {
return false;
}
running_ = true;
status_.running = true;
status_.completed = false;
status_.error = false;
// Execute run command (blocking)
bool result = command("run");
running_ = false;
status_.running = false;
status_.completed = true;
return result;
}
bool Simulator::run_async() {
if (!initialized_) {
return false;
}
running_ = true;
status_.running = true;
status_.completed = false;
status_.error = false;
// Execute bg_run command (non-blocking)
return command("bg_run");
}
bool Simulator::is_running() const {
if (!initialized_) {
return false;
}
return running_ || ngSpice_running() != 0;
}
void Simulator::halt() {
if (is_running()) {
command("bg_halt");
}
}
void Simulator::resume() {
command("bg_resume");
}
void Simulator::reset() {
command("reset");
status_ = SimulationStatus{};
accumulated_output_.clear();
}
SimulationStatus Simulator::get_status() const {
std::lock_guard<std::mutex> lock(mutex_);
return status_;
}
std::string Simulator::current_plot() const {
if (!initialized_) {
return "";
}
char* plot = ngSpice_CurPlot();
return plot ? plot : "";
}
std::vector<std::string> Simulator::all_plots() const {
std::vector<std::string> result;
if (!initialized_) {
return result;
}
char** plots = ngSpice_AllPlots();
if (plots) {
for (int i = 0; plots[i] != nullptr; ++i) {
result.push_back(plots[i]);
}
}
return result;
}
std::vector<std::string> Simulator::all_vectors(const std::string& plot_name) const {
std::vector<std::string> result;
if (!initialized_) {
return result;
}
std::string plot = plot_name.empty() ? current_plot() : plot_name;
if (plot.empty()) {
return result;
}
char** vecs = ngSpice_AllVecs(const_cast<char*>(plot.c_str()));
if (vecs) {
for (int i = 0; vecs[i] != nullptr; ++i) {
result.push_back(vecs[i]);
}
}
return result;
}
VectorInfo Simulator::get_vector_info(const std::string& vec_name) const {
VectorInfo info;
if (!initialized_) {
return info;
}
pvector_info vi = ngGet_Vec_Info(const_cast<char*>(vec_name.c_str()));
if (!vi) {
return info;
}
info.name = vi->v_name ? vi->v_name : "";
info.type = vi->v_type;
info.length = vi->v_length;
info.is_real = (vi->v_compdata == nullptr);
info.is_complex = !info.is_real;
// Copy data
if (vi->v_realdata && vi->v_length > 0) {
info.real_data.assign(vi->v_realdata, vi->v_realdata + vi->v_length);
}
// Note: Complex data would need special handling
// vi->v_compdata is ngcomplex_t* which is {double cx_real, cx_imag}
return info;
}
void Simulator::set_output_callback(std::function<void(const std::string&)> callback) {
std::lock_guard<std::mutex> lock(mutex_);
output_callback_ = std::move(callback);
}
void Simulator::set_status_callback(std::function<void(const std::string&)> callback) {
std::lock_guard<std::mutex> lock(mutex_);
status_callback_ = std::move(callback);
}
std::string Simulator::get_output() const {
std::lock_guard<std::mutex> lock(mutex_);
return accumulated_output_;
}
void Simulator::clear_output() {
std::lock_guard<std::mutex> lock(mutex_);
accumulated_output_.clear();
}
} // namespace ngspice

298
src/cpp/simulator.h

@ -0,0 +1,298 @@
/**
* @file simulator.h
* @brief Core ngspice simulator wrapper for Python extension
*
* This class wraps the ngspice shared library API to provide
* circuit simulation capabilities to Python.
*/
#ifndef NGSPICE_PYTHON_SIMULATOR_H
#define NGSPICE_PYTHON_SIMULATOR_H
#include <string>
#include <vector>
#include <functional>
#include <memory>
#include <mutex>
#include <atomic>
// Forward declarations for ngspice types
extern "C" {
// ngspice shared library API types
typedef struct vecvalues {
char* name;
double creal;
double cimag;
int is_scale;
int is_complex;
} vecvalues, *pvecvalues;
typedef struct vecvaluesall {
int veccount;
int vecindex;
pvecvalues *vecsa;
} vecvaluesall, *pvecvaluesall;
typedef struct vecinfo {
int number;
char *vecname;
int is_real;
void *pdvec;
void *pdvecscale;
} vecinfo, *pvecinfo;
typedef struct vecinfoall {
char *name;
char *title;
char *date;
char *type;
int veccount;
pvecinfo *vecs;
} vecinfoall, *pvecinfoall;
typedef struct vector_info {
char *v_name;
int v_type;
short v_flags;
double *v_realdata;
void *v_compdata; // ngcomplex_t*
int v_length;
} vector_info, *pvector_info;
// Callback function types
typedef int (SendChar)(char*, int, void*);
typedef int (SendStat)(char*, int, void*);
typedef int (ControlledExit)(int, int, int, int, void*);
typedef int (SendData)(pvecvaluesall, int, int, void*);
typedef int (SendInitData)(pvecinfoall, int, void*);
typedef int (BGThreadRunning)(int, int, void*);
// ngspice API functions
int ngSpice_Init(SendChar*, SendStat*, ControlledExit*,
SendData*, SendInitData*, BGThreadRunning*, void*);
int ngSpice_Command(char*);
int ngSpice_Circ(char**);
char* ngSpice_CurPlot(void);
char** ngSpice_AllPlots(void);
char** ngSpice_AllVecs(char*);
pvector_info ngGet_Vec_Info(char*);
int ngSpice_running(void);
}
namespace ngspice {
/**
* @brief Simulation status information
*/
struct SimulationStatus {
bool running = false;
bool completed = false;
bool error = false;
std::string error_message;
double progress = 0.0; // 0.0 to 1.0
std::string status_text;
};
/**
* @brief Vector/trace information
*/
struct VectorInfo {
std::string name;
int type;
bool is_real;
bool is_complex;
int length;
std::vector<double> real_data;
std::vector<double> imag_data; // Only for complex vectors
};
/**
* @brief Core ngspice simulator wrapper
*
* This class manages the ngspice simulation engine lifecycle,
* handles callbacks, and provides methods for loading netlists
* and running simulations.
*/
class Simulator {
public:
/**
* @brief Construct a new Simulator instance
* @param instance_id Optional ID for parallel simulation instances
*/
explicit Simulator(int instance_id = 0);
/**
* @brief Destroy the Simulator instance
*/
~Simulator();
// Disable copy (ngspice has global state)
Simulator(const Simulator&) = delete;
Simulator& operator=(const Simulator&) = delete;
// Allow move
Simulator(Simulator&&) noexcept;
Simulator& operator=(Simulator&&) noexcept;
/**
* @brief Initialize the ngspice engine
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Check if simulator is initialized
*/
bool is_initialized() const { return initialized_; }
/**
* @brief Load a netlist from file
* @param filepath Path to the .net/.cir file
* @return true if loading succeeded
*/
bool load_netlist(const std::string& filepath);
/**
* @brief Load a netlist from string content
* @param netlist_content The netlist as a string
* @return true if loading succeeded
*/
bool load_netlist_string(const std::string& netlist_content);
/**
* @brief Execute a SPICE command
* @param command The command to execute (e.g., "run", "op", "ac ...")
* @return true if command executed successfully
*/
bool command(const std::string& command);
/**
* @brief Run the simulation (blocking)
* @return true if simulation completed successfully
*/
bool run();
/**
* @brief Run simulation in background thread
* @return true if background simulation started
*/
bool run_async();
/**
* @brief Check if simulation is currently running
*/
bool is_running() const;
/**
* @brief Halt a running simulation
*/
void halt();
/**
* @brief Resume a halted simulation
*/
void resume();
/**
* @brief Reset the simulator state
*/
void reset();
/**
* @brief Get current simulation status
*/
SimulationStatus get_status() const;
/**
* @brief Get the current plot name
*/
std::string current_plot() const;
/**
* @brief Get all plot names
*/
std::vector<std::string> all_plots() const;
/**
* @brief Get all vector names in a plot
* @param plot_name Name of the plot (or empty for current)
*/
std::vector<std::string> all_vectors(const std::string& plot_name = "") const;
/**
* @brief Get vector/trace information
* @param vec_name Full vector name (e.g., "tran1.V(out)")
*/
VectorInfo get_vector_info(const std::string& vec_name) const;
/**
* @brief Set callback for stdout/stderr output
*/
void set_output_callback(std::function<void(const std::string&)> callback);
/**
* @brief Set callback for status updates
*/
void set_status_callback(std::function<void(const std::string&)> callback);
/**
* @brief Get accumulated output messages
*/
std::string get_output() const;
/**
* @brief Clear accumulated output
*/
void clear_output();
/**
* @brief Get the output directory for raw files
*/
std::string get_output_dir() const { return output_dir_; }
/**
* @brief Set the output directory for raw files
*/
void set_output_dir(const std::string& dir) { output_dir_ = dir; }
private:
// Static callback functions (registered with ngspice)
static int send_char_callback(char* output, int id, void* user_data);
static int send_stat_callback(char* status, int id, void* user_data);
static int controlled_exit_callback(int status, int immediate, int quit, int id, void* user_data);
static int send_data_callback(pvecvaluesall data, int count, int id, void* user_data);
static int send_init_data_callback(pvecinfoall data, int id, void* user_data);
static int bg_thread_running_callback(int running, int id, void* user_data);
// Instance callback handlers
void handle_output(const std::string& output);
void handle_status(const std::string& status);
void handle_exit(int status, bool immediate, bool quit);
void handle_data(pvecvaluesall data, int count);
void handle_init_data(pvecinfoall data);
void handle_thread_status(bool running);
int instance_id_;
bool initialized_ = false;
std::atomic<bool> running_{false};
std::atomic<bool> bg_thread_active_{false};
mutable std::mutex mutex_;
std::string output_dir_;
std::string accumulated_output_;
SimulationStatus status_;
std::function<void(const std::string&)> output_callback_;
std::function<void(const std::string&)> status_callback_;
// Global instance map for callback routing
static std::mutex instances_mutex_;
static std::vector<Simulator*> instances_;
static Simulator* get_instance(int id);
static void register_instance(Simulator* sim);
static void unregister_instance(Simulator* sim);
};
} // namespace ngspice
#endif // NGSPICE_PYTHON_SIMULATOR_H

130
src/cpp/trace.cpp

@ -0,0 +1,130 @@
/**
* @file trace.cpp
* @brief Trace class implementation
*/
#include "trace.h"
#include <stdexcept>
#include <algorithm>
namespace ngspice {
Trace::Trace(const std::string& name, bool is_complex)
: name_(name)
, is_complex_(is_complex)
{
}
std::vector<double> Trace::get_wave(size_t step) const {
if (step_offsets_.empty()) {
// No step boundaries - return all data
return real_data_;
}
if (step >= step_offsets_.size()) {
throw std::out_of_range("Step index " + std::to_string(step) +
" out of range (max: " +
std::to_string(step_offsets_.size() - 1) + ")");
}
size_t start = step_offsets_[step];
size_t end = (step + 1 < step_offsets_.size())
? step_offsets_[step + 1]
: real_data_.size();
return std::vector<double>(real_data_.begin() + start,
real_data_.begin() + end);
}
std::vector<std::complex<double>> Trace::get_wave_complex(size_t step) const {
if (!is_complex_) {
// Convert real data to complex
auto real = get_wave(step);
std::vector<std::complex<double>> result;
result.reserve(real.size());
for (double v : real) {
result.emplace_back(v, 0.0);
}
return result;
}
if (step_offsets_.empty()) {
// No step boundaries - return all data
std::vector<std::complex<double>> result;
result.reserve(real_data_.size());
for (size_t i = 0; i < real_data_.size(); ++i) {
result.emplace_back(real_data_[i],
i < imag_data_.size() ? imag_data_[i] : 0.0);
}
return result;
}
if (step >= step_offsets_.size()) {
throw std::out_of_range("Step index " + std::to_string(step) +
" out of range");
}
size_t start = step_offsets_[step];
size_t end = (step + 1 < step_offsets_.size())
? step_offsets_[step + 1]
: real_data_.size();
std::vector<std::complex<double>> result;
result.reserve(end - start);
for (size_t i = start; i < end; ++i) {
result.emplace_back(real_data_[i],
i < imag_data_.size() ? imag_data_[i] : 0.0);
}
return result;
}
size_t Trace::step_size(size_t step) const {
if (step_offsets_.empty()) {
return real_data_.size();
}
if (step >= step_offsets_.size()) {
return 0;
}
size_t start = step_offsets_[step];
size_t end = (step + 1 < step_offsets_.size())
? step_offsets_[step + 1]
: real_data_.size();
return end - start;
}
void Trace::reserve(size_t count) {
real_data_.reserve(count);
if (is_complex_) {
imag_data_.reserve(count);
}
}
void Trace::add_point(double value) {
real_data_.push_back(value);
}
void Trace::add_point(double real, double imag) {
real_data_.push_back(real);
imag_data_.push_back(imag);
is_complex_ = true;
}
void Trace::mark_step_boundary() {
step_offsets_.push_back(real_data_.size());
}
void Trace::set_data(std::vector<double> real_data,
std::vector<double> imag_data) {
real_data_ = std::move(real_data);
imag_data_ = std::move(imag_data);
is_complex_ = !imag_data_.empty();
}
void Trace::set_step_offsets(std::vector<size_t> offsets) {
step_offsets_ = std::move(offsets);
}
} // namespace ngspice

129
src/cpp/trace.h

@ -0,0 +1,129 @@
/**
* @file trace.h
* @brief Trace class for waveform data access
*
* The Trace class represents a single simulation vector/waveform
* and provides access to the data as numpy arrays.
*/
#ifndef NGSPICE_PYTHON_TRACE_H
#define NGSPICE_PYTHON_TRACE_H
#include <string>
#include <vector>
#include <complex>
#include <memory>
namespace ngspice {
/**
* @brief Represents a single trace/vector from simulation results
*
* This class holds waveform data and provides methods compatible
* with PyLTSpice's Trace class.
*/
class Trace {
public:
/**
* @brief Construct a Trace
* @param name The trace name (e.g., "V(out)")
* @param is_complex Whether the data is complex
*/
Trace(const std::string& name, bool is_complex = false);
/**
* @brief Get the trace name
*/
const std::string& name() const { return name_; }
/**
* @brief Check if trace contains complex data
*/
bool is_complex() const { return is_complex_; }
/**
* @brief Get the number of steps (for parametric sweeps)
*/
size_t num_steps() const { return step_offsets_.empty() ? 1 : step_offsets_.size(); }
/**
* @brief Get waveform data for a specific step
* @param step Step index (0 for single-step simulations)
* @return Vector of data points
*
* For PyLTSpice compatibility, this returns real values.
* Use get_wave_complex() for complex data.
*/
std::vector<double> get_wave(size_t step = 0) const;
/**
* @brief Get complex waveform data for a specific step
* @param step Step index (0 for single-step simulations)
* @return Vector of complex data points
*/
std::vector<std::complex<double>> get_wave_complex(size_t step = 0) const;
/**
* @brief Get all data points (all steps concatenated)
*/
const std::vector<double>& get_all_real_data() const { return real_data_; }
/**
* @brief Get all imaginary data points
*/
const std::vector<double>& get_all_imag_data() const { return imag_data_; }
/**
* @brief Get total number of data points
*/
size_t size() const { return real_data_.size(); }
/**
* @brief Get number of data points for a specific step
*/
size_t step_size(size_t step = 0) const;
// Data loading methods (used during raw file parsing)
/**
* @brief Reserve space for data
*/
void reserve(size_t count);
/**
* @brief Add a real data point
*/
void add_point(double value);
/**
* @brief Add a complex data point
*/
void add_point(double real, double imag);
/**
* @brief Mark the start of a new step
*/
void mark_step_boundary();
/**
* @brief Set data directly (for efficiency)
*/
void set_data(std::vector<double> real_data,
std::vector<double> imag_data = {});
/**
* @brief Set step offsets directly
*/
void set_step_offsets(std::vector<size_t> offsets);
private:
std::string name_;
bool is_complex_;
std::vector<double> real_data_;
std::vector<double> imag_data_;
std::vector<size_t> step_offsets_; // Index where each step starts
};
} // namespace ngspice
#endif // NGSPICE_PYTHON_TRACE_H

15
src/maths/cmaths/cmath1.c

@ -874,15 +874,22 @@ cx_atanh(void* data, short int type, int length, int* newlength, short int* newt
ngcomplex_t* cc = (ngcomplex_t*)data; ngcomplex_t* cc = (ngcomplex_t*)data;
int i; int i;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
#ifdef _MSC_VER
_Dcomplex midin = _Cbuild(degtorad(realpart(cc[i])), degtorad(imagpart(cc[i])));
_Dcomplex midout = catanh(midin);
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
/* Use manual calculation for atanh - avoids complex.h issues on Windows */
double re = degtorad(realpart(cc[i]));
double im = degtorad(imagpart(cc[i]));
/* atanh(z) = 0.5 * (ln(1+z) - ln(1-z)) */
/* For z = re + im*i, we compute real and imag parts directly */
double denom1 = (1.0 + re) * (1.0 + re) + im * im;
double denom2 = (1.0 - re) * (1.0 - re) + im * im;
d[i].cx_real = 0.25 * log(denom1 / denom2);
d[i].cx_imag = 0.5 * atan2(2.0 * im, 1.0 - re * re - im * im);
#else #else
double complex midin = degtorad(realpart(cc[i])) + _Complex_I * degtorad(imagpart(cc[i])); double complex midin = degtorad(realpart(cc[i])) + _Complex_I * degtorad(imagpart(cc[i]));
double complex midout = catanh(midin); double complex midout = catanh(midin);
#endif
d[i].cx_real = creal(midout); d[i].cx_real = creal(midout);
d[i].cx_imag = cimag(midout); d[i].cx_imag = cimag(midout);
#endif
} }
return ((void*)d); return ((void*)d);
} }

0
tests/__init__.py

69
tests/conftest.py

@ -0,0 +1,69 @@
"""Shared test fixtures for pyngspice tests."""
import os
import sys
import tempfile
import pytest
# Ensure repo root is on path for editable installs
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@pytest.fixture
def tmp_workdir():
"""Provide a temporary working directory, cleaned up after the test."""
with tempfile.TemporaryDirectory() as d:
yield d
@pytest.fixture
def rc_netlist(tmp_workdir):
"""Write a simple RC circuit netlist and return its path."""
path = os.path.join(tmp_workdir, "rc.cir")
with open(path, "w") as f:
f.write("""RC Test Circuit
V1 in 0 DC 1
R1 in out 1k
C1 out 0 1u
.tran 0.1m 10m
.end
""")
return path
@pytest.fixture
def inductor_rser_netlist(tmp_workdir):
"""Write a netlist with LTspice-style Rser= on inductors."""
path = os.path.join(tmp_workdir, "inductor_rser.cir")
with open(path, "w") as f:
f.write("""Tesla Coil Primary
V1 drive 0 SIN(0 100 250000)
L1 drive tank 8.5u Rser=0.012
C1 tank 0 100n
L2 tank sec 500u Rser=2.5
R_load sec 0 50
.tran 0.1u 100u
.backanno
.end
""")
return path
@pytest.fixture
def tesla_coil_netlist(tmp_workdir):
"""Write a Tesla coil netlist with .options savecurrents and Rser=."""
path = os.path.join(tmp_workdir, "tesla_coil.cir")
with open(path, "w") as f:
f.write("""Tesla Coil AC Analysis
V1 vin 0 AC 1
C_mmc vin p1 0.03u
L1 p1 0 10.927u Rser=0.001
L2 0 top 15.987m Rser=0.001
C_topload top 0 13.822p
K1 L1 L2 0.3204
.options savecurrents
.ac dec 100 1k 3meg
.end
""")
return path

817
tests/test_netlist.py

@ -0,0 +1,817 @@
"""Tests for the netlist pre-processor (LTspice -> ngspice translation)."""
import pytest
from pyngspice.netlist import (
preprocess_netlist, _process_inductor_rser,
_collect_component_names, _is_savecurrents_option,
_process_capacitor_probe, _parse_save_cap_currents,
)
class TestInductorRser:
"""Test Rser= parameter extraction from inductor lines."""
def test_basic_rser(self):
netlist = """Tesla Coil
L1 p1 0 8.5u Rser=0.012
C1 p1 0 100n
.tran 1u 100u
.end
"""
result = preprocess_netlist(netlist)
assert "L1 p1 _rser_L1 8.5u" in result
assert "R_L1_ser _rser_L1 0 0.012" in result
assert "Rser" not in result
def test_rser_with_suffix(self):
"""Rser value with engineering suffix (12m = 12 milliohms)."""
netlist = """Test
L1 a b 10u Rser=12m
.end
"""
result = preprocess_netlist(netlist)
assert "L1 a _rser_L1 10u" in result
assert "R_L1_ser _rser_L1 b 12m" in result
def test_rser_scientific_notation(self):
"""Rser value in scientific notation."""
netlist = """Test
L1 a b 1.5e-6 Rser=1.2e-3
.end
"""
result = preprocess_netlist(netlist)
assert "R_L1_ser" in result
assert "1.2e-3" in result
def test_multiple_inductors(self):
"""Multiple inductors with Rser= in same netlist."""
netlist = """Multi Inductor
L1 a 0 10u Rser=0.01
L2 b 0 20u Rser=0.02
L3 c 0 30u Rser=0.03
.end
"""
result = preprocess_netlist(netlist)
assert "R_L1_ser" in result
assert "R_L2_ser" in result
assert "R_L3_ser" in result
assert "_rser_L1" in result
assert "_rser_L2" in result
assert "_rser_L3" in result
def test_named_inductor(self):
"""Inductor with alphanumeric name (Lprimary)."""
netlist = """Test
Lprimary drive tank 8.5u Rser=0.012
.end
"""
result = preprocess_netlist(netlist)
assert "Lprimary drive _rser_Lprimary 8.5u" in result
assert "R_Lprimary_ser _rser_Lprimary tank 0.012" in result
def test_inductor_without_rser_unchanged(self):
"""Inductors without Rser= should pass through unchanged."""
netlist = """Test
L1 a b 10u
.end
"""
result = preprocess_netlist(netlist)
assert "L1 a b 10u" in result
assert "R_L1" not in result
def test_rser_with_spaces(self):
"""Rser with spaces around equals sign."""
netlist = """Test
L1 a b 10u Rser = 0.05
.end
"""
result = preprocess_netlist(netlist)
assert "R_L1_ser" in result
assert "0.05" in result
def test_resistors_before_end(self):
"""Extra resistor lines should be inserted before .end."""
netlist = """Test
L1 a 0 10u Rser=0.01
.tran 1u 100u
.end
"""
result = preprocess_netlist(netlist)
lines = result.splitlines()
# Find positions
rser_idx = next(i for i, l in enumerate(lines) if "R_L1_ser" in l)
end_idx = next(i for i, l in enumerate(lines) if l.strip().lower() == '.end')
assert rser_idx < end_idx
class TestBackanno:
"""Test .backanno directive stripping."""
def test_backanno_removed(self):
netlist = """Test
V1 in 0 1
.backanno
.end
"""
result = preprocess_netlist(netlist)
assert ".backanno" not in result
assert ".end" in result
assert "V1 in 0 1" in result
def test_backanno_case_insensitive(self):
netlist = """Test
V1 in 0 1
.BACKANNO
.end
"""
result = preprocess_netlist(netlist)
assert ".BACKANNO" not in result
class TestPassthrough:
"""Test that standard SPICE constructs pass through unchanged."""
def test_simple_netlist_unchanged(self):
netlist = """Simple RC
V1 in 0 DC 1
R1 in out 1k
C1 out 0 1u
.tran 0.1m 10m
.end"""
result = preprocess_netlist(netlist)
assert result == netlist
def test_behavioral_source_unchanged(self):
"""Behavioral sources should pass through."""
netlist = """Test
B2 N015 0 V=I(L1)*{iscale}
.end
"""
result = preprocess_netlist(netlist)
assert "B2 N015 0 V=I(L1)*{iscale}" in result
def test_subcircuit_unchanged(self):
"""Subcircuit definitions should pass through."""
netlist = """Test
.subckt mycomp in out
R1 in out 1k
.ends mycomp
.end
"""
result = preprocess_netlist(netlist)
assert ".subckt mycomp in out" in result
assert ".ends mycomp" in result
def test_continuation_lines(self):
"""Continuation lines (starting with +) should pass through."""
netlist = """Test
V1 in 0 PULSE(0 5 0 1n 1n
+ 500n 1u)
.end
"""
result = preprocess_netlist(netlist)
assert "+ 500n 1u)" in result
def test_empty_lines_preserved(self):
"""Empty lines should be preserved."""
netlist = """Test
V1 in 0 1
R1 in out 1k
.end"""
result = preprocess_netlist(netlist)
assert "\n\n" in result
class TestProcessInductorRser:
"""Test the internal _process_inductor_rser function directly."""
def test_basic(self):
line, extras = _process_inductor_rser("L1 p1 0 8.5u Rser=0.012")
assert line == "L1 p1 _rser_L1 8.5u"
assert len(extras) == 1
assert extras[0] == "R_L1_ser _rser_L1 0 0.012"
def test_no_rser_returns_original(self):
line, extras = _process_inductor_rser("L1 p1 0 8.5u")
assert line == "L1 p1 0 8.5u"
assert extras == []
def test_preserves_remaining_params(self):
"""Parameters after Rser= that are not LTspice-specific should be kept."""
line, extras = _process_inductor_rser("L1 a b 10u Rser=0.01 IC=0.5")
assert "IC=0.5" in line
assert len(extras) == 1
class TestSaveCurrents:
"""Test .options savecurrents expansion into explicit .save directives."""
def test_savecurrents_expanded(self):
"""`.options savecurrents` should be removed and .save directives added."""
netlist = """Tesla Coil
V1 vin 0 AC 1
C1 vin out 0.03u
R1 out 0 50
.options savecurrents
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
assert ".options savecurrents" not in result.lower()
assert ".save all" in result
assert "i(V1)" in result
assert "i(C1)" in result
assert "i(R1)" in result
def test_savecurrents_includes_all_components(self):
"""All R, C, L, V components should get i(name) saves."""
netlist = """Test
V1 in 0 DC 1
R1 in mid 1k
L1 mid out 10u
C1 out 0 1u
.options savecurrents
.tran 1u 100u
.end
"""
result = preprocess_netlist(netlist)
assert "i(V1)" in result
assert "i(R1)" in result
assert "i(L1)" in result
assert "i(C1)" in result
def test_savecurrents_skips_K_elements(self):
"""K (coupling) elements should NOT get i(K1) saves."""
netlist = """Test
V1 in 0 AC 1
L1 in 0 10u
L2 0 out 20u
K1 L1 L2 0.5
.options savecurrents
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert "i(K1)" not in result
assert "i(V1)" in result
assert "i(L1)" in result
assert "i(L2)" in result
def test_savecurrents_includes_rser_resistors(self):
"""Expanded Rser resistors should be included in .save directives."""
netlist = """Test
V1 in 0 AC 1
L1 in 0 10u Rser=0.01
.options savecurrents
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert "i(R_L1_ser)" in result
assert "i(L1)" in result
assert "i(V1)" in result
def test_savecurrents_case_insensitive(self):
""".OPTIONS SAVECURRENTS should be handled."""
netlist = """Test
V1 in 0 1
R1 in 0 1k
.OPTIONS SAVECURRENTS
.op
.end
"""
result = preprocess_netlist(netlist)
assert ".OPTIONS SAVECURRENTS" not in result
assert ".save all" in result
assert "i(V1)" in result
def test_no_savecurrents_unchanged(self):
"""Netlists without .options savecurrents should not get .save directives."""
netlist = """Test
V1 in 0 DC 1
R1 in out 1k
C1 out 0 1u
.tran 0.1m 10m
.end"""
result = preprocess_netlist(netlist)
assert ".save" not in result.lower()
assert result == netlist
def test_savecurrents_with_rser_full_tesla_coil(self):
"""Full Tesla coil netlist: Rser + savecurrents + K coupling + probes."""
netlist = """Tesla Coil AC Analysis
V1 vin 0 AC 1
C_mmc vin p1 0.03u
L1 p1 0 10.927u Rser=0.001
L2 0 top 15.987m Rser=0.001
C_topload top 0 13.822p
K1 L1 L2 0.3204
.options savecurrents
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# .options savecurrents removed
assert ".options savecurrents" not in result.lower()
# Rser expanded
assert "R_L1_ser" in result
assert "R_L2_ser" in result
# Capacitor probes inserted
assert "V_probe_C_mmc" in result
assert "V_probe_C_topload" in result
# .save directives present
assert ".save all" in result
assert "i(V1)" in result
assert "i(L1)" in result
assert "i(L2)" in result
assert "i(R_L1_ser)" in result
assert "i(R_L2_ser)" in result
assert "i(V_probe_C_mmc)" in result
assert "i(V_probe_C_topload)" in result
# K element NOT in saves
assert "i(K1)" not in result
# .end still present
assert ".end" in result
def test_save_directives_before_end(self):
""".save directives should appear before .end."""
netlist = """Test
V1 in 0 1
R1 in 0 1k
.options savecurrents
.op
.end
"""
result = preprocess_netlist(netlist)
lines = result.splitlines()
save_idx = next(i for i, l in enumerate(lines) if '.save all' in l)
end_idx = next(i for i, l in enumerate(lines) if l.strip().lower() == '.end')
assert save_idx < end_idx
class TestIsSaveCurrentsOption:
"""Test the _is_savecurrents_option helper."""
def test_standard(self):
assert _is_savecurrents_option(".options savecurrents")
def test_uppercase(self):
assert _is_savecurrents_option(".OPTIONS SAVECURRENTS")
def test_mixed_case(self):
assert _is_savecurrents_option(".Options SaveCurrents")
def test_singular_option(self):
assert _is_savecurrents_option(".option savecurrents")
def test_extra_spaces(self):
assert _is_savecurrents_option(".options savecurrents")
def test_not_savecurrents(self):
assert not _is_savecurrents_option(".options reltol=1e-4")
def test_not_options(self):
assert not _is_savecurrents_option("V1 in 0 1")
class TestCollectComponentNames:
"""Test the _collect_component_names helper."""
def test_basic(self):
lines = ["Title", "V1 in 0 1", "R1 in out 1k", ".tran 1u 10u", ".end"]
names = _collect_component_names(lines)
assert names == ["V1", "R1"]
def test_skips_comments(self):
lines = ["* Comment", "V1 in 0 1", "* Another comment", ".end"]
names = _collect_component_names(lines)
assert names == ["V1"]
def test_skips_K_elements(self):
lines = ["V1 in 0 1", "L1 in 0 10u", "K1 L1 L2 0.5", ".end"]
names = _collect_component_names(lines)
assert "K1" not in names
assert "V1" in names
assert "L1" in names
def test_skips_directives(self):
lines = [".tran 1u 10u", "V1 in 0 1", ".end"]
names = _collect_component_names(lines)
assert names == ["V1"]
class TestCapacitorProbes:
"""Test capacitor current probe insertion (0V voltage sources)."""
def test_probe_inserted(self):
"""Basic capacitor should get a 0V probe voltage source."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.options savecurrents
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert 'C1 _probe_C1 out 1u' in result
assert 'V_probe_C1 in _probe_C1 0' in result
def test_probe_multiple_caps(self):
"""Multiple capacitors should each get separate probes."""
netlist = """Test
V1 in 0 AC 1
C1 in mid 1u
C2 mid out 2u
.options savecurrents
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_C1' in result
assert 'V_probe_C2' in result
assert '_probe_C1' in result
assert '_probe_C2' in result
def test_probe_preserves_params(self):
"""Capacitor parameters (IC=, etc.) should be preserved."""
netlist = """Test
V1 in 0 1
C1 in out 1u IC=0.5
.options savecurrents
.tran 1u 100u
.end
"""
result = preprocess_netlist(netlist)
assert 'C1 _probe_C1 out 1u IC=0.5' in result
def test_probe_only_with_savecurrents(self):
"""No probes should be inserted without .options savecurrents."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert '_probe_' not in result
assert 'V_probe_' not in result
def test_probe_with_rser_combined(self):
"""Full Tesla coil: Rser + probes + savecurrents."""
netlist = """Tesla Coil AC Analysis
V1 vin 0 AC 1
C_mmc vin p1 0.03u
L1 p1 0 10.927u Rser=0.001
L2 0 top 15.987m Rser=0.001
C_topload top 0 13.822p
K1 L1 L2 0.3204
.options savecurrents
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# Rser expanded
assert 'R_L1_ser' in result
assert 'R_L2_ser' in result
# Capacitor probes inserted
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_topload' in result
assert 'C_mmc _probe_C_mmc p1 0.03u' in result
assert 'C_topload _probe_C_topload 0 13.822p' in result
# Probe V sources in .save
assert 'i(V_probe_C_mmc)' in result
assert 'i(V_probe_C_topload)' in result
# K element NOT in saves
assert 'i(K1)' not in result
def test_probe_naming_convention(self):
"""Probe names should follow V_probe_Cname convention."""
netlist = """Test
V1 in 0 1
C_mmc in 0 1u
.options savecurrents
.op
.end
"""
result = preprocess_netlist(netlist)
assert '_probe_C_mmc' in result
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_mmc in _probe_C_mmc 0' in result
def test_probe_not_inside_subckt(self):
"""Capacitors inside .subckt should NOT get probes."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.subckt mycomp a b
C2 a b 10u
.ends mycomp
.options savecurrents
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_C1' in result # top-level probed
assert 'V_probe_C2' not in result # subcircuit NOT probed
def test_probe_before_end(self):
"""Probe V sources should be inserted before .end."""
netlist = """Test
V1 in 0 1
C1 in 0 1u
.options savecurrents
.op
.end
"""
result = preprocess_netlist(netlist)
lines = result.splitlines()
probe_idx = next(i for i, l in enumerate(lines) if 'V_probe_C1' in l)
end_idx = next(i for i, l in enumerate(lines) if l.strip().lower() == '.end')
assert probe_idx < end_idx
class TestProcessCapacitorProbe:
"""Test the _process_capacitor_probe helper."""
def test_basic(self):
mod, probe = _process_capacitor_probe("C1 in out 1u")
assert mod == "C1 _probe_C1 out 1u"
assert probe == "V_probe_C1 in _probe_C1 0"
def test_named_cap(self):
mod, probe = _process_capacitor_probe("C_mmc vin p1 0.03u")
assert mod == "C_mmc _probe_C_mmc p1 0.03u"
assert probe == "V_probe_C_mmc vin _probe_C_mmc 0"
def test_preserves_params(self):
mod, probe = _process_capacitor_probe("C1 a b 10u IC=0.5")
assert "IC=0.5" in mod
assert "_probe_C1" in mod
def test_too_few_tokens(self):
"""Lines with fewer than 4 tokens should be returned unchanged."""
mod, probe = _process_capacitor_probe("C1 a")
assert mod == "C1 a"
assert probe == ""
class TestParseSaveCapCurrents:
"""Test the _parse_save_cap_currents helper."""
def test_single_cap(self):
result = _parse_save_cap_currents(".save i(C_mmc)")
assert result == ["C_mmc"]
def test_multiple_caps(self):
result = _parse_save_cap_currents(".save i(C_mmc) i(C_topload)")
assert result == ["C_mmc", "C_topload"]
def test_non_cap_ignored(self):
result = _parse_save_cap_currents(".save i(V1) i(R1) i(L1)")
assert result == []
def test_mixed(self):
result = _parse_save_cap_currents(".save i(C_mmc) i(V1) i(C_topload)")
assert result == ["C_mmc", "C_topload"]
def test_voltage_saves_ignored(self):
result = _parse_save_cap_currents(".save v(out) v(in)")
assert result == []
def test_not_save_directive(self):
result = _parse_save_cap_currents("V1 in 0 1")
assert result == []
def test_case_insensitive(self):
result = _parse_save_cap_currents(".SAVE I(C_mmc)")
assert result == ["C_mmc"]
def test_save_all(self):
result = _parse_save_cap_currents(".save all")
assert result == []
class TestTargetedCapacitorProbes:
"""Test targeted capacitor probing via .save i(C_name) directives."""
def test_single_targeted_probe(self):
"""Only the named capacitor should get a probe."""
netlist = """Tesla Coil
V1 vin 0 AC 1
C_mmc vin p1 0.03u
C_topload p1 0 13.822p
.save i(C_mmc)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# C_mmc should be probed
assert 'V_probe_C_mmc' in result
assert 'C_mmc _probe_C_mmc p1 0.03u' in result
# C_topload should NOT be probed
assert 'V_probe_C_topload' not in result
assert '_probe_C_topload' not in result
# .save should reference probe, not original cap
assert 'i(V_probe_C_mmc)' in result
# .save all should be present
assert '.save all' in result
def test_multiple_targeted_probes_same_line(self):
"""Multiple caps on one .save line should all get probes."""
netlist = """Test
V1 vin 0 AC 1
C_mmc vin p1 0.03u
C_topload p1 0 13.822p
C_other p1 0 10p
.save i(C_mmc) i(C_topload)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_topload' in result
assert 'V_probe_C_other' not in result
assert 'i(V_probe_C_mmc)' in result
assert 'i(V_probe_C_topload)' in result
def test_multiple_save_lines(self):
"""Caps on separate .save lines should all get probes."""
netlist = """Test
V1 vin 0 AC 1
C_mmc vin p1 0.03u
C_topload p1 0 13.822p
.save i(C_mmc)
.save i(C_topload)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_topload' in result
def test_non_capacitor_save_unchanged(self):
""".save i(V1) and .save i(L1) should pass through without probes."""
netlist = """Test
V1 vin 0 AC 1
L1 vin 0 10u
C1 vin 0 1u
.save i(V1) i(L1)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# No probes inserted (no capacitor current saves)
assert 'V_probe_' not in result
assert '_probe_' not in result
# Original saves preserved
assert 'i(V1)' in result
assert 'i(L1)' in result
def test_mixed_save_cap_and_non_cap(self):
""".save with both cap and non-cap currents."""
netlist = """Test
V1 vin 0 AC 1
C_mmc vin p1 0.03u
L1 p1 0 10u
.save i(C_mmc) i(V1) i(L1)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# Cap probed
assert 'V_probe_C_mmc' in result
# .save rewritten for cap, others preserved
assert 'i(V_probe_C_mmc)' in result
assert 'i(V1)' in result
assert 'i(L1)' in result
def test_savecurrents_overrides_targeted(self):
"""When both .options savecurrents and .save i(C_name) exist,
savecurrents takes precedence (probes everything)."""
netlist = """Test
V1 vin 0 AC 1
C_mmc vin p1 0.03u
C_topload p1 0 13.822p
.save i(C_mmc)
.options savecurrents
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# Both caps probed (savecurrents probes everything)
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_topload' in result
def test_no_save_no_probes(self):
"""Without .save or .options savecurrents, no probes should be inserted."""
netlist = """Test
V1 vin 0 AC 1
C1 vin 0 1u
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_' not in result
assert '_probe_' not in result
def test_targeted_probe_not_inside_subckt(self):
"""Capacitors inside .subckt should NOT get probes even if targeted."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.subckt mycomp a b
C1 a b 10u
.ends mycomp
.save i(C1)
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
# Top-level C1 probed
assert 'V_probe_C1' in result
# Only one probe V-source line (not the subckt one)
probe_vsource_lines = [l for l in result.splitlines()
if l.strip().startswith('V_probe_C1')]
assert len(probe_vsource_lines) == 1
def test_save_voltage_with_cap_current(self):
""".save v(out) alongside .save i(C1) should both work."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
R1 out 0 1k
.save v(out) i(C1)
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
assert 'V_probe_C1' in result
assert 'v(out)' in result
assert 'i(V_probe_C1)' in result
def test_targeted_probe_case_insensitive(self):
""".save I(c_mmc) should match C_mmc component (case-insensitive)."""
netlist = """Test
V1 vin 0 AC 1
C_mmc vin p1 0.03u
.save I(c_mmc)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# Should probe C_mmc (case-insensitive match)
assert 'V_probe_C_mmc' in result
def test_save_all_not_duplicated(self):
"""If user already has .save all, don't add another."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.save all
.save i(C1)
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
# Should have exactly one .save all
save_all_count = sum(1 for line in result.splitlines()
if line.strip().lower().split() == ['.save', 'all'])
assert save_all_count == 1
def test_targeted_with_rser_combined(self):
"""Targeted probing combined with inductor Rser expansion."""
netlist = """Tesla Coil AC Analysis
V1 vin 0 AC 1
C_mmc vin p1 0.03u
L1 p1 0 10.927u Rser=0.001
C_topload top 0 13.822p
.save i(C_mmc)
.ac dec 100 1k 3meg
.end
"""
result = preprocess_netlist(netlist)
# Rser expanded
assert 'R_L1_ser' in result
# Only C_mmc probed, not C_topload
assert 'V_probe_C_mmc' in result
assert 'V_probe_C_topload' not in result
# .save rewritten
assert 'i(V_probe_C_mmc)' in result
def test_probes_before_end(self):
"""Targeted probe V-sources should appear before .end."""
netlist = """Test
V1 in 0 AC 1
C1 in out 1u
.save i(C1)
.ac dec 10 1k 1meg
.end
"""
result = preprocess_netlist(netlist)
lines = result.splitlines()
end_idx = next(i for i, l in enumerate(lines) if l.strip().lower() == '.end')
probe_idx = next(i for i, l in enumerate(lines) if 'V_probe_C1' in l and l.strip().startswith('V_probe'))
assert probe_idx < end_idx

127
tests/test_runner.py

@ -0,0 +1,127 @@
"""Tests for the SpiceRunner interface and NgspiceRunner."""
import os
import pytest
from pyngspice.runner import NgspiceRunner, SubprocessRunner, SimulationError, get_runner
class TestNgspiceRunnerDetect:
"""Test NgspiceRunner.detect() and get_executable_path()."""
def test_detect(self):
"""NgspiceRunner should detect the embedded C++ extension."""
from pyngspice import _cpp_available
assert NgspiceRunner.detect() == _cpp_available
def test_executable_path_is_none(self):
"""Embedded runner has no separate executable."""
assert NgspiceRunner.get_executable_path() is None
@pytest.mark.skipif(
not NgspiceRunner.detect(),
reason="C++ extension not built"
)
class TestNgspiceRunnerSimulation:
"""Integration tests for NgspiceRunner (require built C++ extension)."""
def test_run_simple_rc(self, rc_netlist, tmp_workdir):
"""Run a simple RC circuit and verify output files exist."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(rc_netlist)
assert os.path.isfile(raw_file)
assert raw_file.endswith(".raw")
def test_run_with_rser_preprocessing(self, inductor_rser_netlist, tmp_workdir):
"""Run a netlist with Rser= inductors (tests pre-processing)."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(inductor_rser_netlist)
assert os.path.isfile(raw_file)
def test_run_returns_absolute_paths(self, rc_netlist, tmp_workdir):
"""Output paths should be absolute."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(rc_netlist)
assert os.path.isabs(raw_file)
assert os.path.isabs(log_file)
def test_run_with_savecurrents(self, tesla_coil_netlist, tmp_workdir):
"""Run Tesla coil netlist with .options savecurrents — verify cap currents."""
runner = NgspiceRunner(working_directory=tmp_workdir)
raw_file, log_file = runner.run(tesla_coil_netlist)
assert os.path.isfile(raw_file)
assert raw_file.endswith(".raw")
# Verify raw file contains renamed capacitor current traces
from pyngspice import RawRead
raw = RawRead(raw_file)
trace_names = [n.lower() for n in raw.get_trace_names()]
# Capacitor currents should appear with original names (post-processed)
assert 'i(c_mmc)' in trace_names, f"Missing i(c_mmc) in {trace_names}"
assert 'i(c_topload)' in trace_names, f"Missing i(c_topload) in {trace_names}"
# Probe names should NOT appear (renamed away)
assert 'i(v_probe_c_mmc)' not in trace_names
assert 'i(v_probe_c_topload)' not in trace_names
def test_run_nonexistent_netlist(self, tmp_workdir):
"""Should raise FileNotFoundError for missing netlist."""
runner = NgspiceRunner(working_directory=tmp_workdir)
with pytest.raises(FileNotFoundError):
runner.run("/nonexistent/file.net")
def test_working_directory_created(self, rc_netlist):
"""Working directory should be created if it doesn't exist."""
import tempfile
workdir = os.path.join(tempfile.gettempdir(), "pyngspice_test_auto_create")
try:
runner = NgspiceRunner(working_directory=workdir)
assert os.path.isdir(workdir)
finally:
if os.path.isdir(workdir):
os.rmdir(workdir)
class TestSubprocessRunner:
"""Tests for SubprocessRunner."""
def test_detect(self):
"""detect() should return bool without error."""
result = SubprocessRunner.detect()
assert isinstance(result, bool)
def test_get_executable_path(self):
"""get_executable_path() should return str or None."""
result = SubprocessRunner.get_executable_path()
assert result is None or isinstance(result, str)
class TestGetRunner:
"""Tests for the get_runner factory function."""
def test_auto_returns_runner(self):
"""Auto mode should return some runner if anything is available."""
try:
runner = get_runner(backend="auto")
assert isinstance(runner, (NgspiceRunner, SubprocessRunner))
except RuntimeError:
pytest.skip("No ngspice backend available")
def test_invalid_backend(self):
"""Invalid backend name should raise ValueError."""
with pytest.raises(ValueError, match="Unknown backend"):
get_runner(backend="invalid")
@pytest.mark.skipif(
not NgspiceRunner.detect(),
reason="C++ extension not built"
)
def test_embedded_backend(self, tmp_workdir):
"""Explicitly requesting embedded should return NgspiceRunner."""
runner = get_runner(tmp_workdir, backend="embedded")
assert isinstance(runner, NgspiceRunner)

69
tests/test_sim.py

@ -0,0 +1,69 @@
"""Basic simulation test for pyngspice (moved from root test_sim.py)."""
import os
import tempfile
import pytest
from pyngspice import _cpp_available
@pytest.mark.skipif(not _cpp_available, reason="C++ extension not built")
def test_simulator_basic():
"""Test the low-level Simulator class with a simple RC circuit."""
from pyngspice import Simulator
sim = Simulator()
sim.initialize()
assert sim.is_initialized()
netlist = """RC Test Circuit
V1 in 0 DC 1
R1 in out 1k
C1 out 0 1u
.tran 0.1m 10m
.end
"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.cir', delete=False) as f:
f.write(netlist)
netlist_path = f.name
try:
sim.load_netlist(netlist_path)
sim.run()
plots = sim.all_plots()
assert len(plots) > 0
vectors = sim.all_vectors(plots[0])
assert len(vectors) > 0
finally:
os.unlink(netlist_path)
@pytest.mark.skipif(not _cpp_available, reason="C++ extension not built")
def test_simrunner_basic():
"""Test the SimRunner class with a simple RC circuit."""
from pyngspice import SimRunner, RawRead
with tempfile.TemporaryDirectory() as tmpdir:
netlist_path = os.path.join(tmpdir, "rc.cir")
with open(netlist_path, "w") as f:
f.write("""RC Test
V1 in 0 DC 1
R1 in out 1k
C1 out 0 1u
.tran 0.1m 10m
.end
""")
runner = SimRunner(output_folder=tmpdir)
raw_file, log_file = runner.run_now(netlist_path)
assert os.path.isfile(raw_file)
raw = RawRead(raw_file)
traces = raw.get_trace_names()
assert len(traces) > 0
assert raw.num_points > 0

220
tools/winflexbison/FlexLexer.h

@ -0,0 +1,220 @@
// -*-C++-*-
// FlexLexer.h -- define interfaces for lexical analyzer classes generated
// by flex
// Copyright (c) 1993 The Regents of the University of California.
// All rights reserved.
//
// This code is derived from software contributed to Berkeley by
// Kent Williams and Tom Epperly.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the University nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE.
// This file defines FlexLexer, an abstract class which specifies the
// external interface provided to flex C++ lexer objects, and yyFlexLexer,
// which defines a particular lexer class.
//
// If you want to create multiple lexer classes, you use the -P flag
// to rename each yyFlexLexer to some other xxFlexLexer. You then
// include <FlexLexer.h> in your other sources once per lexer class:
//
// #undef yyFlexLexer
// #define yyFlexLexer xxFlexLexer
// #include <FlexLexer.h>
//
// #undef yyFlexLexer
// #define yyFlexLexer zzFlexLexer
// #include <FlexLexer.h>
// ...
#ifndef __FLEX_LEXER_H
// Never included before - need to define base class.
#define __FLEX_LEXER_H
#include <iostream>
extern "C++" {
struct yy_buffer_state;
typedef int yy_state_type;
class FlexLexer
{
public:
virtual ~FlexLexer() { }
const char* YYText() const { return yytext; }
int YYLeng() const { return yyleng; }
virtual void
yy_switch_to_buffer( yy_buffer_state* new_buffer ) = 0;
virtual yy_buffer_state* yy_create_buffer( std::istream* s, int size ) = 0;
virtual yy_buffer_state* yy_create_buffer( std::istream& s, int size ) = 0;
virtual void yy_delete_buffer( yy_buffer_state* b ) = 0;
virtual void yyrestart( std::istream* s ) = 0;
virtual void yyrestart( std::istream& s ) = 0;
virtual int yylex() = 0;
// Call yylex with new input/output sources.
int yylex( std::istream& new_in, std::ostream& new_out )
{
switch_streams( new_in, new_out );
return yylex();
}
int yylex( std::istream* new_in, std::ostream* new_out = 0)
{
switch_streams( new_in, new_out );
return yylex();
}
// Switch to new input/output streams. A nil stream pointer
// indicates "keep the current one".
virtual void switch_streams( std::istream* new_in,
std::ostream* new_out ) = 0;
virtual void switch_streams( std::istream& new_in,
std::ostream& new_out ) = 0;
int lineno() const { return yylineno; }
int debug() const { return yy_flex_debug; }
void set_debug( int flag ) { yy_flex_debug = flag; }
protected:
char* yytext;
int yyleng;
int yylineno; // only maintained if you use %option yylineno
int yy_flex_debug; // only has effect with -d or "%option debug"
};
}
#endif // FLEXLEXER_H
#if defined(yyFlexLexer) || ! defined(yyFlexLexerOnce)
// Either this is the first time through (yyFlexLexerOnce not defined),
// or this is a repeated include to define a different flavor of
// yyFlexLexer, as discussed in the flex manual.
# define yyFlexLexerOnce
extern "C++" {
class yyFlexLexer : public FlexLexer {
public:
// arg_yyin and arg_yyout default to the cin and cout, but we
// only make that assignment when initializing in yylex().
yyFlexLexer( std::istream& arg_yyin, std::ostream& arg_yyout );
yyFlexLexer( std::istream* arg_yyin = 0, std::ostream* arg_yyout = 0 );
private:
void ctor_common();
public:
virtual ~yyFlexLexer();
void yy_switch_to_buffer( yy_buffer_state* new_buffer );
yy_buffer_state* yy_create_buffer( std::istream* s, int size );
yy_buffer_state* yy_create_buffer( std::istream& s, int size );
void yy_delete_buffer( yy_buffer_state* b );
void yyrestart( std::istream* s );
void yyrestart( std::istream& s );
void yypush_buffer_state( yy_buffer_state* new_buffer );
void yypop_buffer_state();
virtual int yylex();
virtual void switch_streams( std::istream& new_in, std::ostream& new_out );
virtual void switch_streams( std::istream* new_in = 0, std::ostream* new_out = 0 );
virtual int yywrap();
protected:
virtual int LexerInput( char* buf, int max_size );
virtual void LexerOutput( const char* buf, int size );
virtual void LexerError( const char* msg );
void yyunput( int c, char* buf_ptr );
int yyinput();
void yy_load_buffer_state();
void yy_init_buffer( yy_buffer_state* b, std::istream& s );
void yy_flush_buffer( yy_buffer_state* b );
int yy_start_stack_ptr;
int yy_start_stack_depth;
int* yy_start_stack;
void yy_push_state( int new_state );
void yy_pop_state();
int yy_top_state();
yy_state_type yy_get_previous_state();
yy_state_type yy_try_NUL_trans( yy_state_type current_state );
int yy_get_next_buffer();
std::istream yyin; // input source for default LexerInput
std::ostream yyout; // output sink for default LexerOutput
// yy_hold_char holds the character lost when yytext is formed.
char yy_hold_char;
// Number of characters read into yy_ch_buf.
int yy_n_chars;
// Points to current character in buffer.
char* yy_c_buf_p;
int yy_init; // whether we need to initialize
int yy_start; // start state number
// Flag which is used to allow yywrap()'s to do buffer switches
// instead of setting up a fresh yyin. A bit of a hack ...
int yy_did_buffer_switch_on_eof;
size_t yy_buffer_stack_top; /**< index of top of stack. */
size_t yy_buffer_stack_max; /**< capacity of stack. */
yy_buffer_state ** yy_buffer_stack; /**< Stack as an array. */
void yyensure_buffer_stack(void);
// The following are not always needed, but may be depending
// on use of certain flex features (like REJECT or yymore()).
yy_state_type yy_last_accepting_state;
char* yy_last_accepting_cpos;
yy_state_type* yy_state_buf;
yy_state_type* yy_state_ptr;
char* yy_full_match;
int* yy_full_state;
int yy_full_lp;
int yy_lp;
int yy_looking_for_trail_begin;
int yy_more_flag;
int yy_more_len;
int yy_more_offset;
int yy_prev_more_offset;
};
}
#endif // yyFlexLexer || ! yyFlexLexerOnce

34
tools/winflexbison/README.md

@ -0,0 +1,34 @@
# WinFlexBison - Flex and Bison for Microsoft Windows
WinFlexBison is a Windows port of [Flex (the fast lexical analyser)](https://github.com/westes/flex/) and [GNU Bison (parser generator)](https://www.gnu.org/software/bison/).
Both win_flex and win_bison are based on upstream sources but depend on system libraries only.
**NOTE**:
* 2.4.x versions include GNU Bison version 2.7
* 2.5.x versions include GNU Bison version 3.x.x
## License
Flex uses a [BSD license](flex/src/COPYING), GNU Bison is [licensed under the GNU General Public License (GPLv3+)](bison/src/COPYING).
All build scripts in WinFlexBison are distributed under GPLv3+. See [COPYING](COPYING) for details.
All documentation, especially those under custom_build_rules/doc, is distributed under the GNU Free Documentation License (FDL 1.3+).
## Build status
Bison 3.x (master) [![Build status](https://ci.appveyor.com/api/projects/status/58lcjnr0mb9uc8c8/branch/master?svg=true)](https://ci.appveyor.com/project/lexxmark/winflexbison/branch/master) and, for compatibility reasons, Bison 2.7 (bison2.7) [![Build status](https://ci.appveyor.com/api/projects/status/58lcjnr0mb9uc8c8/branch/bison2.7?svg=true)](https://ci.appveyor.com/project/lexxmark/winflexbison/branch/bison2.7)
## Downloads
https://github.com/lexxmark/winflexbison/releases provides stable versions.
To test non-released development versions see the artifacts provided by CI under "Build status".
## Changelog
The release page includes the full Changelog but you may also see the [changelog.md](changelog.md) file.
## Build requirements
* Visual Studio 2017 or newer
* optional: CMake (when building with CMake)
## HowTo
You may use win_flex and win_bison directly on the command line or [use them via CustomBuildRules in VisualStudio](custom_build_rules/README.md).
## Example flex/bison files
See https://github.com/meyerd/flex-bison-example

166
tools/winflexbison/changelog.md

@ -0,0 +1,166 @@
## Changelog
**NOTE**:
* 2.4.x versions include bison version 2.7
* 2.5.x versions include bison version 3.x
### version 2.5.25
* upgrade win_bison to version 3.8.2
* upgrade m4 to version 1.4.19
### version 2.5.24
* upgrade win_bison to version 3.7.4
* upgrade m4 to version 1.4.18
* upgrade gnulib
* removed VS2015 support
* fixed win_bison --update option (renaming opened file)
### version 2.5.23
* upgrade win_bison to version 3.7.1
### version 2.5.22
* upgrade win_bison to version 3.5.0
### version 2.5.21
* avoid _m4eof lines in generated bison code while printing warnings
### version 2.5.20
* recovered invoking win_bison from different folders
### version 2.5.19
* upgrade win_bison to version 3.4.1
### version 2.5.18
* upgrade win_bison to version 3.3.2
### version 2.5.17
* upgrade win_bison to version 3.3.1
### version 2.5.16
* upgrade win_bison to version 3.1
* write output flex/bison files in binary mode "wb" that means use '\n' EOL not '\r\n'
* documentation about how to use the custom build-rules is now included
### versions 2.4.12/2.5.15
* upgrade win_bison to version 3.0.5
### versions 2.4.12/2.5.14
* revert to Visual Studio 2015 due to false positive virus alarms for win_flex.exe
### versions 2.4.11/2.5.13
* fixed VS 2017 compilation errors in location.cc
### versions 2.4.11/2.5.12
* migrate to Visual Studio 2017
### versions 2.4.10/2.5.11
* upgrade win_flex to version 2.6.4
* fixed compilation warnings
### versions 2.4.9/2.5.10
* data folder was up to dated for bison 3.0.4
### versions 2.4.9/2.5.9
* recovered --header-file win_flex option
### versions 2.4.8/2.5.8
* fixed outdated FlexLexer.h file
### versions 2.4.7/2.5.7
* upgrade win_flex to version 2.6.3
* fixed compilation warnings
### versions 2.4.6/2.5.6
* upgrade win_bison to version 3.0.4
* win_bison v2.7 is unchanged
* add separate custom build rules
* for win_bison `custom_build_rules\win_bison_only`
* and win_flex `custom_build_rules\win_flex_only`
### versions 2.4.5/2.5.5
* fix missing Additional Options in custom build rules
* fix incorrect "----header-file" option in flex custom build rules
* add some extra flex options to Visual Studio property pages:
1. Prefix (--prefix="...")
2. C++ Class Name (--yyclass="...")
###versions 2.4.4/2.5.4
* fix silent errors in custom build rules
* add some flex/bison options to Visual Studio property pages:
* Bison:
1. Output File Name (--output="...")
2. Defines File Name (--defines="...")
3. Debug (--debug)
4. Verbose (--verbose)
5. No lines (--no-lines)
6. File Prefix (--file-prefix="...")
7. Graph File (--graph="...")
8. Warnings (--warnings="...")
9. Report (--report="...")
10. Report File Name (--report-file="...")
* Flex:
1. Output File Name (--outfile="...")
2. Header File Name (--header-file="...")
3. Windows compatibility mode (--wincompat)
4. Case-insensitive mode (--case-insensitive)
5. Lex-compatibility mode (--lex-compat)
6. Start Condition Stacks (--stack)
7. Bison Bridge Mode (--bison-bridge)
8. No #line Directives (--noline)
9. Generate Reentrant Scanner (--reentrant)
10. Generate C++ Scanner (--c++)
11. Debug Mode (--debug)
### versions 2.4.3/2.5.3
* fix incorrect #line directives in win_flex.exe
see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=542482
### versions 2.4.2/2.5.2
* backport parallel invocations of win_bison version 2.7
* win_bison of version 3.0 is unchanged
### versions 2.4.1/2.5.1
* remove XSI extention syntax for fprintf function (not implemented in windows)
* this fixes Graphviz files generation for bison
**NOTE**:
* 2.4.x versions will include bison version 2.7
* 2.5.x versions will include bison version 3.0
### version 2.5
* upgrade win_bison to version 3.0 and make temporary win_bison's files process unique (so parallel invocations of win_bison are possible)
**NOTE**: Several deprecated features were removed in bison 3.0 so this version can break your projects.
Please see http://savannah.gnu.org/forum/forum.php?forum_id=7663
For the reason of compatibility I don't change win_flex_bison-latest.zip to refer to win_flex_bison-2.5.zip file.
It still refer to win_flex_bison-2.4.zip
### version 2.4
* fix problem with "m4_syscmd is not implemented" message.
* Now win_bison should output correct diagnostic and error messages.
### version 2.3
* hide __attribute__ construction for non GCC compilers
### version 2.2
* added --wincompat option to win_flex (this option changes `<unistd.h>` unix include with `<io.h>` windows analog
also `isatty/fileno` functions changed to `_isatty/_fileno`)
fixed two "'<' : signed/unsigned mismatch" warnings in win_flex generated file
### version 2.1
* fixed crash when execute win_bison.exe under WindowsXP (argv[0] don't have full application path)
* added win_flex_bison-latest.zip package to freeze download link
### version 2.0
* upgrade win_bison to version 2.7 and win_flex to version 2.5.37
### version 1.2
* fixed win_flex.exe #line directives (some #line directives in output file were with unescaped backslashes)
### version 1.1
* fixed win_flex.exe parallel invocations (now all temporary files are process specific)
* added FLEX_TMP_DIR environment variable support to redirect temporary files folder
* added '.exe' to program name in win_flex.exe --version output (CMake support)
* fixed win_bison.exe to use "/data" subfolder related to executable path rather than current working directory
* added BISON_PKGDATADIR environment variable to redirect "/data" subfolder to a different place

173
tools/winflexbison/custom_build_rules/README.md

@ -0,0 +1,173 @@
How to setup custom build rules for Visual Studio 2010 and up.
---------------
First of all you should have the necessary files.
Custom Build rules are separated into a file triplet of `.xml`, `.targets` and `.props`.
You find the custom build rules for win_flex_bison in the **custom_build_rules** directory of the win_flex_bison archive.
You may choose to install one of the following rule set
* the combined rules - [alternative download][1]:
* [win_flex_bison_custom_build.props ](win_flex_bison/win_flex_bison_custom_build.props)
* [win_flex_bison_custom_build.targets](win_flex_bison/win_flex_bison_custom_build.targets)
* [win_flex_bison_custom_build.xml ](win_flex_bison/win_flex_bison_custom_build.xml)
* flex only rules - [alternative download][2]:
* [win_flex_custom_build.props ](win_flex_only/win_flex_custom_build.props)
* [win_flex_custom_build.targets ](win_flex_only/win_flex_custom_build.targets)
* [win_flex_custom_build.xml ](win_flex_only/win_flex_custom_build.xml)
* bison only rules - [alternative download][3]:
* [win_bison_custom_build.props ](win_bison_only/win_bison_custom_build.props)
* [win_bison_custom_build.targets](win_bison_only/win_bison_custom_build.targets)
* [win_bison_custom_build.xml ](win_bison_only/win_bison_custom_build.xml)
This documentation uses the combined rule-set but can be used for all rule-sets.
[1]: https://sourceforge.net/projects/winflexbison/files/win_flex_bison_custom_build_rules.zip/download "Combined build rules for Bison and Flex"
[2]: https://sourceforge.net/projects/winflexbison/files/win_flex_custom_build_rules.zip/download "Build rules for Flex only"
[3]: https://sourceforge.net/projects/winflexbison/files/win_bison_custom_build_rules.zip/download "Build rules for Bison only"
----
Launch Visual Studio and open an VC/VC++ project.
Open context menu for project item in Solution Explorer panel and select "**Build Customizations...**" menu item.
(Note: newer VS versions have this below sub-menu "**Build Dependencies...**".)
![Build Customizations in Solution Explorer](docs/1.png)
----
In popup dialog "Visual C++ Build Customization Files" press "**Find Existing...**" button.
![Customization Files](docs/2.png)
----
In Open File dialog select "**win_flex_bison_custom_build.targets**" file and press "Open".
(Note: you may have to switch the file filter to "(*.*) all files".)
----
You will see "Add Search Path?" message box, press "Yes".
![Adding Search Path](docs/3.png)
----
In "Visual C++ Build Customization Files" dialog check just added item **win_flex_bison_custom_build** and press "OK".
![activate custom build rule](docs/4.png)
----
Now you can add flex and bison files to the project...
![project with flex and bison files added](docs/5.png)
... and build.
In build output you should see something like this:
~~~~
1>------ Rebuild All started: Project: ConsoleApplication1, Configuration: Debug Win32 ------
1> Process sample bison file
1> Process sample flex file
1> stdafx.cpp
1> ConsoleApplication1.cpp
1> Generating Code...
1> ConsoleApplication1.vcxproj -> C:\Users\ConsoleApplication1\Debug\ConsoleApplication1.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
~~~~
-----
For **sample.y** bison file there are two output files: **sample.tab.h** and **sample.tab.cpp**.
For **sample.l** flex file you'll got **sample.flex.cpp**.
Now you can add them to the project and build. (*Don't forget to exclude cpp files from using precompiled headers*)
![Include generated files into Solution](docs/6.png)
~~~~
1>------ Build started: Project: ConsoleApplication1, Configuration: Debug Win32 ------
1> Process sample bison file
1> Process sample flex file
1> sample.tab.cpp
1> sample.flex.cpp
1> Generating Code...
1> ConsoleApplication1.vcxproj -> C:\Users\ConsoleApplication1\Debug\ConsoleApplication1.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
~~~~
----
If your flex/bison file is incorrect and you've got an error. But you don't see actual error message, something like this:
~~~~
1>------ Build started: Project: ConsoleApplication2, Configuration: Debug Win32 ------
1> Process "grammar.y" bison file
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: The command "
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: start /B /WAIT /D "C:...\ConsoleApplication2\ConsoleApplication2\" win_bison.exe --output="grammar.tab.cpp" --defines="grammar.tab.h" --graph="1.dot" "grammar.y"
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: exit /b %errorlevel%" exited with code 1.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
~~~~
You can change Build Output Verbosity from "Minimal" to "Normal" in "Options" dialog
![Verbosity](docs/Verbosity.png)
Then you will see more detailed output:
~~~~
1>BisonTarget:
1> Process "grammar.y" bison file
1> grammar.y:51.1-4: error: invalid directive: '%sdw'
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: The command "
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: start /B /WAIT /D "C:...\ConsoleApplication2\" win_bison.exe --output="grammar.tab.cpp" --defines="grammar.tab.h" --graph="1.dot" "grammar.y"
1>C:...\custom_build_rules\win_flex_bison_custom_build.targets(55,5): error MSB3721: exit /b %errorlevel%" exited with code 1.
1>
1>Build FAILED.
1>
1>Time Elapsed 00:00:01.21
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
~~~~
----
Also you can tune some flex/bison options in files properties dialog:
![Opening File Properties](docs/Properties.png)
![Property Page for Flex sources](docs/FlexProperties.png)
![Property Page for Bison sources](docs/BisonProperties.png)
----
To debug your scanner or parser you can set break points right into **sample.y** or **sample.l** code.
![Debugging Flex source files](docs/Flex_debuging.png)
----
To use the Visual C++ Code editor for Flex/Bison files instead of the text editor adjust your editor settings as follows:
* click **Options** on the **Tools** menu, expand the **Text Editor** node and select **File Extension**
* type extension **`l`** in the **Extension** field and choose **Microsoft Visual C++** in the **Editor** drop-down field, click **Add**
* do the same for the extension **`y`**
You now have syntax highlighting, code-completion and show definition options in your Flex/Bison source.
----
Enjoy!

BIN
tools/winflexbison/custom_build_rules/docs/1.png

After

Width: 531  |  Height: 603  |  Size: 31 KiB

BIN
tools/winflexbison/custom_build_rules/docs/2.png

After

Width: 602  |  Height: 359  |  Size: 11 KiB

BIN
tools/winflexbison/custom_build_rules/docs/3.png

After

Width: 496  |  Height: 160  |  Size: 7.1 KiB

BIN
tools/winflexbison/custom_build_rules/docs/4.png

After

Width: 602  |  Height: 359  |  Size: 12 KiB

BIN
tools/winflexbison/custom_build_rules/docs/5.png

After

Width: 345  |  Height: 250  |  Size: 11 KiB

BIN
tools/winflexbison/custom_build_rules/docs/6.png

After

Width: 346  |  Height: 364  |  Size: 15 KiB

BIN
tools/winflexbison/custom_build_rules/docs/BisonProperties.png

After

Width: 774  |  Height: 452  |  Size: 26 KiB

BIN
tools/winflexbison/custom_build_rules/docs/FlexProperties.png

After

Width: 776  |  Height: 453  |  Size: 26 KiB

BIN
tools/winflexbison/custom_build_rules/docs/Flex_debuging.png

After

Width: 985  |  Height: 429  |  Size: 27 KiB

BIN
tools/winflexbison/custom_build_rules/docs/Properties.png

After

Width: 409  |  Height: 603  |  Size: 31 KiB

BIN
tools/winflexbison/custom_build_rules/docs/Verbosity.png

After

Width: 776  |  Height: 458  |  Size: 29 KiB

23
tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.props

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup
Condition="'$(BisonBeforeTargets)' == '' and '$(BisonAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<BisonBeforeTargets>Midl</BisonBeforeTargets>
<BisonAfterTargets>CustomBuild</BisonAfterTargets>
</PropertyGroup>
<PropertyGroup>
<BisonDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(BisonDependsOn)</BisonDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Bison>
<OutputFile>%(Filename).tab.cpp</OutputFile>
<DefinesFile>%(Filename).tab.h</DefinesFile>
<CommandLineTemplate>
start /B /WAIT /D "%(RootDir)%(Directory)" win_bison.exe [AllOptions] [AdditionalOptions] "%(Filename)%(Extension)"
exit /b %errorlevel%</CommandLineTemplate>
<Outputs>%(RootDir)%(Directory)%(OutputFile);%(RootDir)%(Directory)%(DefinesFile);</Outputs>
<ExecutionDescription>Process "%(Filename)%(Extension)" bison file</ExecutionDescription>
</Bison>
</ItemDefinitionGroup>
</Project>

91
tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.targets

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PropertyPageSchema
Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName
Include="Bison">
<Targets>BisonTarget</Targets>
</AvailableItemName>
</ItemGroup>
<UsingTask
TaskName="Bison"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<Target
Name="BisonTarget"
BeforeTargets="$(BisonBeforeTargets)"
AfterTargets="$(BisonAfterTargets)"
Condition="'@(Bison)' != ''"
DependsOnTargets="$(BisonDependsOn);ComputeBisonOutput"
Outputs="%(Bison.Outputs)"
Inputs="%(Bison.Identity);%(Bison.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Bison
Remove="@(Bison)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Bison_tlog
Include="%(Bison.Outputs)"
Condition="'%(Bison.Outputs)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'">
<Source>@(Bison, '|')</Source>
</Bison_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Bison.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Bison_tlog)' != '' and '%(Bison_tlog.ExcludedFromBuild)' != 'true'"
File="$(IntDir)$(ProjectName).write.1.tlog"
Lines="^%(Bison_tlog.Source);@(Bison_tlog-&gt;'%(Fullpath)')" />
<Bison
Condition="'@(Bison)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Bison.CommandLineTemplate)"
OutputFile="%(Bison.OutputFile)"
DefinesFile="%(Bison.DefinesFile)"
Debug="%(Bison.Debug)"
Verbose="%(Bison.Verbose)"
NoLines="%(Bison.NoLines)"
FilePrefix="%(Bison.FilePrefix)"
GraphFile="%(Bison.GraphFile)"
Warnings="%(Bison.Warnings)"
Report="%(Bison.Report)"
ReportFile="%(Bison.ReportFile)"
AdditionalOptions="%(Bison.AdditionalOptions)"
Inputs="%(Bison.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeBisonOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeBisonOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeBisonOutput"
Condition="'@(Bison)' != ''">
<ItemGroup>
<BisonDirsToMake
Condition="'@(Bison)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'"
Include="%(Bison.Outputs)" />
<Link
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(BisonDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
</Project>

281
tools/winflexbison/custom_build_rules/win_bison_only/win_bison_custom_build.xml

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:transformCallback="Microsoft.Cpp.Dev10.ConvertPropertyCallback">
<Rule
Name="Bison"
PageTemplate="tool"
DisplayName="Bison files"
SwitchPrefix="--"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Bison" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Bison Options">
<Category.DisplayName>
<sys:String>Bison Options</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="OutputFile"
Category="Bison Options"
IsRequired="true"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Output File Name"
Description="Specify the file for the parser implementation file. --output=value"
Switch="output=&quot;[value]&quot;"
/>
<StringListProperty
Name="DefinesFile"
Category="Bison Options"
Subtype="file"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Defines File Name"
Description="Pretend that %defines was specified, i.e., write an extra output file containing macro definitions for the token type names defined in the grammar, as well as a few other declarations. --defines=value"
Switch="defines=&quot;[value]&quot;"
/>
<BoolProperty
Name="Debug"
Category="Bison Options"
DisplayName="Debug"
Description="In the parser implementation file, define the macro YYDEBUG to 1 if it is not already defined, so that the debugging facilities are compiled. (--debug)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Enabling-Traces.html#Enabling-Traces"
Switch="debug" />
<BoolProperty
Name="Verbose"
Category="Bison Options"
DisplayName="Verbose"
Description="Write an extra output file containing verbose descriptions of the parser states and what is done for each type of lookahead token in that state. (--verbose)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Understanding.html#Understanding"
Switch="verbose" />
<BoolProperty
Name="NoLines"
Category="Bison Options"
DisplayName="No lines"
Description="Don’t put any #line preprocessor commands in the parser implementation file. (--no-lines)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
Switch="no-lines" />
<StringListProperty
Name="FilePrefix"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="File Prefix"
Description="Pretend that %file-prefix was specified, i.e., specify prefix to use for all Bison output file names. --file-prefix=prefix"
Switch="file-prefix=&quot;[value]&quot;"
/>
<StringListProperty
Name="GraphFile"
Category="Bison Options"
Subtype="file"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Graph File"
Description="Output a graphical representation of the parser’s automaton computed by Bison, in Graphviz DOT format. --graph=file"
Switch="graph=&quot;[value]&quot;"
/>
<EnumProperty
Name="Warnings"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Warnings"
Description="Output warnings falling in category. (--warnings=category)">
<EnumValue
Name="midrule-values"
DisplayName="midrule-values"
Switch="warnings=midrule-values"/>
<EnumValue
Name="yacc"
DisplayName="yacc"
Switch="warnings=yacc"/>
<EnumValue
Name="conflicts-sr"
DisplayName="conflicts-sr"
Switch="warnings=conflicts-sr"/>
<EnumValue
Name="conflicts-rr"
DisplayName="conflicts-rr"
Switch="warnings=conflicts-rr"/>
<EnumValue
Name="other"
DisplayName="other"
Switch="warnings=other"/>
<EnumValue
Name="all"
DisplayName="all"
Switch="warnings=all"/>
<EnumValue
Name="none"
DisplayName="none"
Switch="warnings=none"/>
<EnumValue
Name="error"
DisplayName="error"
Switch="warnings=error"/>
</EnumProperty>
<EnumProperty
Name="Report"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Report"
Description="Write an extra output file containing verbose description of the comma separated list of things. (--report=things)">
<EnumValue
Name="state"
DisplayName="state"
Switch="report=state"/>
<EnumValue
Name="itemset"
DisplayName="itemset"
Switch="report=itemset"/>
<EnumValue
Name="lookahead"
DisplayName="lookahead"
Switch="report=lookahead"/>
<EnumValue
Name="solved"
DisplayName="solved"
Switch="report=solved"/>
<EnumValue
Name="all"
DisplayName="all"
Switch="report=all"/>
<EnumValue
Name="none"
DisplayName="none"
Switch="report=none"/>
</EnumProperty>
<StringListProperty
Name="ReportFile"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Report File Name"
Description="Specify the file for the verbose description. --report-file=value"
Switch="report-file=&quot;[value]&quot;"
/>
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Bison"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="BisonBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^BisonBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="BisonAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^BisonAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Bison"
DisplayName="Bison files" />
<FileExtension
Name="*.y"
ContentType="Bison" />
<ContentType
Name="Bison"
DisplayName="Bison files"
ItemType="Bison" />
</ProjectSchemaDefinitions>

43
tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.props

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup
Condition="'$(BisonBeforeTargets)' == '' and '$(BisonAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<BisonBeforeTargets>Midl</BisonBeforeTargets>
<BisonAfterTargets>CustomBuild</BisonAfterTargets>
</PropertyGroup>
<PropertyGroup>
<BisonDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(BisonDependsOn)</BisonDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Bison>
<OutputFile>%(Filename).tab.cpp</OutputFile>
<DefinesFile>%(Filename).tab.h</DefinesFile>
<CommandLineTemplate>
start /B /WAIT /D "%(RootDir)%(Directory)" win_bison.exe [AllOptions] [AdditionalOptions] "%(Filename)%(Extension)"
exit /b %errorlevel%</CommandLineTemplate>
<Outputs>%(RootDir)%(Directory)%(OutputFile);%(RootDir)%(Directory)%(DefinesFile);</Outputs>
<ExecutionDescription>Process "%(Filename)%(Extension)" bison file</ExecutionDescription>
</Bison>
</ItemDefinitionGroup>
<PropertyGroup
Condition="'$(FlexBeforeTargets)' == '' and '$(FlexAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<FlexBeforeTargets>Midl</FlexBeforeTargets>
<FlexAfterTargets>CustomBuild</FlexAfterTargets>
</PropertyGroup>
<PropertyGroup>
<FlexDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(FlexDependsOn)</FlexDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Flex>
<OutputFile>%(Filename).flex.cpp</OutputFile>
<Wincompat>true</Wincompat>
<CommandLineTemplate>
start /B /WAIT /D "%(RootDir)%(Directory)" win_flex.exe [AllOptions] [AdditionalOptions] "%(Filename)%(Extension)"
exit /b %errorlevel%</CommandLineTemplate>
<Outputs>%(RootDir)%(Directory)%(OutputFile);</Outputs>
<ExecutionDescription>Process "%(Filename)%(Extension)" flex file</ExecutionDescription>
</Flex>
</ItemDefinitionGroup>
</Project>

178
tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.targets

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PropertyPageSchema
Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName
Include="Bison">
<Targets>BisonTarget</Targets>
</AvailableItemName>
<AvailableItemName
Include="Flex">
<Targets>FlexTarget</Targets>
</AvailableItemName>
</ItemGroup>
<UsingTask
TaskName="Bison"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<UsingTask
TaskName="Flex"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<Target
Name="BisonTarget"
BeforeTargets="$(BisonBeforeTargets)"
AfterTargets="$(BisonAfterTargets)"
Condition="'@(Bison)' != ''"
DependsOnTargets="$(BisonDependsOn);ComputeBisonOutput"
Outputs="%(Bison.Outputs)"
Inputs="%(Bison.Identity);%(Bison.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Bison
Remove="@(Bison)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Bison_tlog
Include="%(Bison.Outputs)"
Condition="'%(Bison.Outputs)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'">
<Source>@(Bison, '|')</Source>
</Bison_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Bison.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Bison_tlog)' != '' and '%(Bison_tlog.ExcludedFromBuild)' != 'true'"
File="$(IntDir)$(ProjectName).write.1.tlog"
Lines="^%(Bison_tlog.Source);@(Bison_tlog-&gt;'%(Fullpath)')" />
<Bison
Condition="'@(Bison)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Bison.CommandLineTemplate)"
OutputFile="%(Bison.OutputFile)"
DefinesFile="%(Bison.DefinesFile)"
Debug="%(Bison.Debug)"
Verbose="%(Bison.Verbose)"
NoLines="%(Bison.NoLines)"
FilePrefix="%(Bison.FilePrefix)"
GraphFile="%(Bison.GraphFile)"
Warnings="%(Bison.Warnings)"
Report="%(Bison.Report)"
ReportFile="%(Bison.ReportFile)"
AdditionalOptions="%(Bison.AdditionalOptions)"
Inputs="%(Bison.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeBisonOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeBisonOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeBisonOutput"
Condition="'@(Bison)' != ''">
<ItemGroup>
<BisonDirsToMake
Condition="'@(Bison)' != '' and '%(Bison.ExcludedFromBuild)' != 'true'"
Include="%(Bison.Outputs)" />
<Link
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(BisonDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(BisonDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
<Target
Name="FlexTarget"
BeforeTargets="$(FlexBeforeTargets)"
AfterTargets="$(FlexAfterTargets)"
Condition="'@(Flex)' != ''"
DependsOnTargets="$(FlexDependsOn);ComputeFlexOutput"
Outputs="%(Flex.Outputs)"
Inputs="%(Flex.Identity);%(Flex.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Flex
Remove="@(Flex)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Flex_tlog
Include="%(Flex.Outputs)"
Condition="'%(Flex.Outputs)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'">
<Source>@(Flex, '|')</Source>
</Flex_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Flex.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Flex_tlog)' != '' and '%(Flex_tlog.ExcludedFromBuild)' != 'true'"
File="$(IntDir)$(ProjectName).write.1.tlog"
Lines="^%(Flex_tlog.Source);@(Flex_tlog-&gt;'%(Fullpath)')" />
<Flex
Condition="'@(Flex)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Flex.CommandLineTemplate)"
OutputFile="%(Flex.OutputFile)"
HeaderFile="%(Flex.HeaderFile)"
Prefix="%(Flex.Prefix)"
Wincompat="%(Flex.Wincompat)"
CaseInsensitive="%(Flex.CaseInsensitive)"
LexCompat="%(Flex.LexCompat)"
Stack="%(Flex.Stack)"
BisonBridge="%(Flex.BisonBridge)"
Noline="%(Flex.Noline)"
Reentrant="%(Flex.Reentrant)"
Cpp="%(Flex.Cpp)"
CppClassName="%(Flex.CppClassName)"
Debug="%(Flex.Debug)"
AdditionalOptions="%(Flex.AdditionalOptions)"
Inputs="%(Flex.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeFlexOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeFlexOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeFlexOutput"
Condition="'@(Flex)' != ''">
<ItemGroup>
<FlexDirsToMake
Condition="'@(Flex)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'"
Include="%(Flex.Outputs)" />
<Link
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(FlexDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
</Project>

521
tools/winflexbison/custom_build_rules/win_flex_bison/win_flex_bison_custom_build.xml

@ -0,0 +1,521 @@
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:transformCallback="Microsoft.Cpp.Dev10.ConvertPropertyCallback">
<Rule
Name="Bison"
PageTemplate="tool"
DisplayName="Bison files"
SwitchPrefix="--"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Bison" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Bison Options">
<Category.DisplayName>
<sys:String>Bison Options</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="OutputFile"
Category="Bison Options"
IsRequired="true"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Output File Name"
Description="Specify the file for the parser implementation file. --output=value"
Switch="output=&quot;[value]&quot;"
/>
<StringListProperty
Name="DefinesFile"
Category="Bison Options"
Subtype="file"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Defines File Name"
Description="Pretend that %defines was specified, i.e., write an extra output file containing macro definitions for the token type names defined in the grammar, as well as a few other declarations. --defines=value"
Switch="defines=&quot;[value]&quot;"
/>
<BoolProperty
Name="Debug"
Category="Bison Options"
DisplayName="Debug"
Description="In the parser implementation file, define the macro YYDEBUG to 1 if it is not already defined, so that the debugging facilities are compiled. (--debug)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Enabling-Traces.html#Enabling-Traces"
Switch="debug" />
<BoolProperty
Name="Verbose"
Category="Bison Options"
DisplayName="Verbose"
Description="Write an extra output file containing verbose descriptions of the parser states and what is done for each type of lookahead token in that state. (--verbose)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Understanding.html#Understanding"
Switch="verbose" />
<BoolProperty
Name="NoLines"
Category="Bison Options"
DisplayName="No lines"
Description="Don’t put any #line preprocessor commands in the parser implementation file. (--no-lines)"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
Switch="no-lines" />
<StringListProperty
Name="FilePrefix"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="File Prefix"
Description="Pretend that %file-prefix was specified, i.e., specify prefix to use for all Bison output file names. --file-prefix=prefix"
Switch="file-prefix=&quot;[value]&quot;"
/>
<StringListProperty
Name="GraphFile"
Category="Bison Options"
Subtype="file"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Graph File"
Description="Output a graphical representation of the parser’s automaton computed by Bison, in Graphviz DOT format. --graph=file"
Switch="graph=&quot;[value]&quot;"
/>
<EnumProperty
Name="Warnings"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Warnings"
Description="Output warnings falling in category. (--warnings=category)">
<EnumValue
Name="midrule-values"
DisplayName="midrule-values"
Switch="warnings=midrule-values"/>
<EnumValue
Name="yacc"
DisplayName="yacc"
Switch="warnings=yacc"/>
<EnumValue
Name="conflicts-sr"
DisplayName="conflicts-sr"
Switch="warnings=conflicts-sr"/>
<EnumValue
Name="conflicts-rr"
DisplayName="conflicts-rr"
Switch="warnings=conflicts-rr"/>
<EnumValue
Name="other"
DisplayName="other"
Switch="warnings=other"/>
<EnumValue
Name="all"
DisplayName="all"
Switch="warnings=all"/>
<EnumValue
Name="none"
DisplayName="none"
Switch="warnings=none"/>
<EnumValue
Name="error"
DisplayName="error"
Switch="warnings=error"/>
</EnumProperty>
<EnumProperty
Name="Report"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Report"
Description="Write an extra output file containing verbose description of the comma separated list of things. (--report=things)">
<EnumValue
Name="state"
DisplayName="state"
Switch="report=state"/>
<EnumValue
Name="itemset"
DisplayName="itemset"
Switch="report=itemset"/>
<EnumValue
Name="lookahead"
DisplayName="lookahead"
Switch="report=lookahead"/>
<EnumValue
Name="solved"
DisplayName="solved"
Switch="report=solved"/>
<EnumValue
Name="all"
DisplayName="all"
Switch="report=all"/>
<EnumValue
Name="none"
DisplayName="none"
Switch="report=none"/>
</EnumProperty>
<StringListProperty
Name="ReportFile"
Category="Bison Options"
HelpUrl="https://www.gnu.org/software/bison/manual/html_node/Bison-Options.html#Bison-Options"
DisplayName="Report File Name"
Description="Specify the file for the verbose description. --report-file=value"
Switch="report-file=&quot;[value]&quot;"
/>
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Bison"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="BisonBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^BisonBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="BisonAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^BisonAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Bison"
DisplayName="Bison files" />
<FileExtension
Name="*.y"
ContentType="Bison" />
<ContentType
Name="Bison"
DisplayName="Bison files"
ItemType="Bison" />
<Rule
Name="Flex"
PageTemplate="tool"
DisplayName="Flex files"
SwitchPrefix="--"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Flex" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Flex Options">
<Category.DisplayName>
<sys:String>Flex Options</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="OutputFile"
Category="Flex Options"
IsRequired="true"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Output File Name"
Description="Directs flex to write the scanner to the file ‘FILE’ instead of ‘lex.yy.c’. --outfile=value"
Switch="outfile=&quot;[value]&quot;"
/>
<StringListProperty
Name="HeaderFile"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Header File Name"
Description="Instructs flex to write a C header to ‘FILE’. This file contains function prototypes, extern variables, and types used by the scanner. Only the external API is exported by the header file. (--header-file=value)"
Switch="header-file=&quot;[value]&quot;"
/>
<StringListProperty
Name="Prefix"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Prefix"
Description="Changes the default ‘yy’ prefix used by flex for all globally-visible variable and function names to instead be ‘PREFIX’. For example, ‘--prefix=foo’ changes the name of yytext to footext. It also changes the name of the default output file from lex.yy.c to lex.foo.c. (--prefix=value)"
Switch="prefix=&quot;[value]&quot;"
/>
<BoolProperty
Name="Wincompat"
Category="Flex Options"
DisplayName="Windows compatibility mode"
Description="This option changes 'unistd.h' unix header with windows analog 'io.h' and replaces isatty/fileno functions to safe windows analogs _isatty/_fileno. (--wincompat)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="wincompat" />
<BoolProperty
Name="CaseInsensitive"
Category="Flex Options"
DisplayName="Case-insensitive mode"
Description="Instructs flex to generate a case-insensitive scanner. The case of letters given in the flex input patterns will be ignored, and tokens in the input will be matched regardless of case. The matched text given in yytext will have the preserved case (i.e., it will not be folded). (--case-insensitive)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="case-insensitive" />
<BoolProperty
Name="LexCompat"
Category="Flex Options"
DisplayName="Lex-compatibility mode"
Description="Turns on maximum compatibility with the original AT&amp;T lex implementation. Note that this does not mean full compatibility. Use of this option costs a considerable amount of performance, and it cannot be used with the ‘--c++’, ‘--full’, ‘--fast’, ‘-Cf’, or ‘-CF’ options. (--lex-compat)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="lex-compat" />
<BoolProperty
Name="Stack"
Category="Flex Options"
DisplayName="Start Condition Stacks"
Description="Enables the use of start condition stacks. (--stack)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="stack" />
<BoolProperty
Name="BisonBridge"
Category="Flex Options"
DisplayName="Bison Bridge Mode"
Description="Instructs flex to generate a C scanner that is meant to be called by a GNU bison parser. The scanner has minor API changes for bison compatibility. In particular, the declaration of yylex is modified to take an additional parameter, yylval. (--bison-bridge)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="bison-bridge" />
<BoolProperty
Name="Noline"
Category="Flex Options"
DisplayName="No #line Directives"
Description="Instructs flex not to generate #line directives. (--noline)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="noline" />
<BoolProperty
Name="Reentrant"
Category="Flex Options"
DisplayName="Generate Reentrant Scanner"
Description="Instructs flex to generate a reentrant C scanner. The generated scanner may safely be used in a multi-threaded environment. The API for a reentrant scanner is different than for a non-reentrant scanner. (--reentrant)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="reentrant" />
<BoolProperty
Name="Cpp"
Category="Flex Options"
DisplayName="Generate C++ Scanner"
Description="Specifies that you want flex to generate a C++ scanner class. (--c++)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="c++" />
<StringListProperty
Name="CppClassName"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="C++ Class Name"
Description="Only applies when generating a C++ scanner (the ‘--c++’ option). It informs flex that you have derived NAME as a subclass of yyFlexLexer, so flex will place your actions in the member function foo::yylex() instead of yyFlexLexer::yylex(). It also generates a yyFlexLexer::yylex() member function that emits a run-time error (by invoking yyFlexLexer::LexerError()) if called. (--yyclass=value)"
Switch="yyclass=&quot;[value]&quot;" />
<BoolProperty
Name="Debug"
Category="Flex Options"
DisplayName="Debug Mode"
Description="Makes the generated scanner run in debug mode. Whenever a pattern is recognized and the global variable yy_flex_debug is non-zero (which is the default), the scanner will write to ‘stderr’ a line of the form: -accepting rule at line 53 ('the matched text'). (--debug)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="debug" />
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Flex"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="FlexBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^FlexBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="FlexAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^FlexAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Flex"
DisplayName="Flex files" />
<FileExtension
Name="*.l"
ContentType="Flex" />
<ContentType
Name="Flex"
DisplayName="Flex files"
ItemType="Flex" />
</ProjectSchemaDefinitions>

23
tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.props

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup
Condition="'$(FlexBeforeTargets)' == '' and '$(FlexAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<FlexBeforeTargets>Midl</FlexBeforeTargets>
<FlexAfterTargets>CustomBuild</FlexAfterTargets>
</PropertyGroup>
<PropertyGroup>
<FlexDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(FlexDependsOn)</FlexDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Flex>
<OutputFile>%(Filename).flex.cpp</OutputFile>
<Wincompat>true</Wincompat>
<CommandLineTemplate>
start /B /WAIT /D "%(RootDir)%(Directory)" win_flex.exe [AllOptions] [AdditionalOptions] "%(Filename)%(Extension)"
exit /b %errorlevel%</CommandLineTemplate>
<Outputs>%(RootDir)%(Directory)%(OutputFile);</Outputs>
<ExecutionDescription>Process "%(Filename)%(Extension)" flex file</ExecutionDescription>
</Flex>
</ItemDefinitionGroup>
</Project>

94
tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.targets

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PropertyPageSchema
Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName
Include="Flex">
<Targets>FlexTarget</Targets>
</AvailableItemName>
</ItemGroup>
<UsingTask
TaskName="Flex"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<Target
Name="FlexTarget"
BeforeTargets="$(FlexBeforeTargets)"
AfterTargets="$(FlexAfterTargets)"
Condition="'@(Flex)' != ''"
DependsOnTargets="$(FlexDependsOn);ComputeFlexOutput"
Outputs="%(Flex.Outputs)"
Inputs="%(Flex.Identity);%(Flex.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Flex
Remove="@(Flex)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Flex_tlog
Include="%(Flex.Outputs)"
Condition="'%(Flex.Outputs)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'">
<Source>@(Flex, '|')</Source>
</Flex_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Flex.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Flex_tlog)' != '' and '%(Flex_tlog.ExcludedFromBuild)' != 'true'"
File="$(IntDir)$(ProjectName).write.1.tlog"
Lines="^%(Flex_tlog.Source);@(Flex_tlog-&gt;'%(Fullpath)')" />
<Flex
Condition="'@(Flex)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Flex.CommandLineTemplate)"
OutputFile="%(Flex.OutputFile)"
HeaderFile="%(Flex.HeaderFile)"
Prefix="%(Flex.Prefix)"
Wincompat="%(Flex.Wincompat)"
CaseInsensitive="%(Flex.CaseInsensitive)"
LexCompat="%(Flex.LexCompat)"
Stack="%(Flex.Stack)"
BisonBridge="%(Flex.BisonBridge)"
Noline="%(Flex.Noline)"
Reentrant="%(Flex.Reentrant)"
Cpp="%(Flex.Cpp)"
CppClassName="%(Flex.CppClassName)"
Debug="%(Flex.Debug)"
AdditionalOptions="%(Flex.AdditionalOptions)"
Inputs="%(Flex.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeFlexOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeFlexOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeFlexOutput"
Condition="'@(Flex)' != ''">
<ItemGroup>
<FlexDirsToMake
Condition="'@(Flex)' != '' and '%(Flex.ExcludedFromBuild)' != 'true'"
Include="%(Flex.Outputs)" />
<Link
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(FlexDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(FlexDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
</Project>

243
tools/winflexbison/custom_build_rules/win_flex_only/win_flex_custom_build.xml

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:transformCallback="Microsoft.Cpp.Dev10.ConvertPropertyCallback">
<Rule
Name="Flex"
PageTemplate="tool"
DisplayName="Flex files"
SwitchPrefix="--"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Flex" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Flex Options">
<Category.DisplayName>
<sys:String>Flex Options</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="OutputFile"
Category="Flex Options"
IsRequired="true"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Output File Name"
Description="Directs flex to write the scanner to the file ‘FILE’ instead of ‘lex.yy.c’. --outfile=value"
Switch="outfile=&quot;[value]&quot;"
/>
<StringListProperty
Name="HeaderFile"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Header File Name"
Description="Instructs flex to write a C header to ‘FILE’. This file contains function prototypes, extern variables, and types used by the scanner. Only the external API is exported by the header file. (--header-file=value)"
Switch="header-file=&quot;[value]&quot;"
/>
<StringListProperty
Name="Prefix"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="Prefix"
Description="Changes the default ‘yy’ prefix used by flex for all globally-visible variable and function names to instead be ‘PREFIX’. For example, ‘--prefix=foo’ changes the name of yytext to footext. It also changes the name of the default output file from lex.yy.c to lex.foo.c. (--prefix=value)"
Switch="prefix=&quot;[value]&quot;"
/>
<BoolProperty
Name="Wincompat"
Category="Flex Options"
DisplayName="Windows compatibility mode"
Description="This option changes 'unistd.h' unix header with windows analog 'io.h' and replaces isatty/fileno functions to safe windows analogs _isatty/_fileno. (--wincompat)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="wincompat" />
<BoolProperty
Name="CaseInsensitive"
Category="Flex Options"
DisplayName="Case-insensitive mode"
Description="Instructs flex to generate a case-insensitive scanner. The case of letters given in the flex input patterns will be ignored, and tokens in the input will be matched regardless of case. The matched text given in yytext will have the preserved case (i.e., it will not be folded). (--case-insensitive)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="case-insensitive" />
<BoolProperty
Name="LexCompat"
Category="Flex Options"
DisplayName="Lex-compatibility mode"
Description="Turns on maximum compatibility with the original AT&amp;T lex implementation. Note that this does not mean full compatibility. Use of this option costs a considerable amount of performance, and it cannot be used with the ‘--c++’, ‘--full’, ‘--fast’, ‘-Cf’, or ‘-CF’ options. (--lex-compat)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="lex-compat" />
<BoolProperty
Name="Stack"
Category="Flex Options"
DisplayName="Start Condition Stacks"
Description="Enables the use of start condition stacks. (--stack)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="stack" />
<BoolProperty
Name="BisonBridge"
Category="Flex Options"
DisplayName="Bison Bridge Mode"
Description="Instructs flex to generate a C scanner that is meant to be called by a GNU bison parser. The scanner has minor API changes for bison compatibility. In particular, the declaration of yylex is modified to take an additional parameter, yylval. (--bison-bridge)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="bison-bridge" />
<BoolProperty
Name="Noline"
Category="Flex Options"
DisplayName="No #line Directives"
Description="Instructs flex not to generate #line directives. (--noline)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="noline" />
<BoolProperty
Name="Reentrant"
Category="Flex Options"
DisplayName="Generate Reentrant Scanner"
Description="Instructs flex to generate a reentrant C scanner. The generated scanner may safely be used in a multi-threaded environment. The API for a reentrant scanner is different than for a non-reentrant scanner. (--reentrant)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="reentrant" />
<BoolProperty
Name="Cpp"
Category="Flex Options"
DisplayName="Generate C++ Scanner"
Description="Specifies that you want flex to generate a C++ scanner class. (--c++)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="c++" />
<StringListProperty
Name="CppClassName"
Category="Flex Options"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
DisplayName="C++ Class Name"
Description="Only applies when generating a C++ scanner (the ‘--c++’ option). It informs flex that you have derived NAME as a subclass of yyFlexLexer, so flex will place your actions in the member function foo::yylex() instead of yyFlexLexer::yylex(). It also generates a yyFlexLexer::yylex() member function that emits a run-time error (by invoking yyFlexLexer::LexerError()) if called. (--yyclass=value)"
Switch="yyclass=&quot;[value]&quot;" />
<BoolProperty
Name="Debug"
Category="Flex Options"
DisplayName="Debug Mode"
Description="Makes the generated scanner run in debug mode. Whenever a pattern is recognized and the global variable yy_flex_debug is non-zero (which is the default), the scanner will write to ‘stderr’ a line of the form: -accepting rule at line 53 ('the matched text'). (--debug)"
HelpUrl="https://westes.github.io/flex/manual/Scanner-Options.html#Scanner-Options"
Switch="debug" />
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Flex"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="FlexBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^FlexBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="FlexAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^FlexAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Flex"
DisplayName="Flex files" />
<FileExtension
Name="*.l"
ContentType="Flex" />
<ContentType
Name="Flex"
DisplayName="Flex files"
ItemType="Flex" />
</ProjectSchemaDefinitions>

227
tools/winflexbison/data/README.md

@ -0,0 +1,227 @@
This directory contains data needed by Bison.
# Directory Content
## Skeletons
Bison skeletons: the general shapes of the different parser kinds, that are
specialized for specific grammars by the bison program.
Currently, the supported skeletons are:
- yacc.c
It used to be named bison.simple: it corresponds to C Yacc
compatible LALR(1) parsers.
- lalr1.cc
Produces a C++ parser class.
- lalr1.java
Produces a Java parser class.
- glr.c
A Generalized LR C parser based on Bison's LALR(1) tables.
- glr.cc
A Generalized LR C++ parser. Actually a C++ wrapper around glr.c.
These skeletons are the only ones supported by the Bison team. Because the
interface between skeletons and the bison program is not finished, *we are
not bound to it*. In particular, Bison is not mature enough for us to
consider that "foreign skeletons" are supported.
## m4sugar
This directory contains M4sugar, sort of an extended library for M4, which
is used by Bison to instantiate the skeletons.
## xslt
This directory contains XSLT programs that transform Bison's XML output into
various formats.
- bison.xsl
A library of routines used by the other XSLT programs.
- xml2dot.xsl
Conversion into GraphViz's dot format.
- xml2text.xsl
Conversion into text.
- xml2xhtml.xsl
Conversion into XHTML.
# Implementation Notes About the Skeletons
"Skeleton" in Bison parlance means "backend": a skeleton is fed by the bison
executable with LR tables, facts about the symbols, etc. and they generate
the output (say parser.cc, parser.hh, location.hh, etc.). They are only in
charge of generating the parser and its auxiliary files, they do not
generate the XML output, the parser.output reports, nor the graphical
rendering.
The bits of information passing from bison to the backend is named
"muscles". Muscles are passed to M4 via its standard input: it's a set of
m4 definitions. To see them, use `--trace=muscles`.
Except for muscles, whose names are generated by bison, the skeletons have
no constraint at all on the macro names: there is no technical/theoretical
limitation, as long as you generate the output, you can do what you want.
However, of course, that would be a bad idea if, say, the C and C++
skeletons used different approaches and had completely different
implementations. That would be a maintenance nightmare.
Below, we document some of the macros that we use in several of the
skeletons. If you are to write a new skeleton, please, implement them for
your language. Overall, be sure to follow the same patterns as the existing
skeletons.
## Vocabulary
We use "formal arguments", or "formals" for short, to denote the declared
parameters of a function (e.g., `int argc, const char **argv`). Yes, this
is somewhat contradictory with `param` in the `%param` directives.
We use "effective arguments", or "args" for short, to denote the values
passed in function calls (e.g., `argc, argv`).
## Symbols
### `b4_symbol(NUM, FIELD)`
In order to unify the handling of the various aspects of symbols (tag, type
name, whether terminal, etc.), bison.exe defines one macro per (token,
field), where field can `has_id`, `id`, etc.: see
`prepare_symbol_definitions()` in `src/output.c`.
NUM can be:
- `empty` to denote the "empty" pseudo-symbol when it exists,
- `eof`, `error`, or `undef`
- a symbol number.
FIELD can be:
- `has_id`: 0 or 1
Whether the symbol has an `id`.
- `id`: string (e.g., `exp`, `NUM`, or `TOK_NUM` with api.token.prefix)
If `has_id`, the name of the token kind (prefixed by api.token.prefix if
defined), otherwise empty. Guaranteed to be usable as a C identifier.
This is used to define the token kind (i.e., the enum used by the return
value of yylex). Should be named `token_kind`.
- `tag`: string
A human readable representation of the symbol. Can be `'foo'`,
`'foo.id'`, `'"foo"'` etc.
- `code`: integer
The token code associated to the token kind `id`.
The external number as used by yylex. Can be ASCII code when a character,
some number chosen by bison, or some user number in the case of `%token
FOO <NUM>`. Corresponds to `yychar` in `yacc.c`.
- `is_token`: 0 or 1
Whether this is a terminal symbol.
- `kind_base`: string (e.g., `YYSYMBOL_exp`, `YYSYMBOL_NUM`)
The base of the symbol kind, i.e., the enumerator of this symbol (token or
nonterminal) which is mapped to its `number`.
- `kind`: string
Same as `kind_base`, but possibly with a prefix in some languages. E.g.,
EOF's `kind_base` and `kind` are `YYSYMBOL_YYEOF` in C, but are
`S_YYEMPTY` and `symbol_kind::S_YYEMPTY` in C++.
- `number`: integer
The code associated to the `kind`.
The internal number (computed from the external number by yytranslate).
Corresponds to yytoken in yacc.c. This is the same number that serves as
key in b4_symbol(NUM, FIELD).
In bison, symbols are first assigned increasing numbers in order of
appearance (but tokens first, then nterms). After grammar reduction,
unused nterms are then renumbered to appear last (i.e., first tokens, then
used nterms and finally unused nterms). This final number NUM is the one
contained in this field, and it is the one used as key in `b4_symbol(NUM,
FIELD)`.
The code of the rule actions, however, is emitted before we know what
symbols are unused, so they use the original numbers. To avoid confusion,
they actually use "orig NUM" instead of just "NUM". bison also emits
definitions for `b4_symbol(orig NUM, number)` that map from original
numbers to the new ones. `b4_symbol` actually resolves `orig NUM` in the
other case, i.e., `b4_symbol(orig 42, tag)` would return the tag of the
symbols whose original number was 42.
- `has_type`: 0, 1
Whether has a semantic value.
- `type_tag`: string
When api.value.type=union, the generated name for the union member.
yytype_INT etc. for symbols that has_id, otherwise yytype_1 etc.
- `type`: string
If it has a semantic value, its type tag, or, if variant are used,
its type.
In the case of api.value.type=union, type is the real type (e.g. int).
- `slot`: string
If it has a semantic value, the name of the union member (i.e., bounces to
either `type_tag` or `type`). It would be better to fix our mess and
always use `type` for the true type of the member, and `type_tag` for the
name of the union member.
- `has_printer`: 0, 1
- `printer`: string
- `printer_file`: string
- `printer_line`: integer
- `printer_loc`: location
If the symbol has a printer, everything about it.
- `has_destructor`, `destructor`, `destructor_file`, `destructor_line`, `destructor_loc`
Likewise.
### `b4_symbol_value(VAL, [SYMBOL-NUM], [TYPE-TAG])`
Expansion of $$, $1, $<TYPE-TAG>3, etc.
The semantic value from a given VAL.
- `VAL`: some semantic value storage (typically a union). e.g., `yylval`
- `SYMBOL-NUM`: the symbol number from which we extract the type tag.
- `TYPE-TAG`, the user forced the `<TYPE-TAG>`.
The result can be used safely, it is put in parens to avoid nasty precedence
issues.
### `b4_lhs_value(SYMBOL-NUM, [TYPE])`
Expansion of `$$` or `$<TYPE>$`, for symbol `SYMBOL-NUM`.
### `b4_rhs_data(RULE-LENGTH, POS)`
The data corresponding to the symbol `#POS`, where the current rule has
`RULE-LENGTH` symbols on RHS.
### `b4_rhs_value(RULE-LENGTH, POS, SYMBOL-NUM, [TYPE])`
Expansion of `$<TYPE>POS`, where the current rule has `RULE-LENGTH` symbols
on RHS.
<!--
Local Variables:
mode: markdown
fill-column: 76
ispell-dictionary: "american"
End:
Copyright (C) 2002, 2008-2015, 2018-2021 Free Software Foundation, Inc.
This file is part of GNU Bison.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->

61
tools/winflexbison/data/bison-default.css

@ -0,0 +1,61 @@
/* Default styling rules for Bison when doing terminal output.
Copyright (C) 2019-2021 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
/* This is an experimental feature. The class names may change in the
future. */
/* Diagnostics. */
.warning { color: purple; }
.error { color: red; }
.note { color: cyan; }
.fixit-insert { color: green; }
/* Semantic values in Bison's own parser traces. */
.value { color: green; }
/* "Sections" in traces (--trace). */
.trace0 { color: green; }
/* Syntax error messages. */
.expected { color: green; }
.unexpected { color: red; }
/* Counterexamples. */
/* Cex: point in rule. */
.cex-dot { color: red; }
/* Cex: coloring various rules. */
.cex-0 { color: yellow; }
.cex-1 { color: green; }
.cex-2 { color: blue; }
.cex-3 { color: purple; }
.cex-4 { color: violet; }
.cex-5 { color: orange; }
.cex-6 { color: brown; }
.cex-7 { color: mauve; }
.cex-8 { color: #013220; } /* Dark green. */
.cex-9 { color: #e75480; } /* Dark pink. */
.cex-10 { color: cyan; }
.cex-11 { color: orange; }
/* Cex: derivation rewriting steps. */
.cex-step { font-style: italic; }
/* Cex: leaves of a derivation. */
.cex-leaf { font-weight: 600; }

58
tools/winflexbison/data/local.mk

@ -0,0 +1,58 @@
## Copyright (C) 2002, 2005-2015, 2018-2021 Free Software Foundation,
## Inc.
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program. If not, see <https://www.gnu.org/licenses/>.
dist_pkgdata_DATA = \
data/README.md \
data/bison-default.css
skeletonsdir = $(pkgdatadir)/skeletons
dist_skeletons_DATA = \
data/skeletons/bison.m4 \
data/skeletons/c++-skel.m4 \
data/skeletons/c++.m4 \
data/skeletons/c-like.m4 \
data/skeletons/c-skel.m4 \
data/skeletons/c.m4 \
data/skeletons/glr.c \
data/skeletons/glr.cc \
data/skeletons/glr2.cc \
data/skeletons/java-skel.m4 \
data/skeletons/java.m4 \
data/skeletons/lalr1.cc \
data/skeletons/lalr1.java \
data/skeletons/location.cc \
data/skeletons/stack.hh \
data/skeletons/traceon.m4 \
data/skeletons/variant.hh \
data/skeletons/yacc.c
# Experimental support for the D language.
dist_skeletons_DATA += \
data/skeletons/d-skel.m4 \
data/skeletons/d.m4 \
data/skeletons/lalr1.d
m4sugardir = $(pkgdatadir)/m4sugar
dist_m4sugar_DATA = \
data/m4sugar/foreach.m4 \
data/m4sugar/m4sugar.m4
xsltdir = $(pkgdatadir)/xslt
dist_xslt_DATA = \
data/xslt/bison.xsl \
data/xslt/xml2dot.xsl \
data/xslt/xml2text.xsl \
data/xslt/xml2xhtml.xsl

362
tools/winflexbison/data/m4sugar/foreach.m4

@ -0,0 +1,362 @@
# -*- Autoconf -*-
# This file is part of Autoconf.
# foreach-based replacements for recursive functions.
# Speeds up GNU M4 1.4.x by avoiding quadratic $@ recursion, but penalizes
# GNU M4 1.6 by requiring more memory and macro expansions.
#
# Copyright (C) 2008-2017, 2020 Free Software Foundation, Inc.
# This file is part of Autoconf. This program is free
# software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Under Section 7 of GPL version 3, you are granted additional
# permissions described in the Autoconf Configure Script Exception,
# version 3.0, as published by the Free Software Foundation.
#
# You should have received a copy of the GNU General Public License
# and a copy of the Autoconf Configure Script Exception along with
# this program; see the files COPYINGv3 and COPYING.EXCEPTION
# respectively. If not, see <https://www.gnu.org/licenses/>.
# Written by Eric Blake.
# In M4 1.4.x, every byte of $@ is rescanned. This means that an
# algorithm on n arguments that recurses with one less argument each
# iteration will scan n * (n + 1) / 2 arguments, for O(n^2) time. In
# M4 1.6, this was fixed so that $@ is only scanned once, then
# back-references are made to information stored about the scan.
# Thus, n iterations need only scan n arguments, for O(n) time.
# Additionally, in M4 1.4.x, recursive algorithms did not clean up
# memory very well, requiring O(n^2) memory rather than O(n) for n
# iterations.
#
# This file is designed to overcome the quadratic nature of $@
# recursion by writing a variant of m4_foreach that uses m4_for rather
# than $@ recursion to operate on the list. This involves more macro
# expansions, but avoids the need to rescan a quadratic number of
# arguments, making these replacements very attractive for M4 1.4.x.
# On the other hand, in any version of M4, expanding additional macros
# costs additional time; therefore, in M4 1.6, where $@ recursion uses
# fewer macros, these replacements actually pessimize performance.
# Additionally, the use of $10 to mean the tenth argument violates
# POSIX; although all versions of m4 1.4.x support this meaning, a
# future m4 version may switch to take it as the first argument
# concatenated with a literal 0, so the implementations in this file
# are not future-proof. Thus, this file is conditionally included as
# part of m4_init(), only when it is detected that M4 probably has
# quadratic behavior (ie. it lacks the macro __m4_version__).
#
# Please keep this file in sync with m4sugar.m4.
# _m4_foreach(PRE, POST, IGNORED, ARG...)
# ---------------------------------------
# Form the common basis of the m4_foreach and m4_map macros. For each
# ARG, expand PRE[ARG]POST[]. The IGNORED argument makes recursion
# easier, and must be supplied rather than implicit.
#
# This version minimizes the number of times that $@ is evaluated by
# using m4_for to generate a boilerplate into _m4_f then passing $@ to
# that temporary macro. Thus, the recursion is done in m4_for without
# reparsing any user input, and is not quadratic. For an idea of how
# this works, note that m4_foreach(i,[1,2],[i]) calls
# _m4_foreach([m4_define([i],],[)i],[],[1],[2])
# which defines _m4_f:
# $1[$4]$2[]$1[$5]$2[]_m4_popdef([_m4_f])
# then calls _m4_f([m4_define([i],],[)i],[],[1],[2]) for a net result:
# m4_define([i],[1])i[]m4_define([i],[2])i[]_m4_popdef([_m4_f]).
m4_define([_m4_foreach],
[m4_if([$#], [3], [],
[m4_pushdef([_m4_f], _m4_for([4], [$#], [1],
[$0_([1], [2],], [)])[_m4_popdef([_m4_f])])_m4_f($@)])])
m4_define([_m4_foreach_],
[[$$1[$$3]$$2[]]])
# m4_case(SWITCH, VAL1, IF-VAL1, VAL2, IF-VAL2, ..., DEFAULT)
# -----------------------------------------------------------
# Find the first VAL that SWITCH matches, and expand the corresponding
# IF-VAL. If there are no matches, expand DEFAULT.
#
# Use m4_for to create a temporary macro in terms of a boilerplate
# m4_if with final cleanup. If $# is even, we have DEFAULT; if it is
# odd, then rounding the last $# up in the temporary macro is
# harmless. For example, both m4_case(1,2,3,4,5) and
# m4_case(1,2,3,4,5,6) result in the intermediate _m4_case being
# m4_if([$1],[$2],[$3],[$1],[$4],[$5],_m4_popdef([_m4_case])[$6])
m4_define([m4_case],
[m4_if(m4_eval([$# <= 2]), [1], [$2],
[m4_pushdef([_$0], [m4_if(]_m4_for([2], m4_eval([($# - 1) / 2 * 2]), [2],
[_$0_(], [)])[_m4_popdef(
[_$0])]m4_dquote($m4_eval([($# + 1) & ~1]))[)])_$0($@)])])
m4_define([_m4_case_],
[$0_([1], [$1], m4_incr([$1]))])
m4_define([_m4_case__],
[[[$$1],[$$2],[$$3],]])
# m4_bmatch(SWITCH, RE1, VAL1, RE2, VAL2, ..., DEFAULT)
# -----------------------------------------------------
# m4 equivalent of
#
# if (SWITCH =~ RE1)
# VAL1;
# elif (SWITCH =~ RE2)
# VAL2;
# elif ...
# ...
# else
# DEFAULT
#
# We build the temporary macro _m4_b:
# m4_define([_m4_b], _m4_defn([_m4_bmatch]))_m4_b([$1], [$2], [$3])...
# _m4_b([$1], [$m-1], [$m])_m4_b([], [], [$m+1]_m4_popdef([_m4_b]))
# then invoke m4_unquote(_m4_b($@)), for concatenation with later text.
m4_define([m4_bmatch],
[m4_if([$#], 0, [m4_fatal([$0: too few arguments: $#])],
[$#], 1, [m4_fatal([$0: too few arguments: $#: $1])],
[$#], 2, [$2],
[m4_pushdef([_m4_b], [m4_define([_m4_b],
_m4_defn([_$0]))]_m4_for([3], m4_eval([($# + 1) / 2 * 2 - 1]),
[2], [_$0_(], [)])[_m4_b([], [],]m4_dquote([$]m4_eval(
[($# + 1) / 2 * 2]))[_m4_popdef([_m4_b]))])m4_unquote(_m4_b($@))])])
m4_define([_m4_bmatch],
[m4_if(m4_bregexp([$1], [$2]), [-1], [], [[$3]m4_define([$0])])])
m4_define([_m4_bmatch_],
[$0_([1], m4_decr([$1]), [$1])])
m4_define([_m4_bmatch__],
[[_m4_b([$$1], [$$2], [$$3])]])
# m4_cond(TEST1, VAL1, IF-VAL1, TEST2, VAL2, IF-VAL2, ..., [DEFAULT])
# -------------------------------------------------------------------
# Similar to m4_if, except that each TEST is expanded when encountered.
# If the expansion of TESTn matches the string VALn, the result is IF-VALn.
# The result is DEFAULT if no tests passed. This macro allows
# short-circuiting of expensive tests, where it pays to arrange quick
# filter tests to run first.
#
# m4_cond already guarantees either 3*n or 3*n + 1 arguments, 1 <= n.
# We only have to speed up _m4_cond, by building the temporary _m4_c:
# m4_define([_m4_c], _m4_defn([m4_unquote]))_m4_c([m4_if(($1), [($2)],
# [[$3]m4_define([_m4_c])])])_m4_c([m4_if(($4), [($5)],
# [[$6]m4_define([_m4_c])])])..._m4_c([m4_if(($m-2), [($m-1)],
# [[$m]m4_define([_m4_c])])])_m4_c([[$m+1]]_m4_popdef([_m4_c]))
# We invoke m4_unquote(_m4_c($@)), for concatenation with later text.
m4_define([_m4_cond],
[m4_pushdef([_m4_c], [m4_define([_m4_c],
_m4_defn([m4_unquote]))]_m4_for([2], m4_eval([$# / 3 * 3 - 1]), [3],
[$0_(], [)])[_m4_c(]m4_dquote(m4_dquote(
[$]m4_eval([$# / 3 * 3 + 1])))[_m4_popdef([_m4_c]))])m4_unquote(_m4_c($@))])
m4_define([_m4_cond_],
[$0_(m4_decr([$1]), [$1], m4_incr([$1]))])
m4_define([_m4_cond__],
[[_m4_c([m4_if(($$1), [($$2)], [[$$3]m4_define([_m4_c])])])]])
# m4_bpatsubsts(STRING, RE1, SUBST1, RE2, SUBST2, ...)
# ----------------------------------------------------
# m4 equivalent of
#
# $_ = STRING;
# s/RE1/SUBST1/g;
# s/RE2/SUBST2/g;
# ...
#
# m4_bpatsubsts already validated an odd number of arguments; we only
# need to speed up _m4_bpatsubsts. To avoid nesting, we build the
# temporary _m4_p:
# m4_define([_m4_p], [$1])m4_define([_m4_p],
# m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$2], [$3]))m4_define([_m4_p],
# m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$4], [$5]))m4_define([_m4_p],...
# m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$m-1], [$m]))m4_unquote(
# _m4_defn([_m4_p])_m4_popdef([_m4_p]))
m4_define([_m4_bpatsubsts],
[m4_pushdef([_m4_p], [m4_define([_m4_p],
]m4_dquote([$]1)[)]_m4_for([3], [$#], [2], [$0_(],
[)])[m4_unquote(_m4_defn([_m4_p])_m4_popdef([_m4_p]))])_m4_p($@)])
m4_define([_m4_bpatsubsts_],
[$0_(m4_decr([$1]), [$1])])
m4_define([_m4_bpatsubsts__],
[[m4_define([_m4_p],
m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$$1], [$$2]))]])
# m4_shiftn(N, ...)
# -----------------
# Returns ... shifted N times. Useful for recursive "varargs" constructs.
#
# m4_shiftn already validated arguments; we only need to speed up
# _m4_shiftn. If N is 3, then we build the temporary _m4_s, defined as
# ,[$5],[$6],...,[$m]_m4_popdef([_m4_s])
# before calling m4_shift(_m4_s($@)).
m4_define([_m4_shiftn],
[m4_if(m4_incr([$1]), [$#], [], [m4_pushdef([_m4_s],
_m4_for(m4_eval([$1 + 2]), [$#], [1],
[[,]m4_dquote($], [)])[_m4_popdef([_m4_s])])m4_shift(_m4_s($@))])])
# m4_do(STRING, ...)
# ------------------
# This macro invokes all its arguments (in sequence, of course). It is
# useful for making your macros more structured and readable by dropping
# unnecessary dnl's and have the macros indented properly.
#
# Here, we use the temporary macro _m4_do, defined as
# $1[]$2[]...[]$n[]_m4_popdef([_m4_do])
m4_define([m4_do],
[m4_if([$#], [0], [],
[m4_pushdef([_$0], _m4_for([1], [$#], [1],
[$], [[[]]])[_m4_popdef([_$0])])_$0($@)])])
# m4_dquote_elt(ARGS)
# -------------------
# Return ARGS as an unquoted list of double-quoted arguments.
#
# _m4_foreach to the rescue.
m4_define([m4_dquote_elt],
[m4_if([$#], [0], [], [[[$1]]_m4_foreach([,m4_dquote(], [)], $@)])])
# m4_reverse(ARGS)
# ----------------
# Output ARGS in reverse order.
#
# Invoke _m4_r($@) with the temporary _m4_r built as
# [$m], [$m-1], ..., [$2], [$1]_m4_popdef([_m4_r])
m4_define([m4_reverse],
[m4_if([$#], [0], [], [$#], [1], [[$1]],
[m4_pushdef([_m4_r], [[$$#]]_m4_for(m4_decr([$#]), [1], [-1],
[[, ]m4_dquote($], [)])[_m4_popdef([_m4_r])])_m4_r($@)])])
# m4_map_args_pair(EXPRESSION, [END-EXPR = EXPRESSION], ARG...)
# -------------------------------------------------------------
# Perform a pairwise grouping of consecutive ARGs, by expanding
# EXPRESSION([ARG1], [ARG2]). If there are an odd number of ARGs, the
# final argument is expanded with END-EXPR([ARGn]).
#
# Build the temporary macro _m4_map_args_pair, with the $2([$m+1])
# only output if $# is odd:
# $1([$3], [$4])[]$1([$5], [$6])[]...$1([$m-1],
# [$m])[]m4_default([$2], [$1])([$m+1])[]_m4_popdef([_m4_map_args_pair])
m4_define([m4_map_args_pair],
[m4_if([$#], [0], [m4_fatal([$0: too few arguments: $#])],
[$#], [1], [m4_fatal([$0: too few arguments: $#: $1])],
[$#], [2], [],
[$#], [3], [m4_default([$2], [$1])([$3])[]],
[m4_pushdef([_$0], _m4_for([3],
m4_eval([$# / 2 * 2 - 1]), [2], [_$0_(], [)])_$0_end(
[1], [2], [$#])[_m4_popdef([_$0])])_$0($@)])])
m4_define([_m4_map_args_pair_],
[$0_([1], [$1], m4_incr([$1]))])
m4_define([_m4_map_args_pair__],
[[$$1([$$2], [$$3])[]]])
m4_define([_m4_map_args_pair_end],
[m4_if(m4_eval([$3 & 1]), [1], [[m4_default([$$2], [$$1])([$$3])[]]])])
# m4_join(SEP, ARG1, ARG2...)
# ---------------------------
# Produce ARG1SEPARG2...SEPARGn. Avoid back-to-back SEP when a given ARG
# is the empty string. No expansion is performed on SEP or ARGs.
#
# Use a self-modifying separator, since we don't know how many
# arguments might be skipped before a separator is first printed, but
# be careful if the separator contains $. _m4_foreach to the rescue.
m4_define([m4_join],
[m4_pushdef([_m4_sep], [m4_define([_m4_sep], _m4_defn([m4_echo]))])]dnl
[_m4_foreach([_$0([$1],], [)], $@)_m4_popdef([_m4_sep])])
m4_define([_m4_join],
[m4_if([$2], [], [], [_m4_sep([$1])[$2]])])
# m4_joinall(SEP, ARG1, ARG2...)
# ------------------------------
# Produce ARG1SEPARG2...SEPARGn. An empty ARG results in back-to-back SEP.
# No expansion is performed on SEP or ARGs.
#
# A bit easier than m4_join. _m4_foreach to the rescue.
m4_define([m4_joinall],
[[$2]m4_if(m4_eval([$# <= 2]), [1], [],
[_m4_foreach([$1], [], m4_shift($@))])])
# m4_list_cmp(A, B)
# -----------------
# Compare the two lists of integer expressions A and B.
#
# m4_list_cmp takes care of any side effects; we only override
# _m4_list_cmp_raw, where we can safely expand lists multiple times.
# First, insert padding so that both lists are the same length; the
# trailing +0 is necessary to handle a missing list. Next, create a
# temporary macro to perform pairwise comparisons until an inequality
# is found. For example, m4_list_cmp([1], [1,2]) creates _m4_cmp as
# m4_if(m4_eval([($1) != ($3)]), [1], [m4_cmp([$1], [$3])],
# m4_eval([($2) != ($4)]), [1], [m4_cmp([$2], [$4])],
# [0]_m4_popdef([_m4_cmp]))
# then calls _m4_cmp([1+0], [0*2], [1], [2+0])
m4_define([_m4_list_cmp_raw],
[m4_if([$1], [$2], 0,
[_m4_list_cmp($1+0_m4_list_pad(m4_count($1), m4_count($2)),
$2+0_m4_list_pad(m4_count($2), m4_count($1)))])])
m4_define([_m4_list_pad],
[m4_if(m4_eval($1 < $2), [1],
[_m4_for(m4_incr([$1]), [$2], [1], [,0*])])])
m4_define([_m4_list_cmp],
[m4_pushdef([_m4_cmp], [m4_if(]_m4_for(
[1], m4_eval([$# >> 1]), [1], [$0_(], [,]m4_eval([$# >> 1])[)])[
[0]_m4_popdef([_m4_cmp]))])_m4_cmp($@)])
m4_define([_m4_list_cmp_],
[$0_([$1], m4_eval([$1 + $2]))])
m4_define([_m4_list_cmp__],
[[m4_eval([($$1) != ($$2)]), [1], [m4_cmp([$$1], [$$2])],
]])
# m4_max(EXPR, ...)
# m4_min(EXPR, ...)
# -----------------
# Return the decimal value of the maximum (or minimum) in a series of
# integer expressions.
#
# _m4_foreach to the rescue; we only need to replace _m4_minmax. Here,
# we need a temporary macro to track the best answer so far, so that
# the foreach expression is tractable.
m4_define([_m4_minmax],
[m4_pushdef([_m4_best], m4_eval([$2]))_m4_foreach(
[m4_define([_m4_best], $1(_m4_best,], [))], m4_shift($@))]dnl
[_m4_best[]_m4_popdef([_m4_best])])
# m4_set_add_all(SET, VALUE...)
# -----------------------------
# Add each VALUE into SET. This is O(n) in the number of VALUEs, and
# can be faster than calling m4_set_add for each VALUE.
#
# _m4_foreach to the rescue. If no deletions have occurred, then
# avoid the speed penalty of m4_set_add.
m4_define([m4_set_add_all],
[m4_if([$#], [0], [], [$#], [1], [],
[m4_define([_m4_set_size($1)], m4_eval(m4_set_size([$1])
+ m4_len(_m4_foreach(m4_ifdef([_m4_set_cleanup($1)],
[[m4_set_add]], [[_$0]])[([$1],], [)], $@))))])])
m4_define([_m4_set_add_all],
[m4_ifdef([_m4_set([$1],$2)], [],
[m4_define([_m4_set([$1],$2)],
[1])m4_pushdef([_m4_set([$1])], [$2])-])])

3329
tools/winflexbison/data/m4sugar/m4sugar.m4
File diff suppressed because it is too large
View File

1241
tools/winflexbison/data/skeletons/bison.m4
File diff suppressed because it is too large
View File

27
tools/winflexbison/data/skeletons/c++-skel.m4

@ -0,0 +1,27 @@
-*- Autoconf -*-
# C++ skeleton dispatching for Bison.
# Copyright (C) 2006-2007, 2009-2015, 2018-2021 Free Software
# Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
b4_glr_if( [m4_define([b4_used_skeleton], [b4_skeletonsdir/[glr.cc]])])
b4_nondeterministic_if([m4_define([b4_used_skeleton], [b4_skeletonsdir/[glr.cc]])])
m4_define_default([b4_used_skeleton], [b4_skeletonsdir/[lalr1.cc]])
m4_define_default([b4_skeleton], ["b4_basename(b4_used_skeleton)"])
m4_include(b4_used_skeleton)

778
tools/winflexbison/data/skeletons/c++.m4

@ -0,0 +1,778 @@
-*- Autoconf -*-
# C++ skeleton for Bison
# Copyright (C) 2002-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Sanity checks, before defaults installed by c.m4.
b4_percent_define_ifdef([[api.value.union.name]],
[b4_complain_at(b4_percent_define_get_loc([[api.value.union.name]]),
[named %union is invalid in C++])])
b4_percent_define_default([[api.symbol.prefix]], [[S_]])
m4_include(b4_skeletonsdir/[c.m4])
b4_percent_define_check_kind([api.namespace], [code], [deprecated])
b4_percent_define_check_kind([api.parser.class], [code], [deprecated])
## ----- ##
## C++. ##
## ----- ##
# b4_comment(TEXT, [PREFIX])
# --------------------------
# Put TEXT in comment. Prefix all the output lines with PREFIX.
m4_define([b4_comment],
[_b4_comment([$1], [$2// ], [$2// ])])
# b4_inline(hh|cc)
# ----------------
# Expand to `inline\n ` if $1 is hh.
m4_define([b4_inline],
[m4_case([$1],
[cc], [],
[hh], [[inline
]],
[m4_fatal([$0: invalid argument: $1])])])
# b4_cxx_portability
# ------------------
m4_define([b4_cxx_portability],
[#if defined __cplusplus
# define YY_CPLUSPLUS __cplusplus
#else
# define YY_CPLUSPLUS 199711L
#endif
// Support move semantics when possible.
#if 201103L <= YY_CPLUSPLUS
# define YY_MOVE std::move
# define YY_MOVE_OR_COPY move
# define YY_MOVE_REF(Type) Type&&
# define YY_RVREF(Type) Type&&
# define YY_COPY(Type) Type
#else
# define YY_MOVE
# define YY_MOVE_OR_COPY copy
# define YY_MOVE_REF(Type) Type&
# define YY_RVREF(Type) const Type&
# define YY_COPY(Type) const Type&
#endif
// Support noexcept when possible.
#if 201103L <= YY_CPLUSPLUS
# define YY_NOEXCEPT noexcept
# define YY_NOTHROW
#else
# define YY_NOEXCEPT
# define YY_NOTHROW throw ()
#endif
// Support constexpr when possible.
#if 201703 <= YY_CPLUSPLUS
# define YY_CONSTEXPR constexpr
#else
# define YY_CONSTEXPR
#endif[]dnl
])
## ---------------- ##
## Default values. ##
## ---------------- ##
b4_percent_define_default([[api.parser.class]], [[parser]])
# Don't do that so that we remember whether we're using a user
# request, or the default value.
#
# b4_percent_define_default([[api.location.type]], [[location]])
b4_percent_define_default([[api.filename.type]], [[const std::string]])
# Make it a warning for those who used betas of Bison 3.0.
b4_percent_define_default([[api.namespace]], m4_defn([b4_prefix]))
b4_percent_define_default([[define_location_comparison]],
[m4_if(b4_percent_define_get([[filename_type]]),
[std::string], [[true]], [[false]])])
## ----------- ##
## Namespace. ##
## ----------- ##
m4_define([b4_namespace_ref], [b4_percent_define_get([[api.namespace]])])
# Don't permit an empty b4_namespace_ref. Any '::parser::foo' appended to it
# would compile as an absolute reference with 'parser' in the global namespace.
# b4_namespace_open would open an anonymous namespace and thus establish
# internal linkage. This would compile. However, it's cryptic, and internal
# linkage for the parser would be specified in all translation units that
# include the header, which is always generated. If we ever need to permit
# internal linkage somehow, surely we can find a cleaner approach.
m4_if(m4_bregexp(b4_namespace_ref, [^[ ]*$]), [-1], [],
[b4_complain_at(b4_percent_define_get_loc([[api.namespace]]),
[[namespace reference is empty]])])
# Instead of assuming the C++ compiler will do it, Bison should reject any
# invalid b4_namespace_ref that would be converted to a valid
# b4_namespace_open. The problem is that Bison doesn't always output
# b4_namespace_ref to uncommented code but should reserve the ability to do so
# in future releases without risking breaking any existing user grammars.
# Specifically, don't allow empty names as b4_namespace_open would just convert
# those into anonymous namespaces, and that might tempt some users.
m4_if(m4_bregexp(b4_namespace_ref, [::[ ]*::]), [-1], [],
[b4_complain_at(b4_percent_define_get_loc([[api.namespace]]),
[[namespace reference has consecutive "::"]])])
m4_if(m4_bregexp(b4_namespace_ref, [::[ ]*$]), [-1], [],
[b4_complain_at(b4_percent_define_get_loc([[api.namespace]]),
[[namespace reference has a trailing "::"]])])
m4_define([b4_namespace_open],
[b4_user_code([b4_percent_define_get_syncline([[api.namespace]])dnl
[namespace ]m4_bpatsubst(m4_dquote(m4_bpatsubst(m4_dquote(b4_namespace_ref),
[^\(.\)[ ]*::], [\1])),
[::], [ { namespace ])[ {]])])
m4_define([b4_namespace_close],
[b4_user_code([b4_percent_define_get_syncline([[api.namespace]])dnl
m4_bpatsubst(m4_dquote(m4_bpatsubst(m4_dquote(b4_namespace_ref[ ]),
[^\(.\)[ ]*\(::\)?\([^][:]\|:[^:]\)*],
[\1])),
[::\([^][:]\|:[^:]\)*], [} ])[} // ]b4_namespace_ref])])
## ------------- ##
## Token kinds. ##
## ------------- ##
# b4_token_enums
# --------------
# Output the definition of the token kinds.
m4_define([b4_token_enums],
[[enum token_kind_type
{
]b4_symbol([-2], [id])[ = -2,
]b4_symbol_foreach([b4_token_enum])dnl
[ };]dnl
])
## -------------- ##
## Symbol kinds. ##
## -------------- ##
# b4_declare_symbol_enum
# ----------------------
# The definition of the symbol internal numbers as an enum.
# Defining YYEMPTY here is important: it forces the compiler
# to use a signed type, which matters for yytoken.
m4_define([b4_declare_symbol_enum],
[[enum symbol_kind_type
{
YYNTOKENS = ]b4_tokens_number[, ///< Number of tokens.
]b4_symbol(empty, kind_base)[ = -2,
]b4_symbol_foreach([ b4_symbol_enum])dnl
[ };]])
## ----------------- ##
## Semantic Values. ##
## ----------------- ##
# b4_value_type_declare
# ---------------------
# Declare value_type.
m4_define([b4_value_type_declare],
[b4_value_type_setup[]dnl
[ /// Symbol semantic values.
]m4_bmatch(b4_percent_define_get_kind([[api.value.type]]),
[code],
[[ typedef ]b4_percent_define_get([[api.value.type]])[ value_type;]],
[m4_bmatch(b4_percent_define_get([[api.value.type]]),
[union\|union-directive],
[[ union value_type
{
]b4_user_union_members[
};]])])dnl
])
# b4_public_types_declare
# -----------------------
# Define the public types: token, semantic value, location, and so forth.
# Depending on %define token_lex, may be output in the header or source file.
m4_define([b4_public_types_declare],
[b4_glr2_cc_if(
[b4_value_type_declare],
[[#ifdef ]b4_api_PREFIX[STYPE
# ifdef __GNUC__
# pragma GCC message "bison: do not #define ]b4_api_PREFIX[STYPE in C++, use %define api.value.type"
# endif
typedef ]b4_api_PREFIX[STYPE value_type;
#else
]b4_value_type_declare[
#endif
/// Backward compatibility (Bison 3.8).
typedef value_type semantic_type;
]])[]b4_locations_if([
/// Symbol locations.
typedef b4_percent_define_get([[api.location.type]],
[[location]]) location_type;])[
/// Syntax errors thrown from user actions.
struct syntax_error : std::runtime_error
{
syntax_error (]b4_locations_if([const location_type& l, ])[const std::string& m)
: std::runtime_error (m)]b4_locations_if([
, location (l)])[
{}
syntax_error (const syntax_error& s)
: std::runtime_error (s.what ())]b4_locations_if([
, location (s.location)])[
{}
~syntax_error () YY_NOEXCEPT YY_NOTHROW;]b4_locations_if([
location_type location;])[
};
/// Token kinds.
struct token
{
]b4_token_enums[]b4_glr2_cc_if([], [[
/// Backward compatibility alias (Bison 3.6).
typedef token_kind_type yytokentype;]])[
};
/// Token kind, as returned by yylex.
typedef token::token_kind_type token_kind_type;]b4_glr2_cc_if([], [[
/// Backward compatibility alias (Bison 3.6).
typedef token_kind_type token_type;]])[
/// Symbol kinds.
struct symbol_kind
{
]b4_declare_symbol_enum[
};
/// (Internal) symbol kind.
typedef symbol_kind::symbol_kind_type symbol_kind_type;
/// The number of tokens.
static const symbol_kind_type YYNTOKENS = symbol_kind::YYNTOKENS;
]])
# b4_symbol_type_define
# ---------------------
# Define symbol_type, the external type for symbols used for symbol
# constructors.
m4_define([b4_symbol_type_define],
[[ /// A complete symbol.
///
/// Expects its Base type to provide access to the symbol kind
/// via kind ().
///
/// Provide access to semantic value]b4_locations_if([ and location])[.
template <typename Base>
struct basic_symbol : Base
{
/// Alias to Base.
typedef Base super_type;
/// Default constructor.
basic_symbol () YY_NOEXCEPT
: value ()]b4_locations_if([
, location ()])[
{}
#if 201103L <= YY_CPLUSPLUS
/// Move constructor.
basic_symbol (basic_symbol&& that)
: Base (std::move (that))
, value (]b4_variant_if([], [std::move (that.value)]))b4_locations_if([
, location (std::move (that.location))])[
{]b4_variant_if([
b4_symbol_variant([this->kind ()], [value], [move],
[std::move (that.value)])
])[}
#endif
/// Copy constructor.
basic_symbol (const basic_symbol& that);]b4_variant_if([[
/// Constructors for typed symbols.
]b4_type_foreach([b4_basic_symbol_constructor_define], [
])], [[
/// Constructor for valueless symbols.
basic_symbol (typename Base::kind_type t]b4_locations_if([,
YY_MOVE_REF (location_type) l])[);
/// Constructor for symbols with semantic value.
basic_symbol (typename Base::kind_type t,
YY_RVREF (value_type) v]b4_locations_if([,
YY_RVREF (location_type) l])[);
]])[
/// Destroy the symbol.
~basic_symbol ()
{
clear ();
}
]b4_glr2_cc_if([[
/// Copy assignment.
basic_symbol& operator= (const basic_symbol& that)
{
Base::operator= (that);]b4_variant_if([[
]b4_symbol_variant([this->kind ()], [value], [copy],
[that.value])], [[
value = that.value]])[;]b4_locations_if([[
location = that.location;]])[
return *this;
}
/// Move assignment.
basic_symbol& operator= (basic_symbol&& that)
{
Base::operator= (std::move (that));]b4_variant_if([[
]b4_symbol_variant([this->kind ()], [value], [move],
[std::move (that.value)])], [[
value = std::move (that.value)]])[;]b4_locations_if([[
location = std::move (that.location);]])[
return *this;
}
]])[
/// Destroy contents, and record that is empty.
void clear () YY_NOEXCEPT
{]b4_variant_if([[
// User destructor.
symbol_kind_type yykind = this->kind ();
basic_symbol<Base>& yysym = *this;
(void) yysym;
switch (yykind)
{
]b4_symbol_foreach([b4_symbol_destructor])dnl
[ default:
break;
}
// Value type destructor.
]b4_symbol_variant([[yykind]], [[value]], [[template destroy]])])[
Base::clear ();
}
]b4_parse_error_bmatch(
[custom\|detailed],
[[ /// The user-facing name of this symbol.
const char *name () const YY_NOEXCEPT
{
return ]b4_parser_class[::symbol_name (this->kind ());
}]],
[simple],
[[#if ]b4_api_PREFIX[DEBUG || ]b4_token_table_flag[
/// The user-facing name of this symbol.
const char *name () const YY_NOEXCEPT
{
return ]b4_parser_class[::symbol_name (this->kind ());
}
#endif // #if ]b4_api_PREFIX[DEBUG || ]b4_token_table_flag[
]],
[verbose],
[[ /// The user-facing name of this symbol.
std::string name () const YY_NOEXCEPT
{
return ]b4_parser_class[::symbol_name (this->kind ());
}]])[]b4_glr2_cc_if([], [[
/// Backward compatibility (Bison 3.6).
symbol_kind_type type_get () const YY_NOEXCEPT;]])[
/// Whether empty.
bool empty () const YY_NOEXCEPT;
/// Destructive move, \a s is emptied into this.
void move (basic_symbol& s);
/// The semantic value.
value_type value;]b4_locations_if([
/// The location.
location_type location;])[
private:
#if YY_CPLUSPLUS < 201103L
/// Assignment operator.
basic_symbol& operator= (const basic_symbol& that);
#endif
};
/// Type access provider for token (enum) based symbols.
struct by_kind
{
/// The symbol kind as needed by the constructor.
typedef token_kind_type kind_type;
/// Default constructor.
by_kind () YY_NOEXCEPT;
#if 201103L <= YY_CPLUSPLUS
/// Move constructor.
by_kind (by_kind&& that) YY_NOEXCEPT;
#endif
/// Copy constructor.
by_kind (const by_kind& that) YY_NOEXCEPT;
/// Constructor from (external) token numbers.
by_kind (kind_type t) YY_NOEXCEPT;
]b4_glr2_cc_if([[
/// Copy assignment.
by_kind& operator= (const by_kind& that);
/// Move assignment.
by_kind& operator= (by_kind&& that);
]])[
/// Record that this symbol is empty.
void clear () YY_NOEXCEPT;
/// Steal the symbol kind from \a that.
void move (by_kind& that);
/// The (internal) type number (corresponding to \a type).
/// \a empty when empty.
symbol_kind_type kind () const YY_NOEXCEPT;]b4_glr2_cc_if([], [[
/// Backward compatibility (Bison 3.6).
symbol_kind_type type_get () const YY_NOEXCEPT;]])[
/// The symbol kind.
/// \a ]b4_symbol_prefix[YYEMPTY when empty.
symbol_kind_type kind_;
};]b4_glr2_cc_if([], [[
/// Backward compatibility for a private implementation detail (Bison 3.6).
typedef by_kind by_type;]])[
/// "External" symbols: returned by the scanner.
struct symbol_type : basic_symbol<by_kind>
{]b4_variant_if([[
/// Superclass.
typedef basic_symbol<by_kind> super_type;
/// Empty symbol.
symbol_type () YY_NOEXCEPT {}
/// Constructor for valueless symbols, and symbols from each type.
]b4_type_foreach([_b4_symbol_constructor_define])dnl
])[};
]])
# b4_public_types_define(hh|cc)
# -----------------------------
# Provide the implementation needed by the public types.
m4_define([b4_public_types_define],
[[ // basic_symbol.
template <typename Base>
]b4_parser_class[::basic_symbol<Base>::basic_symbol (const basic_symbol& that)
: Base (that)
, value (]b4_variant_if([], [that.value]))b4_locations_if([
, location (that.location)])[
{]b4_variant_if([
b4_symbol_variant([this->kind ()], [value], [copy],
[YY_MOVE (that.value)])
])[}
]b4_variant_if([], [[
/// Constructor for valueless symbols.
template <typename Base>
]b4_parser_class[::basic_symbol<Base>::basic_symbol (]b4_join(
[typename Base::kind_type t],
b4_locations_if([YY_MOVE_REF (location_type) l]))[)
: Base (t)
, value ()]b4_locations_if([
, location (l)])[
{}
template <typename Base>
]b4_parser_class[::basic_symbol<Base>::basic_symbol (]b4_join(
[typename Base::kind_type t],
[YY_RVREF (value_type) v],
b4_locations_if([YY_RVREF (location_type) l]))[)
: Base (t)
, value (]b4_variant_if([], [YY_MOVE (v)])[)]b4_locations_if([
, location (YY_MOVE (l))])[
{]b4_variant_if([[
(void) v;
]b4_symbol_variant([this->kind ()], [value], [YY_MOVE_OR_COPY], [YY_MOVE (v)])])[}]])[
]b4_glr2_cc_if([], [[
template <typename Base>
]b4_parser_class[::symbol_kind_type
]b4_parser_class[::basic_symbol<Base>::type_get () const YY_NOEXCEPT
{
return this->kind ();
}
]])[
template <typename Base>
bool
]b4_parser_class[::basic_symbol<Base>::empty () const YY_NOEXCEPT
{
return this->kind () == ]b4_symbol(empty, kind)[;
}
template <typename Base>
void
]b4_parser_class[::basic_symbol<Base>::move (basic_symbol& s)
{
super_type::move (s);
]b4_variant_if([b4_symbol_variant([this->kind ()], [value], [move],
[YY_MOVE (s.value)])],
[value = YY_MOVE (s.value);])[]b4_locations_if([
location = YY_MOVE (s.location);])[
}
// by_kind.
]b4_inline([$1])b4_parser_class[::by_kind::by_kind () YY_NOEXCEPT
: kind_ (]b4_symbol(empty, kind)[)
{}
#if 201103L <= YY_CPLUSPLUS
]b4_inline([$1])b4_parser_class[::by_kind::by_kind (by_kind&& that) YY_NOEXCEPT
: kind_ (that.kind_)
{
that.clear ();
}
#endif
]b4_inline([$1])b4_parser_class[::by_kind::by_kind (const by_kind& that) YY_NOEXCEPT
: kind_ (that.kind_)
{}
]b4_inline([$1])b4_parser_class[::by_kind::by_kind (token_kind_type t) YY_NOEXCEPT
: kind_ (yytranslate_ (t))
{}
]b4_glr2_cc_if([[
]b4_inline([$1])]b4_parser_class[::by_kind&
b4_parser_class[::by_kind::by_kind::operator= (const by_kind& that)
{
kind_ = that.kind_;
return *this;
}
]b4_inline([$1])]b4_parser_class[::by_kind&
b4_parser_class[::by_kind::by_kind::operator= (by_kind&& that)
{
kind_ = that.kind_;
that.clear ();
return *this;
}
]])[
]b4_inline([$1])[void
]b4_parser_class[::by_kind::clear () YY_NOEXCEPT
{
kind_ = ]b4_symbol(empty, kind)[;
}
]b4_inline([$1])[void
]b4_parser_class[::by_kind::move (by_kind& that)
{
kind_ = that.kind_;
that.clear ();
}
]b4_inline([$1])[]b4_parser_class[::symbol_kind_type
]b4_parser_class[::by_kind::kind () const YY_NOEXCEPT
{
return kind_;
}
]b4_glr2_cc_if([], [[
]b4_inline([$1])[]b4_parser_class[::symbol_kind_type
]b4_parser_class[::by_kind::type_get () const YY_NOEXCEPT
{
return this->kind ();
}
]])[
]])
# b4_token_constructor_define
# ----------------------------
# Define make_FOO for all the token kinds.
# Use at class-level. Redefined in variant.hh.
m4_define([b4_token_constructor_define], [])
# b4_yytranslate_define(cc|hh)
# ----------------------------
# Define yytranslate_. Sometimes used in the header file ($1=hh),
# sometimes in the cc file.
m4_define([b4_yytranslate_define],
[ b4_inline([$1])b4_parser_class[::symbol_kind_type
]b4_parser_class[::yytranslate_ (int t) YY_NOEXCEPT
{
]b4_api_token_raw_if(
[[ return static_cast<symbol_kind_type> (t);]],
[[ // YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to
// TOKEN-NUM as returned by yylex.
static
const ]b4_int_type_for([b4_translate])[
translate_table[] =
{
]b4_translate[
};
// Last valid token kind.
const int code_max = ]b4_code_max[;
if (t <= 0)
return symbol_kind::]b4_symbol_prefix[YYEOF;
else if (t <= code_max)
return static_cast <symbol_kind_type> (translate_table[t]);
else
return symbol_kind::]b4_symbol_prefix[YYUNDEF;]])[
}
]])
# b4_lhs_value([TYPE])
# --------------------
m4_define([b4_lhs_value],
[b4_symbol_value([yyval], [$1])])
# b4_rhs_value(RULE-LENGTH, POS, [TYPE])
# --------------------------------------
# FIXME: Dead code.
m4_define([b4_rhs_value],
[b4_symbol_value([yysemantic_stack_@{($1) - ($2)@}], [$3])])
# b4_lhs_location()
# -----------------
# Expansion of @$.
m4_define([b4_lhs_location],
[(yyloc)])
# b4_rhs_location(RULE-LENGTH, POS)
# ---------------------------------
# Expansion of @POS, where the current rule has RULE-LENGTH symbols
# on RHS.
m4_define([b4_rhs_location],
[(yylocation_stack_@{($1) - ($2)@})])
# b4_parse_param_decl
# -------------------
# Extra formal arguments of the constructor.
# Change the parameter names from "foo" into "foo_yyarg", so that
# there is no collision bw the user chosen attribute name, and the
# argument name in the constructor.
m4_define([b4_parse_param_decl],
[m4_ifset([b4_parse_param],
[m4_map_sep([b4_parse_param_decl_1], [, ], [b4_parse_param])])])
m4_define([b4_parse_param_decl_1],
[$1_yyarg])
# b4_parse_param_cons
# -------------------
# Extra initialisations of the constructor.
m4_define([b4_parse_param_cons],
[m4_ifset([b4_parse_param],
[
b4_cc_constructor_calls(b4_parse_param)])])
m4_define([b4_cc_constructor_calls],
[m4_map_sep([b4_cc_constructor_call], [,
], [$@])])
m4_define([b4_cc_constructor_call],
[$2 ($2_yyarg)])
# b4_parse_param_vars
# -------------------
# Extra instance variables.
m4_define([b4_parse_param_vars],
[m4_ifset([b4_parse_param],
[
// User arguments.
b4_cc_var_decls(b4_parse_param)])])
m4_define([b4_cc_var_decls],
[m4_map_sep([b4_cc_var_decl], [
], [$@])])
m4_define([b4_cc_var_decl],
[ $1;])
## ---------##
## Values. ##
## ---------##
# b4_yylloc_default_define
# ------------------------
# Define YYLLOC_DEFAULT.
m4_define([b4_yylloc_default_define],
[[/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
If N is 0, then set CURRENT to the empty location which ends
the previous symbol: RHS[0] (always defined). */
# ifndef YYLLOC_DEFAULT
# define YYLLOC_DEFAULT(Current, Rhs, N) \
do \
if (N) \
{ \
(Current).begin = YYRHSLOC (Rhs, 1).begin; \
(Current).end = YYRHSLOC (Rhs, N).end; \
} \
else \
{ \
(Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \
} \
while (false)
# endif
]])
## -------- ##
## Checks. ##
## -------- ##
b4_token_ctor_if([b4_variant_if([],
[b4_fatal_at(b4_percent_define_get_loc(api.token.constructor),
[cannot use '%s' without '%s'],
[%define api.token.constructor],
[%define api.value.type variant]))])])

72
tools/winflexbison/data/skeletons/c-like.m4

@ -0,0 +1,72 @@
-*- Autoconf -*-
# Common code for C-like languages (C, C++, Java, etc.)
# Copyright (C) 2012-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# _b4_comment(TEXT, OPEN, CONTINUE, END)
# --------------------------------------
# Put TEXT in comment. Avoid trailing spaces: don't indent empty lines.
# Avoid adding indentation to the first line, as the indentation comes
# from OPEN. That's why we don't patsubst([$1], [^\(.\)], [ \1]).
# Turn "*/" in TEXT into "* /" so that we don't unexpectedly close
# the comments before its end.
#
# Prefix all the output lines with PREFIX.
m4_define([_b4_comment],
[$2[]b4_gsub(m4_expand([$1]),
[[*]/], [*\\/],
[/[*]], [/\\*],
[
\(.\)], [
$3\1])$4])
# b4_comment(TEXT, [PREFIX])
# --------------------------
# Put TEXT in comment. Prefix all the output lines with PREFIX.
m4_define([b4_comment],
[_b4_comment([$1], [$2/* ], [$2 ], [ */])])
# _b4_dollar_dollar(VALUE, SYMBOL-NUM, FIELD, DEFAULT-FIELD)
# ----------------------------------------------------------
# If FIELD (or DEFAULT-FIELD) is non-null, return "VALUE.FIELD",
# otherwise just VALUE. Be sure to pass "(VALUE)" if VALUE is a
# pointer.
m4_define([_b4_dollar_dollar],
[b4_symbol_value([$1],
[$2],
m4_if([$3], [[]],
[[$4]], [[$3]]))])
# b4_dollar_pushdef(VALUE-POINTER, SYMBOL-NUM, [TYPE_TAG], LOCATION)
# b4_dollar_popdef
# ------------------------------------------------------------------
# Define b4_dollar_dollar for VALUE-POINTER and DEFAULT-FIELD,
# and b4_at_dollar for LOCATION.
m4_define([b4_dollar_pushdef],
[m4_pushdef([b4_dollar_dollar],
[_b4_dollar_dollar([$1], [$2], m4_dquote($][1), [$3])])dnl
m4_pushdef([b4_at_dollar], [$4])dnl
])
m4_define([b4_dollar_popdef],
[m4_popdef([b4_at_dollar])dnl
m4_popdef([b4_dollar_dollar])dnl
])

27
tools/winflexbison/data/skeletons/c-skel.m4

@ -0,0 +1,27 @@
-*- Autoconf -*-
# C skeleton dispatching for Bison.
# Copyright (C) 2006-2007, 2009-2015, 2018-2021 Free Software
# Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
b4_glr_if( [m4_define([b4_used_skeleton], [b4_skeletonsdir/[glr.c]])])
b4_nondeterministic_if([m4_define([b4_used_skeleton], [b4_skeletonsdir/[glr.c]])])
m4_define_default([b4_used_skeleton], [b4_skeletonsdir/[yacc.c]])
m4_define_default([b4_skeleton], ["b4_basename(b4_used_skeleton)"])
m4_include(b4_used_skeleton)

1125
tools/winflexbison/data/skeletons/c.m4
File diff suppressed because it is too large
View File

26
tools/winflexbison/data/skeletons/d-skel.m4

@ -0,0 +1,26 @@
-*- Autoconf -*-
# D skeleton dispatching for Bison.
# Copyright (C) 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
b4_glr_if( [b4_complain([%%glr-parser not supported for D])])
b4_nondeterministic_if([b4_complain([%%nondeterministic-parser not supported for D])])
m4_define_default([b4_used_skeleton], [b4_skeletonsdir/[lalr1.d]])
m4_define_default([b4_skeleton], ["b4_basename(b4_used_skeleton)"])
m4_include(b4_used_skeleton)

628
tools/winflexbison/data/skeletons/d.m4

@ -0,0 +1,628 @@
-*- Autoconf -*-
# D language support for Bison
# Copyright (C) 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
m4_include(b4_skeletonsdir/[c-like.m4])
# b4_symbol_action(SYMBOL-NUM, ACTION)
# ------------------------------------
# Run the action ACTION ("destructor" or "printer") for SYMBOL-NUM.
m4_define([b4_symbol_action],
[b4_symbol_if([$1], [has_$2],
[b4_dollar_pushdef([yyval],
[$1],
[],
[yyloc])dnl
_b4_symbol_case([$1])[]dnl
b4_syncline([b4_symbol([$1], [$2_line])], [b4_symbol([$1], [$2_file])])dnl
b4_symbol([$1], [$2])
b4_syncline([@oline@], [@ofile@])dnl
break;
b4_dollar_popdef[]dnl
])])
# b4_use(EXPR)
# ------------
# Pacify the compiler about some maybe unused value.
m4_define([b4_use],
[])
# b4_sync_start(LINE, FILE)
# -------------------------
m4_define([b4_sync_start], [[#]line $1 $2])
# b4_list2(LIST1, LIST2)
# ----------------------
# Join two lists with a comma if necessary.
m4_define([b4_list2],
[$1[]m4_ifval(m4_quote($1), [m4_ifval(m4_quote($2), [[, ]])])[]$2])
# b4_percent_define_get3(DEF, PRE, POST, NOT)
# -------------------------------------------
# Expand to the value of DEF surrounded by PRE and POST if it's %define'ed,
# otherwise NOT.
m4_define([b4_percent_define_get3],
[m4_ifval(m4_quote(b4_percent_define_get([$1])),
[$2[]b4_percent_define_get([$1])[]$3], [$4])])
# b4_percent_define_if_get2(ARG1, ARG2, DEF, NOT)
# -----------------------------------------------
# Expand to the value of DEF if ARG1 or ARG2 are %define'ed,
# otherwise NOT.
m4_define([b4_percent_define_if_get2],
[m4_ifval(m4_quote(b4_percent_define_get([$1])),
[$3], [m4_ifval(m4_quote(b4_percent_define_get([$2])),
[$3], [$4])])])
# b4_percent_define_class_before_interface(CLASS, INTERFACE)
# ----------------------------------------------------------
# Expand to a ', ' if both a class and an interface have been %define'ed
m4_define([b4_percent_define_class_before_interface],
[m4_ifval(m4_quote(b4_percent_define_get([$1])),
[m4_ifval(m4_quote(b4_percent_define_get([$2])),
[, ])])])
# b4_flag_value(BOOLEAN-FLAG)
# ---------------------------
m4_define([b4_flag_value], [b4_flag_if([$1], [true], [false])])
# b4_parser_class_declaration
# ---------------------------
# The declaration of the parser class ("class YYParser"), with all its
# qualifiers/annotations.
b4_percent_define_default([[api.parser.abstract]], [[false]])
b4_percent_define_default([[api.parser.final]], [[false]])
b4_percent_define_default([[api.parser.public]], [[false]])
m4_define([b4_parser_class_declaration],
[b4_percent_define_get3([api.parser.annotations], [], [ ])dnl
b4_percent_define_flag_if([api.parser.public], [public ])dnl
b4_percent_define_flag_if([api.parser.abstract], [abstract ])dnl
b4_percent_define_flag_if([api.parser.final], [final ])dnl
[class ]b4_parser_class[]dnl
b4_percent_define_if_get2([api.parser.extends], [api.parser.implements], [ : ])dnl
b4_percent_define_get([api.parser.extends])dnl
b4_percent_define_class_before_interface([api.parser.extends], [api.parser.implements])dnl
b4_percent_define_get([api.parser.implements])])
# b4_lexer_if(TRUE, FALSE)
# ------------------------
m4_define([b4_lexer_if],
[b4_percent_code_ifdef([[lexer]], [$1], [$2])])
# b4_position_type_if(TRUE, FALSE)
# --------------------------------
m4_define([b4_position_type_if],
[b4_percent_define_ifdef([[position_type]], [$1], [$2])])
# b4_location_type_if(TRUE, FALSE)
# --------------------------------
m4_define([b4_location_type_if],
[b4_percent_define_ifdef([[location_type]], [$1], [$2])])
# b4_identification
# -----------------
m4_define([b4_identification],
[[/** Version number for the Bison executable that generated this parser. */
public static immutable string yy_bison_version = "]b4_version_string[";
/** Name of the skeleton that generated this parser. */
public static immutable string yy_bison_skeleton = ]b4_skeleton[;
]])
## ------------ ##
## Data types. ##
## ------------ ##
# b4_int_type(MIN, MAX)
# ---------------------
# Return the smallest int type able to handle numbers ranging from
# MIN to MAX (included).
m4_define([b4_int_type],
[m4_if(b4_ints_in($@, [-128], [127]), [1], [byte],
b4_ints_in($@, [-32768], [32767]), [1], [short],
[int])])
# b4_int_type_for(NAME)
# ---------------------
# Return the smallest int type able to handle numbers ranging from
# `NAME_min' to `NAME_max' (included).
m4_define([b4_int_type_for],
[b4_int_type($1_min, $1_max)])
# b4_null
# -------
m4_define([b4_null], [null])
# b4_integral_parser_table_define(NAME, DATA, COMMENT)
#-----------------------------------------------------
# Define "yy<TABLE-NAME>" whose contents is CONTENT.
m4_define([b4_integral_parser_table_define],
[m4_ifvaln([$3], [b4_comment([$3], [ ])])dnl
private static immutable b4_int_type_for([$2])[[]] yy$1_ =
@{
$2
@};dnl
])
## ------------- ##
## Token kinds. ##
## ------------- ##
m4_define([b4_symbol(-2, id)], [[YYEMPTY]])
b4_percent_define_default([[api.token.raw]], [[true]])
# b4_token_enum(TOKEN-NAME, TOKEN-NUMBER)
# ---------------------------------------
# Output the definition of this token as an enum.
m4_define([b4_token_enum],
[b4_token_format([ %s = %s,
], [$1])])
# b4_token_enums
# --------------
# Output the definition of the tokens as enums.
m4_define([b4_token_enums],
[/* Token kinds. */
public enum TokenKind {
]b4_symbol(empty, id)[ = -2,
b4_symbol_foreach([b4_token_enum])dnl
}
])
# b4_symbol_translate(STRING)
# ---------------------------
# Used by "bison" in the array of symbol names to mark those that
# require translation.
m4_define([b4_symbol_translate],
[[_($1)]])
# _b4_token_constructor_define(SYMBOL-NUM)
# ----------------------------------------
# Define Symbol.FOO for SYMBOL-NUM.
m4_define([_b4_token_constructor_define],
[b4_token_visible_if([$1],
[[
static auto ]b4_symbol([$1], [id])[(]b4_symbol_if([$1], [has_type],
[b4_union_if([b4_symbol([$1], [type]],
[[typeof(YYSemanticType.]b4_symbol([$1], [type])[]])) [val]])dnl
[]b4_locations_if([b4_symbol_if([$1], [has_type], [[, ]])[Location l]])[)
{
return Symbol(TokenKind.]b4_symbol([$1], [id])[]b4_symbol_if([$1], [has_type],
[[, val]])[]b4_locations_if([[, l]])[);
}]])])
# b4_token_constructor_define
# ---------------------------
# Define Symbol.FOO for each token kind FOO.
m4_define([b4_token_constructor_define],
[[
/* Implementation of token constructors for each symbol type visible to
* the user. The code generates static methods that have the same names
* as the TokenKinds.
*/]b4_symbol_foreach([_b4_token_constructor_define])dnl
])
## -------------- ##
## Symbol kinds. ##
## -------------- ##
# b4_symbol_kind(NUM)
# -------------------
m4_define([b4_symbol_kind],
[SymbolKind.b4_symbol_kind_base($@)])
# b4_symbol_enum(SYMBOL-NUM)
# --------------------------
# Output the definition of this symbol as an enum.
m4_define([b4_symbol_enum],
[m4_format([ %-30s %s],
m4_format([[%s = %s,]],
b4_symbol([$1], [kind_base]),
[$1]),
[b4_symbol_tag_comment([$1])])])
# b4_declare_symbol_enum
# ----------------------
# The definition of the symbol internal numbers as an enum.
# Defining YYEMPTY here is important: it forces the compiler
# to use a signed type, which matters for yytoken.
m4_define([b4_declare_symbol_enum],
[[ /* Symbol kinds. */
struct SymbolKind
{
enum
{
]b4_symbol(empty, kind_base)[ = -2, /* No symbol. */
]b4_symbol_foreach([b4_symbol_enum])dnl
[ }
private int yycode_;
alias yycode_ this;
this(int code)
{
yycode_ = code;
}
/* Return YYSTR after stripping away unnecessary quotes and
backslashes, so that it's suitable for yyerror. The heuristic is
that double-quoting is unnecessary unless the string contains an
apostrophe, a comma, or backslash (other than backslash-backslash).
YYSTR is taken from yytname. */
final void toString(W)(W sink) const
if (isOutputRange!(W, char))
{
immutable string[] yy_sname = @{
]b4_symbol_names[
@};]b4_has_translations_if([[
/* YYTRANSLATABLE[SYMBOL-NUM] -- Whether YY_SNAME[SYMBOL-NUM] is
internationalizable. */
immutable ]b4_int_type_for([b4_translatable])[[] yytranslatable = @{
]b4_translatable[
@};]])[
put(sink, yy_sname[yycode_]);
}
}
]])
# b4_case(ID, CODE, [COMMENTS])
# -----------------------------
m4_define([b4_case], [ case $1:m4_ifval([$3], [ b4_comment([$3])])
$2
break;])
## ---------------- ##
## Default values. ##
## ---------------- ##
m4_define([b4_yystype], [b4_percent_define_get([[stype]])])
b4_percent_define_default([[stype]], [[YYSemanticType]])])
# %name-prefix
m4_define_default([b4_prefix], [[YY]])
b4_percent_define_default([[api.parser.class]], [b4_prefix[]Parser])])
m4_define([b4_parser_class], [b4_percent_define_get([[api.parser.class]])])
#b4_percent_define_default([[location_type]], [Location])])
m4_define([b4_location_type], b4_percent_define_ifdef([[location_type]],[b4_percent_define_get([[location_type]])],[YYLocation]))
#b4_percent_define_default([[position_type]], [Position])])
m4_define([b4_position_type], b4_percent_define_ifdef([[position_type]],[b4_percent_define_get([[position_type]])],[YYPosition]))
## ---------------- ##
## api.value.type. ##
## ---------------- ##
# ---------------------- #
# api.value.type=union. #
# ---------------------- #
# b4_symbol_type_register(SYMBOL-NUM)
# -----------------------------------
# Symbol SYMBOL-NUM has a type (for union) instead of a type-tag.
# Extend the definition of %union's body (b4_union_members) with a
# field of that type, and extend the symbol's "type" field to point to
# the field name, instead of the type name.
m4_define([b4_symbol_type_register],
[m4_define([b4_symbol($1, type_tag)],
[b4_symbol_if([$1], [has_id],
[b4_symbol([$1], [id])],
[yykind_[]b4_symbol([$1], [number])])])dnl
m4_append([b4_union_members],
m4_expand([m4_format([ %-40s %s],
m4_expand([b4_symbol([$1], [type]) b4_symbol([$1], [type_tag]);]),
[b4_symbol_tag_comment([$1])])]))
])
# b4_type_define_tag(SYMBOL1-NUM, ...)
# ------------------------------------
# For the batch of symbols SYMBOL1-NUM... (which all have the same
# type), enhance the %union definition for each of them, and set
# there "type" field to the field tag name, instead of the type name.
m4_define([b4_type_define_tag],
[b4_symbol_if([$1], [has_type],
[m4_map([b4_symbol_type_register], [$@])])
])
# b4_symbol_value_union(VAL, SYMBOL-NUM, [TYPE])
# ----------------------------------------------
# Same of b4_symbol_value, but when api.value.type=union.
m4_define([b4_symbol_value_union],
[m4_ifval([$3],
[(*($3*)(&$1))],
[m4_ifval([$2],
[b4_symbol_if([$2], [has_type],
[($1.b4_symbol([$2], [type_tag]))],
[$1])],
[$1])])])
# b4_value_type_setup_union
# -------------------------
# Setup support for api.value.type=union. Symbols are defined with a
# type instead of a union member name: build the corresponding union,
# and give the symbols their tag.
m4_define([b4_value_type_setup_union],
[m4_define([b4_union_members])
b4_type_foreach([b4_type_define_tag])
m4_copy_force([b4_symbol_value_union], [b4_symbol_value])
])
# _b4_value_type_setup_keyword
# ----------------------------
# api.value.type is defined with a keyword/string syntax. Check if
# that is properly defined, and prepare its use.
m4_define([_b4_value_type_setup_keyword],
[b4_percent_define_check_values([[[[api.value.type]],
[[none]],
[[union]],
[[union-directive]],
[[yystype]]]])dnl
m4_case(b4_percent_define_get([[api.value.type]]),
[union], [b4_value_type_setup_union])])
# b4_value_type_setup
# -------------------
# Check if api.value.type is properly defined, and possibly prepare
# its use.
b4_define_silent([b4_value_type_setup],
[
# Define default value.
b4_percent_define_ifdef([[api.value.type]], [],
[# %union => api.value.type=union-directive
m4_ifdef([b4_union_members],
[m4_define([b4_percent_define_kind(api.value.type)], [keyword])
m4_define([b4_percent_define(api.value.type)], [union-directive])],
[# no tag seen => api.value.type={int}
m4_if(b4_tag_seen_flag, 0,
[m4_define([b4_percent_define_kind(api.value.type)], [code])
m4_define([b4_percent_define(api.value.type)], [int])],
[# otherwise api.value.type=yystype
m4_define([b4_percent_define_kind(api.value.type)], [keyword])
m4_define([b4_percent_define(api.value.type)], [yystype])])])])
# Set up.
m4_bmatch(b4_percent_define_get_kind([[api.value.type]]),
[keyword], [_b4_value_type_setup_keyword])
])
## ----------------- ##
## Semantic Values. ##
## ----------------- ##
# b4_symbol_value(VAL, [SYMBOL-NUM], [TYPE-TAG])
# ----------------------------------------------
# See README. FIXME: factor in c-like?
m4_define([b4_symbol_value],
[m4_ifval([$3],
[($1.$3)],
[m4_ifval([$2],
[b4_symbol_if([$2], [has_type],
[($1.b4_symbol([$2], [type]))],
[$1])],
[$1])])])
# b4_lhs_value(SYMBOL-NUM, [TYPE])
# --------------------------------
# See README.
m4_define([b4_lhs_value],
[b4_symbol_value([yyval], [$1], [$2])])
# b4_rhs_value(RULE-LENGTH, POS, SYMBOL-NUM, [TYPE])
# --------------------------------------------------
# See README.
#
# In this simple implementation, %token and %type have class names
# between the angle brackets.
m4_define([b4_rhs_value],
[b4_symbol_value([(yystack.valueAt (b4_subtract([$1], [$2])))], [$3], [$4])])
# b4_lhs_location()
# -----------------
# Expansion of @$.
m4_define([b4_lhs_location],
[(yyloc)])
# b4_rhs_location(RULE-LENGTH, POS)
# ---------------------------------
# Expansion of @POS, where the current rule has RULE-LENGTH symbols
# on RHS.
m4_define([b4_rhs_location],
[yystack.locationAt (b4_subtract($@))])
# b4_lex_param
# b4_parse_param
# --------------
# If defined, b4_lex_param arrives double quoted, but below we prefer
# it to be single quoted. Same for b4_parse_param.
# TODO: should be in bison.m4
m4_define_default([b4_lex_param], [[]]))
m4_define([b4_lex_param], b4_lex_param))
m4_define([b4_parse_param], b4_parse_param))
# b4_lex_param_decl
# -------------------
# Extra formal arguments of the constructor.
m4_define([b4_lex_param_decl],
[m4_ifset([b4_lex_param],
[b4_remove_comma([$1],
b4_param_decls(b4_lex_param))],
[$1])])
m4_define([b4_param_decls],
[m4_map([b4_param_decl], [$@])])
m4_define([b4_param_decl], [, $1])
m4_define([b4_remove_comma], [m4_ifval(m4_quote($1), [$1, ], [])m4_shift2($@)])
# b4_parse_param_decl
# -------------------
# Extra formal arguments of the constructor.
m4_define([b4_parse_param_decl],
[m4_ifset([b4_parse_param],
[b4_remove_comma([$1],
b4_param_decls(b4_parse_param))],
[$1])])
# b4_lex_param_call
# -------------------
# Delegating the lexer parameters to the lexer constructor.
m4_define([b4_lex_param_call],
[m4_ifset([b4_lex_param],
[b4_remove_comma([$1],
b4_param_calls(b4_lex_param))],
[$1])])
m4_define([b4_param_calls],
[m4_map([b4_param_call], [$@])])
m4_define([b4_param_call], [, $2])
# b4_parse_param_cons
# -------------------
# Extra initialisations of the constructor.
m4_define([b4_parse_param_cons],
[m4_ifset([b4_parse_param],
[b4_constructor_calls(b4_parse_param)])])
m4_define([b4_constructor_calls],
[m4_map([b4_constructor_call], [$@])])
m4_define([b4_constructor_call],
[this.$2 = $2;
])
# b4_parse_param_vars
# -------------------
# Extra instance variables.
m4_define([b4_parse_param_vars],
[m4_ifset([b4_parse_param],
[
/* User arguments. */
b4_var_decls(b4_parse_param)])])
m4_define([b4_var_decls],
[m4_map_sep([b4_var_decl], [
], [$@])])
m4_define([b4_var_decl],
[ protected $1;])
# b4_public_types_declare
# -----------------------
# Define the public types: token, semantic value, location, and so forth.
# Depending on %define token_lex, may be output in the header or source file.
m4_define([b4_public_types_declare],
[[
alias Symbol = ]b4_parser_class[.Symbol;
alias Value = ]b4_yystype[;]b4_locations_if([[
alias Location = ]b4_location_type[;
alias Position = ]b4_position_type[;]b4_push_if([[
alias PUSH_MORE = ]b4_parser_class[.YYPUSH_MORE;
alias ABORT = ]b4_parser_class[.YYABORT;
alias ACCEPT = ]b4_parser_class[.YYACCEPT;]])[]])[
]])
# b4_basic_symbol_constructor_define
# ----------------------------------
# Create Symbol struct constructors for all the visible types.
m4_define([b4_basic_symbol_constructor_define],
[b4_token_visible_if([$1],
[[ this(TokenKind token]b4_symbol_if([$1], [has_type],
[[, ]b4_union_if([], [[typeof(YYSemanticType.]])b4_symbol([$1], [type])dnl
[]b4_union_if([], [[) ]])[ val]])[]b4_locations_if([[, Location loc]])[)
{
kind = yytranslate_(token);]b4_union_if([b4_symbol_if([$1], [has_type], [[
static foreach (member; __traits(allMembers, YYSemanticType))
{
static if (is(typeof(mixin("value_." ~ member)) == ]b4_symbol([$1], [type])[))
{
mixin("value_." ~ member ~ " = val;");
}
}]])], [b4_symbol_if([$1], [has_type], [[
value_.]b4_symbol([$1], [type])[ = val;]])])[]b4_locations_if([
location_ = loc;])[
}
]])])
# b4_symbol_type_define
# ---------------------
# Define symbol_type, the external type for symbols used for symbol
# constructors.
m4_define([b4_symbol_type_define],
[[
/**
* A complete symbol
*/
struct Symbol
{
private SymbolKind kind;
private Value value_;]b4_locations_if([[
private Location location_;]])[
]b4_type_foreach([b4_basic_symbol_constructor_define])[
SymbolKind token() { return kind; }
Value value() { return value_; }]b4_locations_if([[
Location location() { return location_; }]])[
]b4_token_ctor_if([b4_token_constructor_define])[
}
]])

2763
tools/winflexbison/data/skeletons/glr.c
File diff suppressed because it is too large
View File

397
tools/winflexbison/data/skeletons/glr.cc

@ -0,0 +1,397 @@
# C++ GLR skeleton for Bison
# Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# This skeleton produces a C++ class that encapsulates a C glr parser.
# This is in order to reduce the maintenance burden. The glr.c
# skeleton is clean and pure enough so that there are no real
# problems. The C++ interface is the same as that of lalr1.cc. In
# fact, glr.c can replace yacc.c without the user noticing any
# difference, and similarly for glr.cc replacing lalr1.cc.
#
# The passing of parse-params
#
# The additional arguments are stored as members of the parser
# object, yyparser. The C routines need to carry yyparser
# throughout the C parser; that's easy: make yyparser an
# additional parse-param. But because the C++ skeleton needs to
# know the "real" original parse-param, we save them
# (b4_parse_param_orig). Note that b4_parse_param is overquoted
# (and c.m4 strips one level of quotes). This is a PITA, and
# explains why there are so many levels of quotes.
#
# The locations
#
# We use location.cc just like lalr1.cc, but because glr.c stores
# the locations in a union, the position and location classes
# must not have a constructor. Therefore, contrary to lalr1.cc, we
# must not define "b4_location_constructors". As a consequence the
# user must initialize the first positions (in particular the
# filename member).
# We require a pure interface.
m4_define([b4_pure_flag], [1])
m4_include(b4_skeletonsdir/[c++.m4])
b4_bison_locations_if([m4_include(b4_skeletonsdir/[location.cc])])
m4_define([b4_parser_class],
[b4_percent_define_get([[api.parser.class]])])
# Save the parse parameters.
m4_define([b4_parse_param_orig], m4_defn([b4_parse_param]))
# b4_parse_param_wrap
# -------------------
# New ones.
m4_ifset([b4_parse_param],
[m4_define([b4_parse_param_wrap],
[[b4_namespace_ref::b4_parser_class[& yyparser], [[yyparser]]],]
m4_defn([b4_parse_param]))],
[m4_define([b4_parse_param_wrap],
[[b4_namespace_ref::b4_parser_class[& yyparser], [[yyparser]]]])
])
# b4_yy_symbol_print_define
# -------------------------
# Bypass the default implementation to generate the "yy_symbol_print"
# and "yy_symbol_value_print" functions.
m4_define([b4_yy_symbol_print_define],
[[/*--------------------.
| Print this symbol. |
`--------------------*/
static void
yy_symbol_print (FILE *, ]b4_namespace_ref::b4_parser_class[::symbol_kind_type yytoken,
const ]b4_namespace_ref::b4_parser_class[::value_type *yyvaluep]b4_locations_if([[,
const ]b4_namespace_ref::b4_parser_class[::location_type *yylocationp]])[]b4_user_formals[)
{
]b4_parse_param_use[]dnl
[ yyparser.yy_symbol_print_ (yytoken, yyvaluep]b4_locations_if([, yylocationp])[);
}
]])[
# Hijack the initial action to initialize the locations.
]b4_bison_locations_if([m4_define([b4_initial_action],
[yylloc.initialize ();]m4_ifdef([b4_initial_action], [
m4_defn([b4_initial_action])]))])[
# Hijack the post prologue to declare yyerror.
]m4_append([b4_post_prologue],
[b4_syncline([@oline@], [@ofile@])dnl
[static void
yyerror (]b4_locations_if([[const ]b4_namespace_ref::b4_parser_class[::location_type *yylocationp,
]])[]m4_ifset([b4_parse_param], [b4_formals(b4_parse_param),
])[const char* msg);]])[
# Inserted before the epilogue to define implementations (yyerror, parser member
# functions etc.).
]m4_define([b4_glr_cc_pre_epilogue],
[b4_syncline([@oline@], [@ofile@])dnl
[
/*------------------.
| Report an error. |
`------------------*/
static void
yyerror (]b4_locations_if([[const ]b4_namespace_ref::b4_parser_class[::location_type *yylocationp,
]])[]m4_ifset([b4_parse_param], [b4_formals(b4_parse_param),
])[const char* msg)
{
]b4_parse_param_use[]dnl
[ yyparser.error (]b4_locations_if([[*yylocationp, ]])[msg);
}
]b4_namespace_open[
]dnl In this section, the parse params are the original parse_params.
m4_pushdef([b4_parse_param], m4_defn([b4_parse_param_orig]))dnl
[ /// Build a parser object.
]b4_parser_class::b4_parser_class[ (]b4_parse_param_decl[)]m4_ifset([b4_parse_param], [
:])[
#if ]b4_api_PREFIX[DEBUG
]m4_ifset([b4_parse_param], [ ], [ :])[yycdebug_ (&std::cerr)]m4_ifset([b4_parse_param], [,])[
#endif]b4_parse_param_cons[
{}
]b4_parser_class::~b4_parser_class[ ()
{}
]b4_parser_class[::syntax_error::~syntax_error () YY_NOEXCEPT YY_NOTHROW
{}
int
]b4_parser_class[::operator() ()
{
return parse ();
}
int
]b4_parser_class[::parse ()
{
return ::yy_parse_impl (*this]b4_user_args[);
}
#if ]b4_api_PREFIX[DEBUG
/*--------------------.
| Print this symbol. |
`--------------------*/
void
]b4_parser_class[::yy_symbol_value_print_ (symbol_kind_type yykind,
const value_type* yyvaluep]b4_locations_if([[,
const location_type* yylocationp]])[) const
{]b4_locations_if([[
YY_USE (yylocationp);]])[
YY_USE (yyvaluep);
std::ostream& yyo = debug_stream ();
std::ostream& yyoutput = yyo;
YY_USE (yyoutput);
]b4_symbol_actions([printer])[
}
void
]b4_parser_class[::yy_symbol_print_ (symbol_kind_type yykind,
const value_type* yyvaluep]b4_locations_if([[,
const location_type* yylocationp]])[) const
{
*yycdebug_ << (yykind < YYNTOKENS ? "token" : "nterm")
<< ' ' << yysymbol_name (yykind) << " ("]b4_locations_if([[
<< *yylocationp << ": "]])[;
yy_symbol_value_print_ (yykind, yyvaluep]b4_locations_if([[, yylocationp]])[);
*yycdebug_ << ')';
}
std::ostream&
]b4_parser_class[::debug_stream () const
{
return *yycdebug_;
}
void
]b4_parser_class[::set_debug_stream (std::ostream& o)
{
yycdebug_ = &o;
}
]b4_parser_class[::debug_level_type
]b4_parser_class[::debug_level () const
{
return yydebug;
}
void
]b4_parser_class[::set_debug_level (debug_level_type l)
{
// Actually, it is yydebug which is really used.
yydebug = l;
}
#endif
]m4_popdef([b4_parse_param])dnl
b4_namespace_close[]dnl
])
m4_define([b4_define_symbol_kind],
[m4_format([#define %-15s %s],
b4_symbol($][1, kind_base),
b4_namespace_ref[::]b4_parser_class[::symbol_kind::]b4_symbol($1, kind_base))
])
# b4_glr_cc_setup
# ---------------
# Setup redirections for glr.c: Map the names used in c.m4 to the ones used
# in c++.m4.
m4_define([b4_glr_cc_setup],
[[]b4_attribute_define[
]b4_null_define[
// This skeleton is based on C, yet compiles it as C++.
// So expect warnings about C style casts.
#if defined __clang__ && 306 <= __clang_major__ * 100 + __clang_minor__
# pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined __GNUC__ && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
# pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
// On MacOS, PTRDIFF_MAX is defined as long long, which Clang's
// -pedantic reports as being a C++11 extension.
#if defined __APPLE__ && YY_CPLUSPLUS < 201103L \
&& defined __clang__ && 4 <= __clang_major__
# pragma clang diagnostic ignored "-Wc++11-long-long"
#endif
#undef ]b4_symbol(empty, [id])[
#define ]b4_symbol(empty, [id])[ ]b4_namespace_ref[::]b4_parser_class[::token::]b4_symbol(empty, [id])[
#undef ]b4_symbol(eof, [id])[
#define ]b4_symbol(eof, [id])[ ]b4_namespace_ref[::]b4_parser_class[::token::]b4_symbol(eof, [id])[
#undef ]b4_symbol(error, [id])[
#define ]b4_symbol(error, [id])[ ]b4_namespace_ref[::]b4_parser_class[::token::]b4_symbol(error, [id])[
#ifndef ]b4_api_PREFIX[STYPE
# define ]b4_api_PREFIX[STYPE ]b4_namespace_ref[::]b4_parser_class[::value_type
#endif
#ifndef ]b4_api_PREFIX[LTYPE
# define ]b4_api_PREFIX[LTYPE ]b4_namespace_ref[::]b4_parser_class[::location_type
#endif
typedef ]b4_namespace_ref[::]b4_parser_class[::symbol_kind_type yysymbol_kind_t;
// Expose C++ symbol kinds to C.
]b4_define_symbol_kind(-2)dnl
b4_symbol_foreach([b4_define_symbol_kind])])[
]])
m4_define([b4_undef_symbol_kind],
[[#undef ]b4_symbol($1, kind_base)[
]])
# b4_glr_cc_cleanup
# -----------------
# Remove redirections for glr.c.
m4_define([b4_glr_cc_cleanup],
[[#undef ]b4_symbol(empty, [id])[
#undef ]b4_symbol(eof, [id])[
#undef ]b4_symbol(error, [id])[
]b4_undef_symbol_kind(-2)dnl
b4_symbol_foreach([b4_undef_symbol_kind])dnl
])
# b4_shared_declarations(hh|cc)
# -----------------------------
# Declaration that might either go into the header (if --header, $1 = hh)
# or in the implementation file.
m4_define([b4_shared_declarations],
[m4_pushdef([b4_parse_param], m4_defn([b4_parse_param_orig]))dnl
b4_percent_code_get([[requires]])[
#include <iostream>
#include <stdexcept>
#include <string>
]b4_cxx_portability[
]m4_ifdef([b4_location_include],
[[# include ]b4_location_include])[
]b4_variant_if([b4_variant_includes])[
// Whether we are compiled with exception support.
#ifndef YY_EXCEPTIONS
# if defined __GNUC__ && !defined __EXCEPTIONS
# define YY_EXCEPTIONS 0
# else
# define YY_EXCEPTIONS 1
# endif
#endif
]b4_YYDEBUG_define[
]b4_namespace_open[
]b4_bison_locations_if([m4_ifndef([b4_location_file],
[b4_location_define])])[
/// A Bison parser.
class ]b4_parser_class[
{
public:
]b4_public_types_declare[
/// Build a parser object.
]b4_parser_class[ (]b4_parse_param_decl[);
virtual ~]b4_parser_class[ ();
/// Parse. An alias for parse ().
/// \returns 0 iff parsing succeeded.
int operator() ();
/// Parse.
/// \returns 0 iff parsing succeeded.
virtual int parse ();
#if ]b4_api_PREFIX[DEBUG
/// The current debugging stream.
std::ostream& debug_stream () const;
/// Set the current debugging stream.
void set_debug_stream (std::ostream &);
/// Type for debugging levels.
typedef int debug_level_type;
/// The current debugging level.
debug_level_type debug_level () const;
/// Set the current debugging level.
void set_debug_level (debug_level_type l);
#endif
/// Report a syntax error.]b4_locations_if([[
/// \param loc where the syntax error is found.]])[
/// \param msg a description of the syntax error.
virtual void error (]b4_locations_if([[const location_type& loc, ]])[const std::string& msg);
# if ]b4_api_PREFIX[DEBUG
public:
/// \brief Report a symbol value on the debug stream.
/// \param yykind The symbol kind.
/// \param yyvaluep Its semantic value.]b4_locations_if([[
/// \param yylocationp Its location.]])[
virtual void yy_symbol_value_print_ (symbol_kind_type yykind,
const value_type* yyvaluep]b4_locations_if([[,
const location_type* yylocationp]])[) const;
/// \brief Report a symbol on the debug stream.
/// \param yykind The symbol kind.
/// \param yyvaluep Its semantic value.]b4_locations_if([[
/// \param yylocationp Its location.]])[
virtual void yy_symbol_print_ (symbol_kind_type yykind,
const value_type* yyvaluep]b4_locations_if([[,
const location_type* yylocationp]])[) const;
private:
/// Debug stream.
std::ostream* yycdebug_;
#endif
]b4_parse_param_vars[
};
]b4_namespace_close[
]b4_percent_code_get([[provides]])[
]m4_popdef([b4_parse_param])dnl
])[
]b4_header_if(
[b4_output_begin([b4_spec_header_file])
b4_copyright([Skeleton interface for Bison GLR parsers in C++],
[2002-2015, 2018-2021])[
// C++ GLR parser skeleton written by Akim Demaille.
]b4_disclaimer[
]b4_cpp_guard_open([b4_spec_mapped_header_file])[
]b4_shared_declarations[
]b4_cpp_guard_close([b4_spec_mapped_header_file])[
]b4_output_end])
# Let glr.c (and b4_shared_declarations) believe that the user
# arguments include the parser itself.
m4_pushdef([b4_parse_param], m4_defn([b4_parse_param_wrap]))
m4_include(b4_skeletonsdir/[glr.c])
m4_popdef([b4_parse_param])

3533
tools/winflexbison/data/skeletons/glr2.cc
File diff suppressed because it is too large
View File

27
tools/winflexbison/data/skeletons/java-skel.m4

@ -0,0 +1,27 @@
-*- Autoconf -*-
# Java skeleton dispatching for Bison.
# Copyright (C) 2007, 2009-2015, 2018-2021 Free Software Foundation,
# Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
b4_glr_if( [b4_complain([%%glr-parser not supported for Java])])
b4_nondeterministic_if([b4_complain([%%nondeterministic-parser not supported for Java])])
m4_define_default([b4_used_skeleton], [b4_skeletonsdir/[lalr1.java]])
m4_define_default([b4_skeleton], ["b4_basename(b4_used_skeleton)"])
m4_include(b4_used_skeleton)

502
tools/winflexbison/data/skeletons/java.m4

@ -0,0 +1,502 @@
-*- Autoconf -*-
# Java language support for Bison
# Copyright (C) 2007-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
m4_include(b4_skeletonsdir/[c-like.m4])
# b4_list2(LIST1, LIST2)
# ----------------------
# Join two lists with a comma if necessary.
m4_define([b4_list2],
[$1[]m4_ifval(m4_quote($1), [m4_ifval(m4_quote($2), [[, ]])])[]$2])
# b4_percent_define_get3(DEF, PRE, POST, NOT)
# -------------------------------------------
# Expand to the value of DEF surrounded by PRE and POST if it's %define'ed,
# otherwise NOT.
m4_define([b4_percent_define_get3],
[m4_ifval(m4_quote(b4_percent_define_get([$1])),
[$2[]b4_percent_define_get([$1])[]$3], [$4])])
# b4_flag_value(BOOLEAN-FLAG)
# ---------------------------
m4_define([b4_flag_value], [b4_flag_if([$1], [true], [false])])
# b4_parser_class_declaration
# ---------------------------
# The declaration of the parser class ("class YYParser"), with all its
# qualifiers/annotations.
b4_percent_define_default([[api.parser.abstract]], [[false]])
b4_percent_define_default([[api.parser.final]], [[false]])
b4_percent_define_default([[api.parser.public]], [[false]])
b4_percent_define_default([[api.parser.strictfp]], [[false]])
m4_define([b4_parser_class_declaration],
[b4_percent_define_get3([api.parser.annotations], [], [ ])dnl
b4_percent_define_flag_if([api.parser.public], [public ])dnl
b4_percent_define_flag_if([api.parser.abstract], [abstract ])dnl
b4_percent_define_flag_if([api.parser.final], [final ])dnl
b4_percent_define_flag_if([api.parser.strictfp], [strictfp ])dnl
[class ]b4_parser_class[]dnl
b4_percent_define_get3([api.parser.extends], [ extends ])dnl
b4_percent_define_get3([api.parser.implements], [ implements ])])
# b4_lexer_if(TRUE, FALSE)
# ------------------------
m4_define([b4_lexer_if],
[b4_percent_code_ifdef([[lexer]], [$1], [$2])])
# b4_identification
# -----------------
m4_define([b4_identification],
[[ /** Version number for the Bison executable that generated this parser. */
public static final String bisonVersion = "]b4_version_string[";
/** Name of the skeleton that generated this parser. */
public static final String bisonSkeleton = ]b4_skeleton[;
]])
## ------------ ##
## Data types. ##
## ------------ ##
# b4_int_type(MIN, MAX)
# ---------------------
# Return the smallest int type able to handle numbers ranging from
# MIN to MAX (included).
m4_define([b4_int_type],
[m4_if(b4_ints_in($@, [-128], [127]), [1], [byte],
b4_ints_in($@, [-32768], [32767]), [1], [short],
[int])])
# b4_int_type_for(NAME)
# ---------------------
# Return the smallest int type able to handle numbers ranging from
# 'NAME_min' to 'NAME_max' (included).
m4_define([b4_int_type_for],
[b4_int_type($1_min, $1_max)])
# b4_null
# -------
m4_define([b4_null], [null])
# b4_typed_parser_table_define(TYPE, NAME, DATA, COMMENT)
# -------------------------------------------------------
# We use intermediate functions (e.g., yypact_init) to work around the
# 64KB limit for JVM methods. See
# https://lists.gnu.org/r/help-bison/2008-11/msg00004.html.
m4_define([b4_typed_parser_table_define],
[m4_ifval([$4], [b4_comment([$4])
])dnl
[private static final ]$1[[] yy$2_ = yy$2_init();
private static final ]$1[[] yy$2_init()
{
return new ]$1[[]
{
]$3[
};
}]])
# b4_integral_parser_table_define(NAME, DATA, COMMENT)
#-----------------------------------------------------
m4_define([b4_integral_parser_table_define],
[b4_typed_parser_table_define([b4_int_type_for([$2])], [$1], [$2], [$3])])
## ------------- ##
## Token kinds. ##
## ------------- ##
# b4_token_enum(TOKEN-NUM)
# ------------------------
# Output the definition of this token as an enum.
m4_define([b4_token_enum],
[b4_token_visible_if([$1],
[m4_format([[ /** Token %s, to be returned by the scanner. */
static final int %s = %s%s;
]],
b4_symbol([$1], [tag]),
b4_symbol([$1], [id]),
b4_symbol([$1], b4_api_token_raw_if([[number]], [[code]])))])])
# b4_token_enums
# --------------
# Output the definition of the tokens (if there are) as enums.
m4_define([b4_token_enums],
[b4_any_token_visible_if([ /* Token kinds. */
b4_symbol_foreach([b4_token_enum])])])
## -------------- ##
## Symbol kinds. ##
## -------------- ##
# b4_symbol_kind(NUM)
# -------------------
m4_define([b4_symbol_kind],
[SymbolKind.b4_symbol_kind_base($@)])
# b4_symbol_enum(SYMBOL-NUM)
# --------------------------
# Output the definition of this symbol as an enum.
m4_define([b4_symbol_enum],
[m4_format([ %-30s %s],
m4_format([[%s(%s)%s]],
b4_symbol([$1], [kind_base]),
[$1],
m4_if([$1], b4_last_symbol, [[;]], [[,]])),
[b4_symbol_tag_comment([$1])])])
# b4_declare_symbol_enum
# ----------------------
# The definition of the symbol internal numbers as an enum.
m4_define([b4_declare_symbol_enum],
[[ public enum SymbolKind
{
]b4_symbol_foreach([b4_symbol_enum])[
private final int yycode_;
SymbolKind (int n) {
this.yycode_ = n;
}
private static final SymbolKind[] values_ = {
]m4_map_args_sep([b4_symbol_kind(], [)], [,
], b4_symbol_numbers)[
};
static final SymbolKind get(int code) {
return values_[code];
}
public final int getCode() {
return this.yycode_;
}
]b4_parse_error_bmatch(
[simple\|verbose],
[[ /* Return YYSTR after stripping away unnecessary quotes and
backslashes, so that it's suitable for yyerror. The heuristic is
that double-quoting is unnecessary unless the string contains an
apostrophe, a comma, or backslash (other than backslash-backslash).
YYSTR is taken from yytname. */
private static String yytnamerr_(String yystr)
{
if (yystr.charAt (0) == '"')
{
StringBuffer yyr = new StringBuffer();
strip_quotes: for (int i = 1; i < yystr.length(); i++)
switch (yystr.charAt(i))
{
case '\'':
case ',':
break strip_quotes;
case '\\':
if (yystr.charAt(++i) != '\\')
break strip_quotes;
/* Fall through. */
default:
yyr.append(yystr.charAt(i));
break;
case '"':
return yyr.toString();
}
}
return yystr;
}
/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
First, the terminals, then, starting at \a YYNTOKENS_, nonterminals. */
]b4_typed_parser_table_define([String], [tname], [b4_tname])[
/* The user-facing name of this symbol. */
public final String getName() {
return yytnamerr_(yytname_[yycode_]);
}
]],
[custom\|detailed],
[[ /* YYNAMES_[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
First, the terminals, then, starting at \a YYNTOKENS_, nonterminals. */
]b4_typed_parser_table_define([String], [names], [b4_symbol_names])[
/* The user-facing name of this symbol. */
public final String getName() {
return yynames_[yycode_];
}]])[
};
]])])
# b4_case(ID, CODE, [COMMENTS])
# -----------------------------
# We need to fool Java's stupid unreachable code detection.
m4_define([b4_case],
[ case $1:m4_ifval([$3], [ b4_comment([$3])])
if (yyn == $1)
$2;
break;
])
# b4_predicate_case(LABEL, CONDITIONS)
# ------------------------------------
m4_define([b4_predicate_case],
[ case $1:
if (! ($2)) YYERROR;
break;
])
## -------- ##
## Checks. ##
## -------- ##
b4_percent_define_check_kind([[api.value.type]], [code], [deprecated])
b4_percent_define_check_kind([[annotations]], [code], [deprecated])
b4_percent_define_check_kind([[extends]], [code], [deprecated])
b4_percent_define_check_kind([[implements]], [code], [deprecated])
b4_percent_define_check_kind([[init_throws]], [code], [deprecated])
b4_percent_define_check_kind([[lex_throws]], [code], [deprecated])
b4_percent_define_check_kind([[api.parser.class]], [code], [deprecated])
b4_percent_define_check_kind([[throws]], [code], [deprecated])
## ---------------- ##
## Default values. ##
## ---------------- ##
m4_define([b4_yystype], [b4_percent_define_get([[api.value.type]])])
b4_percent_define_default([[api.value.type]], [[Object]])
b4_percent_define_default([[api.symbol.prefix]], [[S_]])
# b4_api_prefix, b4_api_PREFIX
# ----------------------------
# Corresponds to %define api.prefix
b4_percent_define_default([[api.prefix]], [[YY]])
m4_define([b4_api_prefix],
[b4_percent_define_get([[api.prefix]])])
m4_define([b4_api_PREFIX],
[m4_toupper(b4_api_prefix)])
# b4_prefix
# ---------
# If the %name-prefix is not given, it is api.prefix.
m4_define_default([b4_prefix], [b4_api_prefix])
b4_percent_define_default([[api.parser.class]], [b4_prefix[]Parser])
m4_define([b4_parser_class], [b4_percent_define_get([[api.parser.class]])])
b4_percent_define_default([[lex_throws]], [[java.io.IOException]])
m4_define([b4_lex_throws], [b4_percent_define_get([[lex_throws]])])
b4_percent_define_default([[throws]], [])
m4_define([b4_throws], [b4_percent_define_get([[throws]])])
b4_percent_define_default([[init_throws]], [])
m4_define([b4_init_throws], [b4_percent_define_get([[init_throws]])])
b4_percent_define_default([[api.location.type]], [Location])
m4_define([b4_location_type], [b4_percent_define_get([[api.location.type]])])
b4_percent_define_default([[api.position.type]], [Position])
m4_define([b4_position_type], [b4_percent_define_get([[api.position.type]])])
## ----------------- ##
## Semantic Values. ##
## ----------------- ##
# b4_symbol_translate(STRING)
# ---------------------------
# Used by "bison" in the array of symbol names to mark those that
# require translation.
m4_define([b4_symbol_translate],
[[i18n($1)]])
# b4_trans(STRING)
# ----------------
# Translate a string if i18n is enabled. Avoid collision with b4_translate.
m4_define([b4_trans],
[b4_has_translations_if([i18n($1)], [$1])])
# b4_symbol_value(VAL, [SYMBOL-NUM], [TYPE-TAG])
# ----------------------------------------------
# See README.
m4_define([b4_symbol_value],
[m4_ifval([$3],
[(($3)($1))],
[m4_ifval([$2],
[b4_symbol_if([$2], [has_type],
[((b4_symbol([$2], [type]))($1))],
[$1])],
[$1])])])
# b4_lhs_value([SYMBOL-NUM], [TYPE])
# ----------------------------------
# See README.
m4_define([b4_lhs_value], [yyval])
# b4_rhs_data(RULE-LENGTH, POS)
# -----------------------------
# See README.
m4_define([b4_rhs_data],
[yystack.valueAt (b4_subtract($@))])
# b4_rhs_value(RULE-LENGTH, POS, SYMBOL-NUM, [TYPE])
# --------------------------------------------------
# See README.
#
# In this simple implementation, %token and %type have class names
# between the angle brackets.
m4_define([b4_rhs_value],
[b4_symbol_value([b4_rhs_data([$1], [$2])], [$3], [$4])])
# b4_lhs_location()
# -----------------
# Expansion of @$.
m4_define([b4_lhs_location],
[(yyloc)])
# b4_rhs_location(RULE-LENGTH, POS)
# ---------------------------------
# Expansion of @POS, where the current rule has RULE-LENGTH symbols
# on RHS.
m4_define([b4_rhs_location],
[yystack.locationAt (b4_subtract($@))])
# b4_lex_param
# b4_parse_param
# --------------
# If defined, b4_lex_param arrives double quoted, but below we prefer
# it to be single quoted. Same for b4_parse_param.
# TODO: should be in bison.m4
m4_define_default([b4_lex_param], [[]])
m4_define([b4_lex_param], b4_lex_param)
m4_define([b4_parse_param], b4_parse_param)
# b4_lex_param_decl
# -----------------
# Extra formal arguments of the constructor.
m4_define([b4_lex_param_decl],
[m4_ifset([b4_lex_param],
[b4_remove_comma([$1],
b4_param_decls(b4_lex_param))],
[$1])])
m4_define([b4_param_decls],
[m4_map([b4_param_decl], [$@])])
m4_define([b4_param_decl], [, $1])
m4_define([b4_remove_comma], [m4_ifval(m4_quote($1), [$1, ], [])m4_shift2($@)])
# b4_parse_param_decl
# -------------------
# Extra formal arguments of the constructor.
m4_define([b4_parse_param_decl],
[m4_ifset([b4_parse_param],
[b4_remove_comma([$1],
b4_param_decls(b4_parse_param))],
[$1])])
# b4_lex_param_call
# -----------------
# Delegating the lexer parameters to the lexer constructor.
m4_define([b4_lex_param_call],
[m4_ifset([b4_lex_param],
[b4_remove_comma([$1],
b4_param_calls(b4_lex_param))],
[$1])])
m4_define([b4_param_calls],
[m4_map([b4_param_call], [$@])])
m4_define([b4_param_call], [, $2])
# b4_parse_param_cons
# -------------------
# Extra initialisations of the constructor.
m4_define([b4_parse_param_cons],
[m4_ifset([b4_parse_param],
[b4_constructor_calls(b4_parse_param)])])
m4_define([b4_constructor_calls],
[m4_map([b4_constructor_call], [$@])])
m4_define([b4_constructor_call],
[this.$2 = $2;
])
# b4_parse_param_vars
# -------------------
# Extra instance variables.
m4_define([b4_parse_param_vars],
[m4_ifset([b4_parse_param],
[
/* User arguments. */
b4_var_decls(b4_parse_param)])])
m4_define([b4_var_decls],
[m4_map_sep([b4_var_decl], [
], [$@])])
m4_define([b4_var_decl],
[ protected final $1;])
# b4_maybe_throws(THROWS)
# -----------------------
# Expand to either an empty string or "throws THROWS".
m4_define([b4_maybe_throws],
[m4_ifval($1, [ throws $1])])

1633
tools/winflexbison/data/skeletons/lalr1.cc
File diff suppressed because it is too large
View File

1326
tools/winflexbison/data/skeletons/lalr1.d
File diff suppressed because it is too large
View File

1303
tools/winflexbison/data/skeletons/lalr1.java
File diff suppressed because it is too large
View File

380
tools/winflexbison/data/skeletons/location.cc

@ -0,0 +1,380 @@
# C++ skeleton for Bison
# Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
m4_pushdef([b4_copyright_years],
[2002-2015, 2018-2021])
# b4_location_file
# ----------------
# Name of the file containing the position/location class,
# if we want this file.
b4_percent_define_check_file([b4_location_file],
[[api.location.file]],
b4_header_if([[location.hh]]))
# b4_location_include
# -------------------
# If location.hh is to be generated, the name under which should it be
# included.
#
# b4_location_path
# ----------------
# The path to use for the CPP guard.
m4_ifdef([b4_location_file],
[m4_define([b4_location_include],
[b4_percent_define_get([[api.location.include]],
["b4_location_file"])])
m4_define([b4_location_path],
b4_percent_define_get([[api.location.include]],
["b4_mapped_dir_prefix[]b4_location_file"]))
m4_define([b4_location_path],
m4_substr(m4_defn([b4_location_path]), 1, m4_eval(m4_len(m4_defn([b4_location_path])) - 2)))
])
# b4_position_file
# ----------------
# Name of the file containing the position class, if we want this file.
b4_header_if(
[b4_required_version_if(
[30200], [],
[m4_ifdef([b4_location_file],
[m4_define([b4_position_file], [position.hh])])])])
# b4_location_define
# ------------------
# Define the position and location classes.
m4_define([b4_location_define],
[[ /// A point in a source file.
class position
{
public:
/// Type for file name.
typedef ]b4_percent_define_get([[api.filename.type]])[ filename_type;
/// Type for line and column numbers.
typedef int counter_type;
]m4_ifdef([b4_location_constructors], [[
/// Construct a position.
explicit position (filename_type* f = YY_NULLPTR,
counter_type l = ]b4_location_initial_line[,
counter_type c = ]b4_location_initial_column[)
: filename (f)
, line (l)
, column (c)
{}
]])[
/// Initialization.
void initialize (filename_type* fn = YY_NULLPTR,
counter_type l = ]b4_location_initial_line[,
counter_type c = ]b4_location_initial_column[)
{
filename = fn;
line = l;
column = c;
}
/** \name Line and Column related manipulators
** \{ */
/// (line related) Advance to the COUNT next lines.
void lines (counter_type count = 1)
{
if (count)
{
column = ]b4_location_initial_column[;
line = add_ (line, count, ]b4_location_initial_line[);
}
}
/// (column related) Advance to the COUNT next columns.
void columns (counter_type count = 1)
{
column = add_ (column, count, ]b4_location_initial_column[);
}
/** \} */
/// File name to which this position refers.
filename_type* filename;
/// Current line number.
counter_type line;
/// Current column number.
counter_type column;
private:
/// Compute max (min, lhs+rhs).
static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min)
{
return lhs + rhs < min ? min : lhs + rhs;
}
};
/// Add \a width columns, in place.
inline position&
operator+= (position& res, position::counter_type width)
{
res.columns (width);
return res;
}
/// Add \a width columns.
inline position
operator+ (position res, position::counter_type width)
{
return res += width;
}
/// Subtract \a width columns, in place.
inline position&
operator-= (position& res, position::counter_type width)
{
return res += -width;
}
/// Subtract \a width columns.
inline position
operator- (position res, position::counter_type width)
{
return res -= width;
}
]b4_percent_define_flag_if([[define_location_comparison]], [[
/// Compare two position objects.
inline bool
operator== (const position& pos1, const position& pos2)
{
return (pos1.line == pos2.line
&& pos1.column == pos2.column
&& (pos1.filename == pos2.filename
|| (pos1.filename && pos2.filename
&& *pos1.filename == *pos2.filename)));
}
/// Compare two position objects.
inline bool
operator!= (const position& pos1, const position& pos2)
{
return !(pos1 == pos2);
}
]])[
/** \brief Intercept output stream redirection.
** \param ostr the destination output stream
** \param pos a reference to the position to redirect
*/
template <typename YYChar>
std::basic_ostream<YYChar>&
operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
{
if (pos.filename)
ostr << *pos.filename << ':';
return ostr << pos.line << '.' << pos.column;
}
/// Two points in a source file.
class location
{
public:
/// Type for file name.
typedef position::filename_type filename_type;
/// Type for line and column numbers.
typedef position::counter_type counter_type;
]m4_ifdef([b4_location_constructors], [
/// Construct a location from \a b to \a e.
location (const position& b, const position& e)
: begin (b)
, end (e)
{}
/// Construct a 0-width location in \a p.
explicit location (const position& p = position ())
: begin (p)
, end (p)
{}
/// Construct a 0-width location in \a f, \a l, \a c.
explicit location (filename_type* f,
counter_type l = ]b4_location_initial_line[,
counter_type c = ]b4_location_initial_column[)
: begin (f, l, c)
, end (f, l, c)
{}
])[
/// Initialization.
void initialize (filename_type* f = YY_NULLPTR,
counter_type l = ]b4_location_initial_line[,
counter_type c = ]b4_location_initial_column[)
{
begin.initialize (f, l, c);
end = begin;
}
/** \name Line and Column related manipulators
** \{ */
public:
/// Reset initial location to final location.
void step ()
{
begin = end;
}
/// Extend the current location to the COUNT next columns.
void columns (counter_type count = 1)
{
end += count;
}
/// Extend the current location to the COUNT next lines.
void lines (counter_type count = 1)
{
end.lines (count);
}
/** \} */
public:
/// Beginning of the located region.
position begin;
/// End of the located region.
position end;
};
/// Join two locations, in place.
inline location&
operator+= (location& res, const location& end)
{
res.end = end.end;
return res;
}
/// Join two locations.
inline location
operator+ (location res, const location& end)
{
return res += end;
}
/// Add \a width columns to the end position, in place.
inline location&
operator+= (location& res, location::counter_type width)
{
res.columns (width);
return res;
}
/// Add \a width columns to the end position.
inline location
operator+ (location res, location::counter_type width)
{
return res += width;
}
/// Subtract \a width columns to the end position, in place.
inline location&
operator-= (location& res, location::counter_type width)
{
return res += -width;
}
/// Subtract \a width columns to the end position.
inline location
operator- (location res, location::counter_type width)
{
return res -= width;
}
]b4_percent_define_flag_if([[define_location_comparison]], [[
/// Compare two location objects.
inline bool
operator== (const location& loc1, const location& loc2)
{
return loc1.begin == loc2.begin && loc1.end == loc2.end;
}
/// Compare two location objects.
inline bool
operator!= (const location& loc1, const location& loc2)
{
return !(loc1 == loc2);
}
]])[
/** \brief Intercept output stream redirection.
** \param ostr the destination output stream
** \param loc a reference to the location to redirect
**
** Avoid duplicate information.
*/
template <typename YYChar>
std::basic_ostream<YYChar>&
operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
{
location::counter_type end_col
= 0 < loc.end.column ? loc.end.column - 1 : 0;
ostr << loc.begin;
if (loc.end.filename
&& (!loc.begin.filename
|| *loc.begin.filename != *loc.end.filename))
ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
else if (loc.begin.line < loc.end.line)
ostr << '-' << loc.end.line << '.' << end_col;
else if (loc.begin.column < end_col)
ostr << '-' << end_col;
return ostr;
}
]])
m4_ifdef([b4_position_file], [[
]b4_output_begin([b4_dir_prefix], [b4_position_file])[
]b4_generated_by[
// Starting with Bison 3.2, this file is useless: the structure it
// used to define is now defined in "]b4_location_file[".
//
// To get rid of this file:
// 1. add '%require "3.2"' (or newer) to your grammar file
// 2. remove references to this file from your build system
// 3. if you used to include it, include "]b4_location_file[" instead.
#include ]b4_location_include[
]b4_output_end[
]])
m4_ifdef([b4_location_file], [[
]b4_output_begin([b4_dir_prefix], [b4_location_file])[
]b4_copyright([Locations for Bison parsers in C++])[
/**
** \file ]b4_location_path[
** Define the ]b4_namespace_ref[::location class.
*/
]b4_cpp_guard_open([b4_location_path])[
# include <iostream>
# include <string>
]b4_null_define[
]b4_namespace_open[
]b4_location_define[
]b4_namespace_close[
]b4_cpp_guard_close([b4_location_path])[
]b4_output_end[
]])
m4_popdef([b4_copyright_years])

157
tools/winflexbison/data/skeletons/stack.hh

@ -0,0 +1,157 @@
# C++ skeleton for Bison
# Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# b4_stack_file
# -------------
# Name of the file containing the stack class, if we want this file.
b4_header_if([b4_required_version_if([30200], [],
[m4_define([b4_stack_file], [stack.hh])])])
# b4_stack_define
# ---------------
m4_define([b4_stack_define],
[[ /// A stack with random access from its top.
template <typename T, typename S = std::vector<T> >
class stack
{
public:
// Hide our reversed order.
typedef typename S::iterator iterator;
typedef typename S::const_iterator const_iterator;
typedef typename S::size_type size_type;
typedef typename std::ptrdiff_t index_type;
stack (size_type n = 200) YY_NOEXCEPT
: seq_ (n)
{}
#if 201103L <= YY_CPLUSPLUS
/// Non copyable.
stack (const stack&) = delete;
/// Non copyable.
stack& operator= (const stack&) = delete;
#endif
/// Random access.
///
/// Index 0 returns the topmost element.
const T&
operator[] (index_type i) const
{
return seq_[size_type (size () - 1 - i)];
}
/// Random access.
///
/// Index 0 returns the topmost element.
T&
operator[] (index_type i)
{
return seq_[size_type (size () - 1 - i)];
}
/// Steal the contents of \a t.
///
/// Close to move-semantics.
void
push (YY_MOVE_REF (T) t)
{
seq_.push_back (T ());
operator[] (0).move (t);
}
/// Pop elements from the stack.
void
pop (std::ptrdiff_t n = 1) YY_NOEXCEPT
{
for (; 0 < n; --n)
seq_.pop_back ();
}
/// Pop all elements from the stack.
void
clear () YY_NOEXCEPT
{
seq_.clear ();
}
/// Number of elements on the stack.
index_type
size () const YY_NOEXCEPT
{
return index_type (seq_.size ());
}
/// Iterator on top of the stack (going downwards).
const_iterator
begin () const YY_NOEXCEPT
{
return seq_.begin ();
}
/// Bottom of the stack.
const_iterator
end () const YY_NOEXCEPT
{
return seq_.end ();
}
/// Present a slice of the top of a stack.
class slice
{
public:
slice (const stack& stack, index_type range) YY_NOEXCEPT
: stack_ (stack)
, range_ (range)
{}
const T&
operator[] (index_type i) const
{
return stack_[range_ - i];
}
private:
const stack& stack_;
index_type range_;
};
private:
#if YY_CPLUSPLUS < 201103L
/// Non copyable.
stack (const stack&);
/// Non copyable.
stack& operator= (const stack&);
#endif
/// The wrapped container.
S seq_;
};
]])
m4_ifdef([b4_stack_file],
[b4_output_begin([b4_dir_prefix], [b4_stack_file])[
]b4_generated_by[
// Starting with Bison 3.2, this file is useless: the structure it
// used to define is now defined with the parser itself.
//
// To get rid of this file:
// 1. add '%require "3.2"' (or newer) to your grammar file
// 2. remove references to this file from your build system.
]b4_output_end[
]])

2
tools/winflexbison/data/skeletons/traceon.m4

@ -0,0 +1,2 @@
dnl GNU M4 treats -dV in a position-independent manner.
m4_debugmode(V)m4_traceon()dnl

525
tools/winflexbison/data/skeletons/variant.hh

@ -0,0 +1,525 @@
# C++ skeleton for Bison
# Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
## --------- ##
## variant. ##
## --------- ##
# b4_assert
# ---------
# The name of YY_ASSERT.
m4_define([b4_assert],
[b4_api_PREFIX[]_ASSERT])
# b4_symbol_variant(YYTYPE, YYVAL, ACTION, [ARGS])
# ------------------------------------------------
# Run some ACTION ("build", or "destroy") on YYVAL of symbol type
# YYTYPE.
m4_define([b4_symbol_variant],
[m4_pushdef([b4_dollar_dollar],
[$2.$3< $][3 > (m4_shift3($@))])dnl
switch ($1)
{
b4_type_foreach([_b4_type_action])[]dnl
default:
break;
}
m4_popdef([b4_dollar_dollar])dnl
])
# _b4_char_sizeof_counter
# -----------------------
# A counter used by _b4_char_sizeof_dummy to create fresh symbols.
m4_define([_b4_char_sizeof_counter],
[0])
# _b4_char_sizeof_dummy
# ---------------------
# At each call return a new C++ identifier.
m4_define([_b4_char_sizeof_dummy],
[m4_define([_b4_char_sizeof_counter], m4_incr(_b4_char_sizeof_counter))dnl
dummy[]_b4_char_sizeof_counter])
# b4_char_sizeof(SYMBOL-NUMS)
# ---------------------------
# To be mapped on the list of type names to produce:
#
# char dummy1[sizeof (type_name_1)];
# char dummy2[sizeof (type_name_2)];
#
# for defined type names.
m4_define([b4_char_sizeof],
[b4_symbol_if([$1], [has_type],
[
m4_map([ b4_symbol_tag_comment], [$@])dnl
char _b4_char_sizeof_dummy@{sizeof (b4_symbol([$1], [type]))@};
])])
# b4_variant_includes
# -------------------
# The needed includes for variants support.
m4_define([b4_variant_includes],
[b4_parse_assert_if([[#include <typeinfo>
#ifndef ]b4_assert[
# include <cassert>
# define ]b4_assert[ assert
#endif
]])])
## -------------------------- ##
## Adjustments for variants. ##
## -------------------------- ##
# b4_value_type_declare
# ---------------------
# Define value_type.
m4_define([b4_value_type_declare],
[[ /// A buffer to store and retrieve objects.
///
/// Sort of a variant, but does not keep track of the nature
/// of the stored data, since that knowledge is available
/// via the current parser state.
class value_type
{
public:
/// Type of *this.
typedef value_type self_type;
/// Empty construction.
value_type () YY_NOEXCEPT
: yyraw_ ()]b4_parse_assert_if([
, yytypeid_ (YY_NULLPTR)])[
{}
/// Construct and fill.
template <typename T>
value_type (YY_RVREF (T) t)]b4_parse_assert_if([
: yytypeid_ (&typeid (T))])[
{]b4_parse_assert_if([[
]b4_assert[ (sizeof (T) <= size);]])[
new (yyas_<T> ()) T (YY_MOVE (t));
}
#if 201103L <= YY_CPLUSPLUS
/// Non copyable.
value_type (const self_type&) = delete;
/// Non copyable.
self_type& operator= (const self_type&) = delete;
#endif
/// Destruction, allowed only if empty.
~value_type () YY_NOEXCEPT
{]b4_parse_assert_if([
]b4_assert[ (!yytypeid_);
])[}
# if 201103L <= YY_CPLUSPLUS
/// Instantiate a \a T in here from \a t.
template <typename T, typename... U>
T&
emplace (U&&... u)
{]b4_parse_assert_if([[
]b4_assert[ (!yytypeid_);
]b4_assert[ (sizeof (T) <= size);
yytypeid_ = & typeid (T);]])[
return *new (yyas_<T> ()) T (std::forward <U>(u)...);
}
# else
/// Instantiate an empty \a T in here.
template <typename T>
T&
emplace ()
{]b4_parse_assert_if([[
]b4_assert[ (!yytypeid_);
]b4_assert[ (sizeof (T) <= size);
yytypeid_ = & typeid (T);]])[
return *new (yyas_<T> ()) T ();
}
/// Instantiate a \a T in here from \a t.
template <typename T>
T&
emplace (const T& t)
{]b4_parse_assert_if([[
]b4_assert[ (!yytypeid_);
]b4_assert[ (sizeof (T) <= size);
yytypeid_ = & typeid (T);]])[
return *new (yyas_<T> ()) T (t);
}
# endif
/// Instantiate an empty \a T in here.
/// Obsolete, use emplace.
template <typename T>
T&
build ()
{
return emplace<T> ();
}
/// Instantiate a \a T in here from \a t.
/// Obsolete, use emplace.
template <typename T>
T&
build (const T& t)
{
return emplace<T> (t);
}
/// Accessor to a built \a T.
template <typename T>
T&
as () YY_NOEXCEPT
{]b4_parse_assert_if([[
]b4_assert[ (yytypeid_);
]b4_assert[ (*yytypeid_ == typeid (T));
]b4_assert[ (sizeof (T) <= size);]])[
return *yyas_<T> ();
}
/// Const accessor to a built \a T (for %printer).
template <typename T>
const T&
as () const YY_NOEXCEPT
{]b4_parse_assert_if([[
]b4_assert[ (yytypeid_);
]b4_assert[ (*yytypeid_ == typeid (T));
]b4_assert[ (sizeof (T) <= size);]])[
return *yyas_<T> ();
}
/// Swap the content with \a that, of same type.
///
/// Both variants must be built beforehand, because swapping the actual
/// data requires reading it (with as()), and this is not possible on
/// unconstructed variants: it would require some dynamic testing, which
/// should not be the variant's responsibility.
/// Swapping between built and (possibly) non-built is done with
/// self_type::move ().
template <typename T>
void
swap (self_type& that) YY_NOEXCEPT
{]b4_parse_assert_if([[
]b4_assert[ (yytypeid_);
]b4_assert[ (*yytypeid_ == *that.yytypeid_);]])[
std::swap (as<T> (), that.as<T> ());
}
/// Move the content of \a that to this.
///
/// Destroys \a that.
template <typename T>
void
move (self_type& that)
{
# if 201103L <= YY_CPLUSPLUS
emplace<T> (std::move (that.as<T> ()));
# else
emplace<T> ();
swap<T> (that);
# endif
that.destroy<T> ();
}
# if 201103L <= YY_CPLUSPLUS
/// Move the content of \a that to this.
template <typename T>
void
move (self_type&& that)
{
emplace<T> (std::move (that.as<T> ()));
that.destroy<T> ();
}
#endif
/// Copy the content of \a that to this.
template <typename T>
void
copy (const self_type& that)
{
emplace<T> (that.as<T> ());
}
/// Destroy the stored \a T.
template <typename T>
void
destroy ()
{
as<T> ().~T ();]b4_parse_assert_if([
yytypeid_ = YY_NULLPTR;])[
}
private:
#if YY_CPLUSPLUS < 201103L
/// Non copyable.
value_type (const self_type&);
/// Non copyable.
self_type& operator= (const self_type&);
#endif
/// Accessor to raw memory as \a T.
template <typename T>
T*
yyas_ () YY_NOEXCEPT
{
void *yyp = yyraw_;
return static_cast<T*> (yyp);
}
/// Const accessor to raw memory as \a T.
template <typename T>
const T*
yyas_ () const YY_NOEXCEPT
{
const void *yyp = yyraw_;
return static_cast<const T*> (yyp);
}
/// An auxiliary type to compute the largest semantic type.
union union_type
{]b4_type_foreach([b4_char_sizeof])[ };
/// The size of the largest semantic type.
enum { size = sizeof (union_type) };
/// A buffer to store semantic values.
union
{
/// Strongest alignment constraints.
long double yyalign_me_;
/// A buffer large enough to store any of the semantic values.
char yyraw_[size];
};]b4_parse_assert_if([
/// Whether the content is built: if defined, the name of the stored type.
const std::type_info *yytypeid_;])[
};
]])
# How the semantic value is extracted when using variants.
# b4_symbol_value(VAL, SYMBOL-NUM, [TYPE])
# ----------------------------------------
# See README.
m4_define([b4_symbol_value],
[m4_ifval([$3],
[$1.as< $3 > ()],
[m4_ifval([$2],
[b4_symbol_if([$2], [has_type],
[$1.as < b4_symbol([$2], [type]) > ()],
[$1])],
[$1])])])
# b4_symbol_value_template(VAL, SYMBOL-NUM, [TYPE])
# -------------------------------------------------
# Same as b4_symbol_value, but used in a template method.
m4_define([b4_symbol_value_template],
[m4_ifval([$3],
[$1.template as< $3 > ()],
[m4_ifval([$2],
[b4_symbol_if([$2], [has_type],
[$1.template as < b4_symbol([$2], [type]) > ()],
[$1])],
[$1])])])
## ------------- ##
## make_SYMBOL. ##
## ------------- ##
# _b4_includes_tokens(SYMBOL-NUM...)
# ----------------------------------
# Expands to non-empty iff one of the SYMBOL-NUM denotes
# a token.
m4_define([_b4_is_token],
[b4_symbol_if([$1], [is_token], [1])])
m4_define([_b4_includes_tokens],
[m4_map([_b4_is_token], [$@])])
# _b4_token_maker_define(SYMBOL-NUM)
# ----------------------------------
# Declare make_SYMBOL for SYMBOL-NUM. Use at class-level.
m4_define([_b4_token_maker_define],
[b4_token_visible_if([$1],
[#if 201103L <= YY_CPLUSPLUS
static
symbol_type
make_[]_b4_symbol([$1], [id]) (b4_join(
b4_symbol_if([$1], [has_type],
[b4_symbol([$1], [type]) v]),
b4_locations_if([location_type l])))
{
return symbol_type (b4_join([token::b4_symbol([$1], [id])],
b4_symbol_if([$1], [has_type], [std::move (v)]),
b4_locations_if([std::move (l)])));
}
#else
static
symbol_type
make_[]_b4_symbol([$1], [id]) (b4_join(
b4_symbol_if([$1], [has_type],
[const b4_symbol([$1], [type])& v]),
b4_locations_if([const location_type& l])))
{
return symbol_type (b4_join([token::b4_symbol([$1], [id])],
b4_symbol_if([$1], [has_type], [v]),
b4_locations_if([l])));
}
#endif
])])
# b4_token_kind(SYMBOL-NUM)
# -------------------------
# Some tokens don't have an ID.
m4_define([b4_token_kind],
[b4_symbol_if([$1], [has_id],
[token::b4_symbol([$1], [id])],
[b4_symbol([$1], [code])])])
# _b4_tok_in(SYMBOL-NUM, ...)
# ---------------------------
# See b4_tok_in below. The SYMBOL-NUMs... are tokens only.
#
# We iterate over the tokens to group them by "range" of token numbers (not
# symbols numbers!).
#
# b4_fst is the start of that range.
# b4_prev is the previous value.
# b4_val is the current value.
# If b4_val is the successor of b4_prev in token numbers, update the latter,
# otherwise emit the code for range b4_fst .. b4_prev.
# $1 is also used as a terminator in the foreach, but it will not be printed.
#
m4_define([_b4_tok_in],
[m4_pushdef([b4_prev], [$1])dnl
m4_pushdef([b4_fst], [$1])dnl
m4_pushdef([b4_sep], [])dnl
m4_foreach([b4_val], m4_dquote(m4_shift($@, $1)),
[m4_if(b4_symbol(b4_val, [code]), m4_eval(b4_symbol(b4_prev, [code]) + 1), [],
[b4_sep[]m4_if(b4_fst, b4_prev,
[tok == b4_token_kind(b4_fst)],
[(b4_token_kind(b4_fst) <= tok && tok <= b4_token_kind(b4_prev))])[]dnl
m4_define([b4_fst], b4_val)dnl
m4_define([b4_sep], [
|| ])])dnl
m4_define([b4_prev], b4_val)])dnl
m4_popdef([b4_sep])dnl
m4_popdef([b4_fst])dnl
m4_popdef([b4_prev])dnl
])
# _b4_filter_tokens(SYMBOL-NUM, ...)
# ----------------------------------
# Expand as the list of tokens amongst SYMBOL-NUM.
m4_define([_b4_filter_tokens],
[m4_pushdef([b4_sep])dnl
m4_foreach([b4_val], [$@],
[b4_symbol_if(b4_val, [is_token], [b4_sep[]b4_val[]m4_define([b4_sep], [,])])])dnl
m4_popdef([b4_sep])dnl
])
# b4_tok_in(SYMBOL-NUM, ...)
# ---------------------------
# A C++ conditional that checks that `tok` is a member of this list of symbol
# numbers.
m4_define([b4_tok_in],
[_$0(_b4_filter_tokens($@))])
# _b4_symbol_constructor_define(SYMBOL-NUM...)
# --------------------------------------------
# Define a symbol_type constructor common to all the SYMBOL-NUM (they
# have the same type). Use at class-level.
m4_define([_b4_symbol_constructor_define],
[m4_ifval(_b4_includes_tokens($@),
[[#if 201103L <= YY_CPLUSPLUS
symbol_type (]b4_join(
[int tok],
b4_symbol_if([$1], [has_type],
[b4_symbol([$1], [type]) v]),
b4_locations_if([location_type l]))[)
: super_type (]b4_join([token_kind_type (tok)],
b4_symbol_if([$1], [has_type], [std::move (v)]),
b4_locations_if([std::move (l)]))[)
#else
symbol_type (]b4_join(
[int tok],
b4_symbol_if([$1], [has_type],
[const b4_symbol([$1], [type])& v]),
b4_locations_if([const location_type& l]))[)
: super_type (]b4_join([token_kind_type (tok)],
b4_symbol_if([$1], [has_type], [v]),
b4_locations_if([l]))[)
#endif
{]b4_parse_assert_if([[
#if !defined _MSC_VER || defined __clang__
]b4_assert[ (]b4_tok_in($@)[);
#endif
]])[}
]])])
# b4_basic_symbol_constructor_define(SYMBOL-NUM)
# ----------------------------------------------
# Generate a constructor for basic_symbol from given type.
m4_define([b4_basic_symbol_constructor_define],
[[#if 201103L <= YY_CPLUSPLUS
basic_symbol (]b4_join(
[typename Base::kind_type t],
b4_symbol_if([$1], [has_type], [b4_symbol([$1], [type])&& v]),
b4_locations_if([location_type&& l]))[)
: Base (t)]b4_symbol_if([$1], [has_type], [
, value (std::move (v))])[]b4_locations_if([
, location (std::move (l))])[
{}
#else
basic_symbol (]b4_join(
[typename Base::kind_type t],
b4_symbol_if([$1], [has_type], [const b4_symbol([$1], [type])& v]),
b4_locations_if([const location_type& l]))[)
: Base (t)]b4_symbol_if([$1], [has_type], [
, value (v)])[]b4_locations_if([
, location (l)])[
{}
#endif
]])
# b4_token_constructor_define
# ---------------------------
# Define the overloaded versions of make_FOO for all the token kinds.
m4_define([b4_token_constructor_define],
[ // Implementation of make_symbol for each token kind.
b4_symbol_foreach([_b4_token_maker_define])])

2209
tools/winflexbison/data/skeletons/yacc.c
File diff suppressed because it is too large
View File

105
tools/winflexbison/data/xslt/bison.xsl

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
bison.xsl - common templates for Bison XSLT.
Copyright (C) 2007-2015, 2018-2021 Free Software Foundation, Inc.
This file is part of Bison, the GNU Compiler Compiler.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:bison="https://www.gnu.org/software/bison/">
<xsl:key
name="bison:symbolByName"
match="/bison-xml-report/grammar/nonterminals/nonterminal"
use="@name"
/>
<xsl:key
name="bison:symbolByName"
match="/bison-xml-report/grammar/terminals/terminal"
use="@name"
/>
<xsl:key
name="bison:ruleByNumber"
match="/bison-xml-report/grammar/rules/rule"
use="@number"
/>
<xsl:key
name="bison:ruleByLhs"
match="/bison-xml-report/grammar/rules/rule[
@usefulness != 'useless-in-grammar']"
use="lhs"
/>
<xsl:key
name="bison:ruleByRhs"
match="/bison-xml-report/grammar/rules/rule[
@usefulness != 'useless-in-grammar']"
use="rhs/symbol"
/>
<!-- For the specified state, output: #sr-conflicts,#rr-conflicts -->
<xsl:template match="state" mode="bison:count-conflicts">
<xsl:variable name="transitions" select="actions/transitions"/>
<xsl:variable name="reductions" select="actions/reductions"/>
<xsl:variable
name="terminals"
select="
$transitions/transition[@type='shift']/@symbol
| $reductions/reduction/@symbol
"
/>
<xsl:variable name="conflict-data">
<xsl:for-each select="$terminals">
<xsl:variable name="name" select="."/>
<xsl:if test="generate-id($terminals[. = $name][1]) = generate-id(.)">
<xsl:variable
name="shift-count"
select="count($transitions/transition[@symbol=$name])"
/>
<xsl:variable
name="reduce-count"
select="count($reductions/reduction[@symbol=$name])"
/>
<xsl:if test="$shift-count > 0 and $reduce-count > 0">
<xsl:text>s</xsl:text>
</xsl:if>
<xsl:if test="$reduce-count > 1">
<xsl:text>r</xsl:text>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="string-length(translate($conflict-data, 'r', ''))"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="string-length(translate($conflict-data, 's', ''))"/>
</xsl:template>
<xsl:template name="space">
<xsl:param name="repeat">0</xsl:param>
<xsl:param name="fill" select="' '"/>
<xsl:if test="number($repeat) &gt;= 1">
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$repeat - 1"/>
<xsl:with-param name="fill" select="$fill"/>
</xsl:call-template>
<xsl:value-of select="$fill"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

401
tools/winflexbison/data/xslt/xml2dot.xsl

@ -0,0 +1,401 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
xml2dot.xsl - transform Bison XML Report into DOT.
Copyright (C) 2007-2015, 2018-2021 Free Software Foundation, Inc.
This file is part of Bison, the GNU Compiler Compiler.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Written by Wojciech Polak <polak@gnu.org>.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:bison="https://www.gnu.org/software/bison/">
<xsl:import href="bison.xsl"/>
<xsl:output method="text" encoding="UTF-8" indent="no"/>
<xsl:template match="/">
<xsl:apply-templates select="bison-xml-report"/>
</xsl:template>
<xsl:template match="bison-xml-report">
<xsl:text>// Generated by GNU Bison </xsl:text>
<xsl:value-of select="@version"/>
<xsl:text>.&#10;</xsl:text>
<xsl:text>// Report bugs to &lt;</xsl:text>
<xsl:value-of select="@bug-report"/>
<xsl:text>&gt;.&#10;</xsl:text>
<xsl:text>// Home page: &lt;</xsl:text>
<xsl:value-of select="@url"/>
<xsl:text>&gt;.&#10;&#10;</xsl:text>
<xsl:apply-templates select="automaton">
<xsl:with-param name="filename" select="filename"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="automaton">
<xsl:param name="filename"/>
<xsl:text>digraph "</xsl:text>
<xsl:call-template name="escape">
<xsl:with-param name="subject" select="$filename"/>
</xsl:call-template>
<xsl:text>"&#10;{
node [fontname = courier, shape = box, colorscheme = paired6]
edge [fontname = courier]
</xsl:text>
<xsl:apply-templates select="state"/>
<xsl:text>}&#10;</xsl:text>
</xsl:template>
<xsl:template match="automaton/state">
<xsl:call-template name="output-node">
<xsl:with-param name="number" select="@number"/>
<xsl:with-param name="label">
<xsl:apply-templates select="itemset/item"/>
</xsl:with-param>
</xsl:call-template>
<xsl:apply-templates select="actions/transitions"/>
<xsl:apply-templates select="actions/reductions">
<xsl:with-param name="staten">
<xsl:value-of select="@number"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="actions/reductions">
<xsl:param name="staten"/>
<xsl:for-each select='reduction'>
<!-- These variables are needed because the current context can't be
referred to directly in XPath expressions. -->
<xsl:variable name="rul">
<xsl:value-of select="@rule"/>
</xsl:variable>
<xsl:variable name="ena">
<xsl:value-of select="@enabled"/>
</xsl:variable>
<!-- The foreach's body is protected by this, so that we are actually
going to iterate once per reduction rule, and not per lookahead. -->
<xsl:if test='not(preceding-sibling::*[@rule=$rul and @enabled=$ena])'>
<xsl:variable name="rule">
<xsl:choose>
<!-- The acceptation state is referred to as 'accept' in the XML, but
just as '0' in the DOT. -->
<xsl:when test="@rule='accept'">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@rule"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- The edge's beginning -->
<xsl:call-template name="reduction-edge-start">
<xsl:with-param name="state" select="$staten"/>
<xsl:with-param name="rule" select="$rule"/>
<xsl:with-param name="enabled" select="@enabled"/>
</xsl:call-template>
<!-- The edge's tokens -->
<!-- Don't show labels for the default action. In other cases, there will
always be at least one token, so 'label="[]"' will not occur. -->
<xsl:if test='$rule!=0 and not(../reduction[@enabled=$ena and @rule=$rule and @symbol="$default"])'>
<xsl:text>label="[</xsl:text>
<xsl:for-each select='../reduction[@enabled=$ena and @rule=$rule]'>
<xsl:call-template name="escape">
<xsl:with-param name="subject" select="@symbol"/>
</xsl:call-template>
<xsl:if test="position() != last ()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>]", </xsl:text>
</xsl:if>
<!-- The edge's end -->
<xsl:text>style=solid]&#10;</xsl:text>
<!-- The diamond representing the reduction -->
<xsl:call-template name="reduction-node">
<xsl:with-param name="state" select="$staten"/>
<xsl:with-param name="rule" select="$rule"/>
<xsl:with-param name="color">
<xsl:choose>
<xsl:when test='@enabled="true"'>
<xsl:text>3</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>5</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="actions/transitions">
<xsl:apply-templates select="transition"/>
</xsl:template>
<xsl:template match="item">
<xsl:param name="prev-rule-number"
select="preceding-sibling::item[1]/@rule-number"/>
<xsl:apply-templates select="key('bison:ruleByNumber', @rule-number)">
<xsl:with-param name="dot" select="@dot"/>
<xsl:with-param name="num" select="@rule-number"/>
<xsl:with-param name="prev-lhs"
select="key('bison:ruleByNumber', $prev-rule-number)/lhs[text()]"
/>
</xsl:apply-templates>
<xsl:apply-templates select="lookaheads"/>
</xsl:template>
<xsl:template match="rule">
<xsl:param name="dot"/>
<xsl:param name="num"/>
<xsl:param name="prev-lhs"/>
<xsl:text>&#10;</xsl:text>
<xsl:choose>
<xsl:when test="$num &lt; 10">
<xsl:text> </xsl:text>
</xsl:when>
<xsl:when test="$num &lt; 100">
<xsl:text> </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text></xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="$num"/>
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="$prev-lhs = lhs[text()]">
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="'|'"/>
<xsl:with-param name="pad" select="number(string-length(lhs[text()])) + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="lhs"/>
<xsl:text>:</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$dot = 0">
<xsl:text> .</xsl:text>
</xsl:if>
<!-- RHS -->
<xsl:for-each select="rhs/symbol|rhs/empty">
<xsl:apply-templates select="."/>
<xsl:if test="$dot = position()">
<xsl:text> .</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="symbol">
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="empty">
<xsl:text> %empty</xsl:text>
</xsl:template>
<xsl:template match="lookaheads">
<xsl:text> [</xsl:text>
<xsl:apply-templates select="symbol"/>
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="lookaheads/symbol">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template name="reduction-edge-start">
<xsl:param name="state"/>
<xsl:param name="rule"/>
<xsl:param name="enabled"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$state"/>
<xsl:text> -> "</xsl:text>
<xsl:value-of select="$state"/>
<xsl:text>R</xsl:text>
<xsl:value-of select="$rule"/>
<xsl:if test='$enabled = "false"'>
<xsl:text>d</xsl:text>
</xsl:if>
<xsl:text>" [</xsl:text>
</xsl:template>
<xsl:template name="reduction-node">
<xsl:param name="state"/>
<xsl:param name="rule"/>
<xsl:param name="color"/>
<xsl:text> "</xsl:text>
<xsl:value-of select="$state"/>
<xsl:text>R</xsl:text>
<xsl:value-of select="$rule"/>
<xsl:if test="$color = 5">
<xsl:text>d</xsl:text>
</xsl:if>
<xsl:text>" [label="</xsl:text>
<xsl:choose>
<xsl:when test="$rule = 0">
<xsl:text>Acc", fillcolor=1</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>R</xsl:text>
<xsl:value-of select="$rule"/>
<xsl:text>", fillcolor=</xsl:text>
<xsl:value-of select="$color"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text>, shape=diamond, style=filled]&#10;</xsl:text>
</xsl:template>
<xsl:template match="transition">
<xsl:call-template name="output-edge">
<xsl:with-param name="src" select="../../../@number"/>
<xsl:with-param name="dst" select="@state"/>
<xsl:with-param name="style">
<xsl:choose>
<xsl:when test="@symbol = 'error'">
<xsl:text>dotted</xsl:text>
</xsl:when>
<xsl:when test="@type = 'shift'">
<xsl:text>solid</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>dashed</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
<xsl:with-param name="label">
<xsl:if test="not(@symbol = 'error')">
<xsl:value-of select="@symbol"/>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="output-node">
<xsl:param name="number"/>
<xsl:param name="label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$number"/>
<xsl:text> [label="</xsl:text>
<xsl:text>State </xsl:text>
<xsl:value-of select="$number"/>
<xsl:text>\n</xsl:text>
<xsl:call-template name="escape">
<xsl:with-param name="subject" select="$label"/>
</xsl:call-template>
<xsl:text>\l"]&#10;</xsl:text>
</xsl:template>
<xsl:template name="output-edge">
<xsl:param name="src"/>
<xsl:param name="dst"/>
<xsl:param name="style"/>
<xsl:param name="label"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$src"/>
<xsl:text> -> </xsl:text>
<xsl:value-of select="$dst"/>
<xsl:text> [style=</xsl:text>
<xsl:value-of select="$style"/>
<xsl:if test="$label and $label != ''">
<xsl:text> label="</xsl:text>
<xsl:call-template name="escape">
<xsl:with-param name="subject" select="$label"/>
</xsl:call-template>
<xsl:text>"</xsl:text>
</xsl:if>
<xsl:text>]&#10;</xsl:text>
</xsl:template>
<xsl:template name="escape">
<xsl:param name="subject"/> <!-- required -->
<xsl:call-template name="string-replace">
<xsl:with-param name="subject">
<xsl:call-template name="string-replace">
<xsl:with-param name="subject">
<xsl:call-template name="string-replace">
<xsl:with-param name="subject" select="$subject"/>
<xsl:with-param name="search" select="'\'"/>
<xsl:with-param name="replace" select="'\\'"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="search" select="'&quot;'"/>
<xsl:with-param name="replace" select="'\&quot;'"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="search" select="'&#10;'"/>
<xsl:with-param name="replace" select="'\l'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="string-replace">
<xsl:param name="subject"/>
<xsl:param name="search"/>
<xsl:param name="replace"/>
<xsl:choose>
<xsl:when test="contains($subject, $search)">
<xsl:variable name="before" select="substring-before($subject, $search)"/>
<xsl:variable name="after" select="substring-after($subject, $search)"/>
<xsl:value-of select="$before"/>
<xsl:value-of select="$replace"/>
<xsl:call-template name="string-replace">
<xsl:with-param name="subject" select="$after"/>
<xsl:with-param name="search" select="$search"/>
<xsl:with-param name="replace" select="$replace"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$subject"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="lpad">
<xsl:param name="str" select="''"/>
<xsl:param name="pad" select="0"/>
<xsl:variable name="diff" select="$pad - string-length($str)" />
<xsl:choose>
<xsl:when test="$diff &lt; 0">
<xsl:value-of select="$str"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$diff"/>
</xsl:call-template>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

572
tools/winflexbison/data/xslt/xml2text.xsl

@ -0,0 +1,572 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
xml2text.xsl - transform Bison XML Report into plain text.
Copyright (C) 2007-2015, 2018-2021 Free Software Foundation, Inc.
This file is part of Bison, the GNU Compiler Compiler.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Written by Wojciech Polak <polak@gnu.org>.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:bison="https://www.gnu.org/software/bison/">
<xsl:import href="bison.xsl"/>
<xsl:output method="text" encoding="UTF-8" indent="no"/>
<xsl:template match="/">
<xsl:apply-templates select="bison-xml-report"/>
</xsl:template>
<xsl:template match="bison-xml-report">
<xsl:apply-templates select="grammar" mode="reductions"/>
<xsl:apply-templates select="grammar" mode="useless-in-parser"/>
<xsl:apply-templates select="automaton" mode="conflicts"/>
<xsl:apply-templates select="grammar"/>
<xsl:apply-templates select="automaton"/>
</xsl:template>
<xsl:template match="grammar" mode="reductions">
<xsl:apply-templates select="nonterminals" mode="useless-in-grammar"/>
<xsl:apply-templates select="terminals" mode="unused-in-grammar"/>
<xsl:apply-templates select="rules" mode="useless-in-grammar"/>
</xsl:template>
<xsl:template match="nonterminals" mode="useless-in-grammar">
<xsl:if test="nonterminal[@usefulness='useless-in-grammar']">
<xsl:text>Nonterminals useless in grammar&#10;&#10;</xsl:text>
<xsl:for-each select="nonterminal[@usefulness='useless-in-grammar']">
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="terminals" mode="unused-in-grammar">
<xsl:if test="terminal[@usefulness='unused-in-grammar']">
<xsl:text>Terminals unused in grammar&#10;&#10;</xsl:text>
<xsl:for-each select="terminal[@usefulness='unused-in-grammar']">
<xsl:sort select="@symbol-number" data-type="number"/>
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="rules" mode="useless-in-grammar">
<xsl:variable name="set" select="rule[@usefulness='useless-in-grammar']"/>
<xsl:if test="$set">
<xsl:text>Rules useless in grammar&#10;</xsl:text>
<xsl:call-template name="style-rule-set">
<xsl:with-param name="rule-set" select="$set"/>
</xsl:call-template>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="grammar" mode="useless-in-parser">
<xsl:variable
name="set" select="rules/rule[@usefulness='useless-in-parser']"
/>
<xsl:if test="$set">
<xsl:text>Rules useless in parser due to conflicts&#10;</xsl:text>
<xsl:call-template name="style-rule-set">
<xsl:with-param name="rule-set" select="$set"/>
</xsl:call-template>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="grammar">
<xsl:text>Grammar&#10;</xsl:text>
<xsl:call-template name="style-rule-set">
<xsl:with-param
name="rule-set" select="rules/rule[@usefulness!='useless-in-grammar']"
/>
</xsl:call-template>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:apply-templates select="terminals"/>
<xsl:apply-templates select="nonterminals"/>
</xsl:template>
<xsl:template name="style-rule-set">
<xsl:param name="rule-set"/>
<xsl:for-each select="$rule-set">
<xsl:apply-templates select=".">
<xsl:with-param name="pad" select="'3'"/>
<xsl:with-param name="prev-lhs">
<xsl:if test="position()>1">
<xsl:variable name="position" select="position()"/>
<xsl:value-of select="$rule-set[$position - 1]/lhs"/>
</xsl:if>
</xsl:with-param>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="grammar/terminals">
<xsl:text>Terminals, with rules where they appear&#10;&#10;</xsl:text>
<xsl:apply-templates select="terminal"/>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:template>
<xsl:template match="grammar/nonterminals">
<xsl:text>Nonterminals, with rules where they appear&#10;&#10;</xsl:text>
<xsl:apply-templates select="nonterminal[@usefulness!='useless-in-grammar']"/>
</xsl:template>
<xsl:template match="terminal">
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:call-template name="line-wrap">
<xsl:with-param name="first-line-length">
<xsl:choose>
<xsl:when test="string-length(@name) &gt; 66">0</xsl:when>
<xsl:otherwise>
<xsl:value-of select="66 - string-length(@name)" />
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
<xsl:with-param name="line-length" select="66" />
<xsl:with-param name="text">
<xsl:if test="string-length(@type) != 0">
<xsl:value-of select="concat(' &lt;', @type, '&gt;')"/>
</xsl:if>
<xsl:value-of select="concat(' (', @token-number, ')')"/>
<xsl:for-each select="key('bison:ruleByRhs', @name)">
<xsl:value-of select="concat(' ', @number)"/>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="nonterminal">
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:if test="string-length(@type) != 0">
<xsl:value-of select="concat(' &lt;', @type, '&gt;')"/>
</xsl:if>
<xsl:value-of select="concat(' (', @symbol-number, ')')"/>
<xsl:text>&#10;</xsl:text>
<xsl:variable name="output">
<xsl:call-template name="line-wrap">
<xsl:with-param name="line-length" select="66" />
<xsl:with-param name="text">
<xsl:text> </xsl:text>
<xsl:if test="key('bison:ruleByLhs', @name)">
<xsl:text>on@left:</xsl:text>
<xsl:for-each select="key('bison:ruleByLhs', @name)">
<xsl:value-of select="concat(' ', @number)"/>
</xsl:for-each>
</xsl:if>
<xsl:if test="key('bison:ruleByRhs', @name)">
<xsl:if test="key('bison:ruleByLhs', @name)">
<xsl:text>&#10; </xsl:text>
</xsl:if>
<xsl:text>on@right:</xsl:text>
<xsl:for-each select="key('bison:ruleByRhs', @name)">
<xsl:value-of select="concat(' ', @number)"/>
</xsl:for-each>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="translate($output, '@', ' ')" />
</xsl:template>
<xsl:template match="automaton" mode="conflicts">
<xsl:variable name="conflict-report">
<xsl:apply-templates select="state" mode="conflicts"/>
</xsl:variable>
<xsl:if test="string-length($conflict-report) != 0">
<xsl:value-of select="$conflict-report"/>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="state" mode="conflicts">
<xsl:variable name="conflict-counts">
<xsl:apply-templates select="." mode="bison:count-conflicts" />
</xsl:variable>
<xsl:variable
name="sr-count" select="substring-before($conflict-counts, ',')"
/>
<xsl:variable
name="rr-count" select="substring-after($conflict-counts, ',')"
/>
<xsl:if test="$sr-count > 0 or $rr-count > 0">
<xsl:value-of select="concat('State ', @number, ' conflicts:')"/>
<xsl:if test="$sr-count > 0">
<xsl:value-of select="concat(' ', $sr-count, ' shift/reduce')"/>
<xsl:if test="$rr-count > 0">
<xsl:value-of select="(',')"/>
</xsl:if>
</xsl:if>
<xsl:if test="$rr-count > 0">
<xsl:value-of select="concat(' ', $rr-count, ' reduce/reduce')"/>
</xsl:if>
<xsl:value-of select="'&#10;'"/>
</xsl:if>
</xsl:template>
<xsl:template match="automaton">
<xsl:apply-templates select="state">
<xsl:with-param name="pad" select="'3'"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="automaton/state">
<xsl:param name="pad"/>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:text>State </xsl:text>
<xsl:value-of select="@number"/>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:apply-templates select="itemset/item">
<xsl:with-param name="pad" select="$pad"/>
</xsl:apply-templates>
<xsl:apply-templates select="actions/transitions">
<xsl:with-param name="type" select="'shift'"/>
</xsl:apply-templates>
<xsl:apply-templates select="actions/errors"/>
<xsl:apply-templates select="actions/reductions"/>
<xsl:apply-templates select="actions/transitions">
<xsl:with-param name="type" select="'goto'"/>
</xsl:apply-templates>
<xsl:apply-templates select="solved-conflicts"/>
</xsl:template>
<xsl:template match="actions/transitions">
<xsl:param name="type"/>
<xsl:if test="transition[@type = $type]">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="transition[@type = $type]">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="transition[@type = $type]"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="actions/errors">
<xsl:if test="error">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="error">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="error"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="actions/reductions">
<xsl:if test="reduction">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="reduction">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="reduction"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="item">
<xsl:param name="pad"/>
<xsl:param name="prev-rule-number"
select="preceding-sibling::item[1]/@rule-number"/>
<xsl:apply-templates
select="key('bison:ruleByNumber', current()/@rule-number)"
>
<xsl:with-param name="itemset" select="'true'"/>
<xsl:with-param name="pad" select="$pad"/>
<xsl:with-param
name="prev-lhs"
select="key('bison:ruleByNumber', $prev-rule-number)/lhs[text()]"
/>
<xsl:with-param name="dot" select="@dot"/>
<xsl:with-param name="lookaheads">
<xsl:apply-templates select="lookaheads"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="rule">
<xsl:param name="itemset"/>
<xsl:param name="pad"/>
<xsl:param name="prev-lhs"/>
<xsl:param name="dot"/>
<xsl:param name="lookaheads"/>
<xsl:if test="$itemset != 'true' and not($prev-lhs = lhs[text()])">
<xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:text> </xsl:text>
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="string(@number)"/>
<xsl:with-param name="pad" select="number($pad)"/>
</xsl:call-template>
<xsl:text> </xsl:text>
<!-- LHS -->
<xsl:choose>
<xsl:when test="$itemset != 'true' and $prev-lhs = lhs[text()]">
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="'|'"/>
<xsl:with-param name="pad" select="number(string-length(lhs[text()])) + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$itemset = 'true' and $prev-lhs = lhs[text()]">
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="'|'"/>
<xsl:with-param name="pad" select="number(string-length(lhs[text()])) + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="lhs"/>
<xsl:text>:</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- RHS -->
<xsl:for-each select="rhs/*">
<xsl:if test="position() = $dot + 1">
<xsl:text></xsl:text>
</xsl:if>
<xsl:apply-templates select="."/>
<xsl:if test="position() = last() and position() = $dot">
<xsl:text></xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:if test="$lookaheads">
<xsl:value-of select="$lookaheads"/>
</xsl:if>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="symbol">
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="empty">
<xsl:text> %empty</xsl:text>
</xsl:template>
<xsl:template match="lookaheads">
<xsl:text> [</xsl:text>
<xsl:apply-templates select="symbol"/>
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="lookaheads/symbol">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="transition">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:choose>
<xsl:when test="@type = 'shift'">
<xsl:text>shift, and go to state </xsl:text>
<xsl:value-of select="@state"/>
</xsl:when>
<xsl:when test="@type = 'goto'">
<xsl:text>go to state </xsl:text>
<xsl:value-of select="@state"/>
</xsl:when>
</xsl:choose>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="error">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:text>error</xsl:text>
<xsl:text> (</xsl:text>
<xsl:value-of select="text()"/>
<xsl:text>)</xsl:text>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="reduction">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:if test="@enabled = 'false'">
<xsl:text>[</xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="@rule = 'accept'">
<xsl:text>accept</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>reduce using rule </xsl:text>
<xsl:value-of select="@rule"/>
<xsl:text> (</xsl:text>
<xsl:value-of
select="key('bison:ruleByNumber', current()/@rule)/lhs[text()]"/>
<xsl:text>)</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@enabled = 'false'">
<xsl:text>]</xsl:text>
</xsl:if>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="solved-conflicts">
<xsl:if test="resolution">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="resolution"/>
</xsl:if>
</xsl:template>
<xsl:template match="resolution">
<xsl:text> Conflict between rule </xsl:text>
<xsl:value-of select="@rule"/>
<xsl:text> and token </xsl:text>
<xsl:value-of select="@symbol"/>
<xsl:text> resolved as </xsl:text>
<xsl:if test="@type = 'error'">
<xsl:text>an </xsl:text>
</xsl:if>
<xsl:value-of select="@type"/>
<xsl:text> (</xsl:text>
<xsl:value-of select="."/>
<xsl:text>).&#10;</xsl:text>
</xsl:template>
<xsl:template name="max-width-symbol">
<xsl:param name="node"/>
<xsl:variable name="longest">
<xsl:for-each select="$node">
<xsl:sort data-type="number" select="string-length(@symbol)"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="string-length(@symbol)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$longest"/>
</xsl:template>
<xsl:template name="lpad">
<xsl:param name="str" select="''"/>
<xsl:param name="pad" select="0"/>
<xsl:variable name="diff" select="$pad - string-length($str)" />
<xsl:choose>
<xsl:when test="$diff &lt; 0">
<xsl:value-of select="$str"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$diff"/>
</xsl:call-template>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="rpad">
<xsl:param name="str" select="''"/>
<xsl:param name="pad" select="0"/>
<xsl:variable name="diff" select="$pad - string-length($str)"/>
<xsl:choose>
<xsl:when test="$diff &lt; 0">
<xsl:value-of select="$str"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$diff"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="line-wrap">
<xsl:param name="line-length"/> <!-- required -->
<xsl:param name="first-line-length" select="$line-length"/>
<xsl:param name="text"/> <!-- required -->
<xsl:choose>
<xsl:when test="normalize-space($text) = ''" />
<xsl:when test="string-length($text) &lt;= $first-line-length">
<xsl:value-of select="concat($text, '&#10;')" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="break-pos">
<xsl:call-template name="ws-search">
<xsl:with-param name="text" select="$text" />
<xsl:with-param name="start" select="$first-line-length+1" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="substring($text, 1, $break-pos - 1)" />
<xsl:text>&#10;</xsl:text>
<xsl:call-template name="line-wrap">
<xsl:with-param name="line-length" select="$line-length" />
<xsl:with-param
name="text" select="concat(' ', substring($text, $break-pos+1))"
/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ws-search">
<xsl:param name="text"/> <!-- required -->
<xsl:param name="start"/> <!-- required -->
<xsl:variable name="search-text" select="substring($text, $start)" />
<xsl:choose>
<xsl:when test="not(contains($search-text, ' '))">
<xsl:value-of select="string-length($text)+1" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="$start + string-length(substring-before($search-text, ' '))"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

765
tools/winflexbison/data/xslt/xml2xhtml.xsl

@ -0,0 +1,765 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
xml2html.xsl - transform Bison XML Report into XHTML.
Copyright (C) 2007-2015, 2018-2021 Free Software Foundation, Inc.
This file is part of Bison, the GNU Compiler Compiler.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Written by Wojciech Polak <polak@gnu.org>.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:bison="https://www.gnu.org/software/bison/">
<xsl:import href="bison.xsl"/>
<xsl:output method="xml" encoding="UTF-8"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
indent="yes"/>
<xsl:template match="/">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>
<xsl:value-of select="bison-xml-report/filename"/>
<xsl:text> - GNU Bison XML Automaton Report</xsl:text>
</title>
<style type="text/css"><![CDATA[
body {
font-family: "Nimbus Sans L", Arial, sans-serif;
font-size: 9pt;
}
a:link {
color: #1f00ff;
text-decoration: none;
}
a:visited {
color: #1f00ff;
text-decoration: none;
}
a:hover {
color: red;
}
#menu a {
text-decoration: underline;
}
.i {
font-style: italic;
}
.pre {
font-family: monospace;
white-space: pre;
}
ol.decimal {
list-style-type: decimal;
}
ol.lower-alpha {
list-style-type: lower-alpha;
}
.dot {
color: #cc0000;
}
#footer {
margin-top: 3.5em;
font-size: 7pt;
}
]]></style>
</head>
<body>
<xsl:apply-templates select="bison-xml-report"/>
<xsl:text>&#10;&#10;</xsl:text>
<div id="footer"><hr />This document was generated using
<a href="https://www.gnu.org/software/bison/" title="GNU Bison">
GNU Bison <xsl:value-of select="/bison-xml-report/@version"/></a>
XML Automaton Report.<br />
<!-- default copying notice -->
Verbatim copying and distribution of this entire page is
permitted in any medium, provided this notice is preserved.</div>
</body>
</html>
</xsl:template>
<xsl:template match="bison-xml-report">
<h1>GNU Bison XML Automaton Report</h1>
<p>
input grammar: <span class="i"><xsl:value-of select="filename"/></span>
</p>
<xsl:text>&#10;&#10;</xsl:text>
<h3>Table of Contents</h3>
<ul id="menu">
<li>
<a href="#reductions">Reductions</a>
<ul class="lower-alpha">
<li><a href="#nonterminals_useless_in_grammar">Nonterminals useless in grammar</a></li>
<li><a href="#terminals_unused_in_grammar">Terminals unused in grammar</a></li>
<li><a href="#rules_useless_in_grammar">Rules useless in grammar</a></li>
<xsl:if test="grammar/rules/rule[@usefulness='useless-in-parser']">
<li><a href="#rules_useless_in_parser">Rules useless in parser due to conflicts</a></li>
</xsl:if>
</ul>
</li>
<li><a href="#conflicts">Conflicts</a></li>
<li>
<a href="#grammar">Grammar</a>
<ul class="lower-alpha">
<li><a href="#grammar">Itemset</a></li>
<li><a href="#terminals">Terminal symbols</a></li>
<li><a href="#nonterminals">Nonterminal symbols</a></li>
</ul>
</li>
<li><a href="#automaton">Automaton</a></li>
</ul>
<xsl:apply-templates select="grammar" mode="reductions"/>
<xsl:apply-templates select="grammar" mode="useless-in-parser"/>
<xsl:apply-templates select="automaton" mode="conflicts"/>
<xsl:apply-templates select="grammar"/>
<xsl:apply-templates select="automaton"/>
</xsl:template>
<xsl:template match="grammar" mode="reductions">
<h2>
<a name="reductions"/>
<xsl:text> Reductions</xsl:text>
</h2>
<xsl:apply-templates select="nonterminals" mode="useless-in-grammar"/>
<xsl:apply-templates select="terminals" mode="unused-in-grammar"/>
<xsl:apply-templates select="rules" mode="useless-in-grammar"/>
</xsl:template>
<xsl:template match="nonterminals" mode="useless-in-grammar">
<h3>
<a name="nonterminals_useless_in_grammar"/>
<xsl:text> Nonterminals useless in grammar</xsl:text>
</h3>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:if test="nonterminal[@usefulness='useless-in-grammar']">
<p class="pre">
<xsl:for-each select="nonterminal[@usefulness='useless-in-grammar']">
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#10;&#10;</xsl:text>
</p>
</xsl:if>
</xsl:template>
<xsl:template match="terminals" mode="unused-in-grammar">
<h3>
<a name="terminals_unused_in_grammar"/>
<xsl:text> Terminals unused in grammar</xsl:text>
</h3>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:if test="terminal[@usefulness='unused-in-grammar']">
<p class="pre">
<xsl:for-each select="terminal[@usefulness='unused-in-grammar']">
<xsl:sort select="@symbol-number" data-type="number"/>
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>&#10;</xsl:text>
</xsl:for-each>
<xsl:text>&#10;&#10;</xsl:text>
</p>
</xsl:if>
</xsl:template>
<xsl:template match="rules" mode="useless-in-grammar">
<h3>
<a name="rules_useless_in_grammar"/>
<xsl:text> Rules useless in grammar</xsl:text>
</h3>
<xsl:text>&#10;</xsl:text>
<xsl:variable name="set" select="rule[@usefulness='useless-in-grammar']"/>
<xsl:if test="$set">
<p class="pre">
<xsl:call-template name="style-rule-set">
<xsl:with-param name="rule-set" select="$set"/>
</xsl:call-template>
<xsl:text>&#10;&#10;</xsl:text>
</p>
</xsl:if>
</xsl:template>
<xsl:template match="grammar" mode="useless-in-parser">
<xsl:variable
name="set" select="rules/rule[@usefulness='useless-in-parser']"
/>
<xsl:if test="$set">
<h2>
<a name="rules_useless_in_parser"/>
<xsl:text> Rules useless in parser due to conflicts</xsl:text>
</h2>
<xsl:text>&#10;</xsl:text>
<p class="pre">
<xsl:call-template name="style-rule-set">
<xsl:with-param name="rule-set" select="$set"/>
</xsl:call-template>
</p>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="grammar">
<h2>
<a name="grammar"/>
<xsl:text> Grammar</xsl:text>
</h2>
<xsl:text>&#10;</xsl:text>
<p class="pre">
<xsl:call-template name="style-rule-set">
<xsl:with-param name="anchor" select="'true'" />
<xsl:with-param
name="rule-set" select="rules/rule[@usefulness!='useless-in-grammar']"
/>
</xsl:call-template>
</p>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:apply-templates select="terminals"/>
<xsl:apply-templates select="nonterminals"/>
</xsl:template>
<xsl:template name="style-rule-set">
<xsl:param name="anchor"/>
<xsl:param name="rule-set"/>
<xsl:for-each select="$rule-set">
<xsl:apply-templates select=".">
<xsl:with-param name="anchor" select="$anchor"/>
<xsl:with-param name="pad" select="'3'"/>
<xsl:with-param name="prev-lhs">
<xsl:if test="position()>1">
<xsl:variable name="position" select="position()"/>
<xsl:value-of select="$rule-set[$position - 1]/lhs"/>
</xsl:if>
</xsl:with-param>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="automaton" mode="conflicts">
<h2>
<a name="conflicts"/>
<xsl:text> Conflicts</xsl:text>
</h2>
<xsl:text>&#10;&#10;</xsl:text>
<xsl:variable name="conflict-report">
<xsl:apply-templates select="state" mode="conflicts"/>
</xsl:variable>
<xsl:if test="string-length($conflict-report) != 0">
<p class="pre">
<xsl:copy-of select="$conflict-report"/>
<xsl:text>&#10;&#10;</xsl:text>
</p>
</xsl:if>
</xsl:template>
<xsl:template match="state" mode="conflicts">
<xsl:variable name="conflict-counts">
<xsl:apply-templates select="." mode="bison:count-conflicts" />
</xsl:variable>
<xsl:variable
name="sr-count" select="substring-before($conflict-counts, ',')"
/>
<xsl:variable
name="rr-count" select="substring-after($conflict-counts, ',')"
/>
<xsl:if test="$sr-count > 0 or $rr-count > 0">
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#state_', @number)"/>
</xsl:attribute>
<xsl:value-of select="concat('State ', @number)"/>
</a>
<xsl:text> conflicts:</xsl:text>
<xsl:if test="$sr-count > 0">
<xsl:value-of select="concat(' ', $sr-count, ' shift/reduce')"/>
<xsl:if test="$rr-count > 0">
<xsl:value-of select="(',')"/>
</xsl:if>
</xsl:if>
<xsl:if test="$rr-count > 0">
<xsl:value-of select="concat(' ', $rr-count, ' reduce/reduce')"/>
</xsl:if>
<xsl:value-of select="'&#10;'"/>
</xsl:if>
</xsl:template>
<xsl:template match="grammar/terminals">
<h3>
<a name="terminals"/>
<xsl:text> Terminals, with rules where they appear</xsl:text>
</h3>
<xsl:text>&#10;&#10;</xsl:text>
<ul>
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="terminal"/>
</ul>
<xsl:text>&#10;&#10;</xsl:text>
</xsl:template>
<xsl:template match="grammar/nonterminals">
<h3>
<a name="nonterminals"/>
<xsl:text> Nonterminals, with rules where they appear</xsl:text>
</h3>
<xsl:text>&#10;&#10;</xsl:text>
<ul>
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates
select="nonterminal[@usefulness!='useless-in-grammar']"
/>
</ul>
</xsl:template>
<xsl:template match="terminal">
<xsl:text> </xsl:text>
<li>
<b><xsl:value-of select="@name"/></b>
<xsl:if test="string-length(@type) != 0">
<xsl:value-of select="concat(' &lt;', @type, '&gt;')"/>
</xsl:if>
<xsl:value-of select="concat(' (', @token-number, ')')"/>
<xsl:for-each select="key('bison:ruleByRhs', @name)">
<xsl:apply-templates select="." mode="number-link"/>
</xsl:for-each>
</li>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="nonterminal">
<xsl:text> </xsl:text>
<li>
<b><xsl:value-of select="@name"/></b>
<xsl:if test="string-length(@type) != 0">
<xsl:value-of select="concat(' &lt;', @type, '&gt;')"/>
</xsl:if>
<xsl:value-of select="concat(' (', @symbol-number, ')')"/>
<xsl:text>&#10; </xsl:text>
<ul>
<xsl:text>&#10;</xsl:text>
<xsl:if test="key('bison:ruleByLhs', @name)">
<xsl:text> </xsl:text>
<li>
<xsl:text>on left:</xsl:text>
<xsl:for-each select="key('bison:ruleByLhs', @name)">
<xsl:apply-templates select="." mode="number-link"/>
</xsl:for-each>
</li>
<xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:if test="key('bison:ruleByRhs', @name)">
<xsl:text> </xsl:text>
<li>
<xsl:text>on right:</xsl:text>
<xsl:for-each select="key('bison:ruleByRhs', @name)">
<xsl:apply-templates select="." mode="number-link"/>
</xsl:for-each>
</li>
<xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:text> </xsl:text>
</ul>
<xsl:text>&#10; </xsl:text>
</li>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="rule" mode="number-link">
<xsl:text> </xsl:text>
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#rule_', @number)"/>
</xsl:attribute>
<xsl:value-of select="@number"/>
</a>
</xsl:template>
<xsl:template match="automaton">
<h2>
<a name="automaton"/>
<xsl:text> Automaton</xsl:text>
</h2>
<xsl:apply-templates select="state">
<xsl:with-param name="pad" select="'3'"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="automaton/state">
<xsl:param name="pad"/>
<xsl:text>&#10;&#10;</xsl:text>
<h3>
<a>
<xsl:attribute name="name">
<xsl:value-of select="concat('state_', @number)"/>
</xsl:attribute>
</a>
<xsl:text>State </xsl:text>
<xsl:value-of select="@number"/>
</h3>
<xsl:text>&#10;&#10;</xsl:text>
<p class="pre">
<xsl:apply-templates select="itemset/item">
<xsl:with-param name="pad" select="$pad"/>
</xsl:apply-templates>
<xsl:apply-templates select="actions/transitions">
<xsl:with-param name="type" select="'shift'"/>
</xsl:apply-templates>
<xsl:apply-templates select="actions/errors"/>
<xsl:apply-templates select="actions/reductions"/>
<xsl:apply-templates select="actions/transitions">
<xsl:with-param name="type" select="'goto'"/>
</xsl:apply-templates>
<xsl:apply-templates select="solved-conflicts"/>
</p>
</xsl:template>
<xsl:template match="actions/transitions">
<xsl:param name="type"/>
<xsl:if test="transition[@type = $type]">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="transition[@type = $type]">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="transition[@type = $type]"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="actions/errors">
<xsl:if test="error">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="error">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="error"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="actions/reductions">
<xsl:if test="reduction">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="reduction">
<xsl:with-param name="pad">
<xsl:call-template name="max-width-symbol">
<xsl:with-param name="node" select="reduction"/>
</xsl:call-template>
</xsl:with-param>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="item">
<xsl:param name="pad"/>
<xsl:param name="prev-rule-number"
select="preceding-sibling::item[1]/@rule-number"/>
<xsl:apply-templates
select="key('bison:ruleByNumber', current()/@rule-number)"
>
<xsl:with-param name="itemset" select="'true'"/>
<xsl:with-param name="pad" select="$pad"/>
<xsl:with-param name="prev-lhs"
select="key('bison:ruleByNumber', $prev-rule-number)/lhs[text()]"
/>
<xsl:with-param name="dot" select="@dot"/>
<xsl:with-param name="lookaheads">
<xsl:apply-templates select="lookaheads"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<!--
anchor = 'true': define as an <a> anchor.
itemset = 'true': show the items.
-->
<xsl:template match="rule">
<xsl:param name="anchor"/>
<xsl:param name="itemset"/>
<xsl:param name="pad"/>
<xsl:param name="prev-lhs"/>
<xsl:param name="dot"/>
<xsl:param name="lookaheads"/>
<xsl:if test="$itemset != 'true' and not($prev-lhs = lhs[text()])">
<xsl:text>&#10;</xsl:text>
</xsl:if>
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="$anchor = 'true'">
<a>
<xsl:attribute name="name">
<xsl:value-of select="concat('rule_', @number)"/>
</xsl:attribute>
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="string(@number)"/>
<xsl:with-param name="pad" select="number($pad)"/>
</xsl:call-template>
</a>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#rule_', @number)"/>
</xsl:attribute>
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="string(@number)"/>
<xsl:with-param name="pad" select="number($pad)"/>
</xsl:call-template>
</a>
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text>
<!-- LHS -->
<xsl:choose>
<xsl:when test="$prev-lhs = lhs[text()]">
<xsl:call-template name="lpad">
<xsl:with-param name="str" select="'|'"/>
<xsl:with-param name="pad" select="number(string-length(lhs[text()])) + 2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<span class="i">
<xsl:value-of select="lhs"/>
</span>
<xsl:text> &#8594;</xsl:text>
</xsl:otherwise>
</xsl:choose>
<!-- RHS -->
<xsl:for-each select="rhs/*">
<xsl:if test="position() = $dot + 1">
<xsl:text> </xsl:text>
<span class="dot">&#x2022;</span>
</xsl:if>
<xsl:apply-templates select="."/>
<xsl:if test="position() = last() and position() = $dot">
<xsl:text> </xsl:text>
<span class="dot">&#x2022;</span>
</xsl:if>
</xsl:for-each>
<xsl:if test="$lookaheads">
<xsl:value-of select="$lookaheads"/>
</xsl:if>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="symbol">
<xsl:text> </xsl:text>
<xsl:choose>
<xsl:when test="name(key('bison:symbolByName', .)) = 'nonterminal'">
<span class="i"><xsl:value-of select="."/></span>
</xsl:when>
<xsl:otherwise>
<b><xsl:value-of select="."/></b>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="empty">
<xsl:text> %empty</xsl:text>
</xsl:template>
<xsl:template match="lookaheads">
<xsl:text> [</xsl:text>
<xsl:apply-templates select="symbol"/>
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="lookaheads/symbol">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="transition">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:choose>
<xsl:when test="@type = 'shift'">
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#state_', @state)"/>
</xsl:attribute>
<xsl:value-of select="concat('shift, and go to state ', @state)"/>
</a>
</xsl:when>
<xsl:when test="@type = 'goto'">
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#state_', @state)"/>
</xsl:attribute>
<xsl:value-of select="concat('go to state ', @state)"/>
</a>
</xsl:when>
</xsl:choose>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="error">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:text>error</xsl:text>
<xsl:text> (</xsl:text>
<xsl:value-of select="text()"/>
<xsl:text>)</xsl:text>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="reduction">
<xsl:param name="pad"/>
<xsl:text> </xsl:text>
<xsl:call-template name="rpad">
<xsl:with-param name="str" select="string(@symbol)"/>
<xsl:with-param name="pad" select="number($pad) + 2"/>
</xsl:call-template>
<xsl:if test="@enabled = 'false'">
<xsl:text>[</xsl:text>
</xsl:if>
<xsl:choose>
<xsl:when test="@rule = 'accept'">
<xsl:text>accept</xsl:text>
</xsl:when>
<xsl:otherwise>
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#rule_', @rule)"/>
</xsl:attribute>
<xsl:value-of select="concat('reduce using rule ', @rule)"/>
</a>
<xsl:text> (</xsl:text>
<xsl:value-of
select="key('bison:ruleByNumber', current()/@rule)/lhs[text()]"
/>
<xsl:text>)</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="@enabled = 'false'">
<xsl:text>]</xsl:text>
</xsl:if>
<xsl:text>&#10;</xsl:text>
</xsl:template>
<xsl:template match="solved-conflicts">
<xsl:if test="resolution">
<xsl:text>&#10;</xsl:text>
<xsl:apply-templates select="resolution"/>
</xsl:if>
</xsl:template>
<xsl:template match="resolution">
<xsl:text> Conflict between </xsl:text>
<a>
<xsl:attribute name="href">
<xsl:value-of select="concat('#rule_', @rule)"/>
</xsl:attribute>
<xsl:value-of select="concat('rule ',@rule)"/>
</a>
<xsl:text> and token </xsl:text>
<xsl:value-of select="@symbol"/>
<xsl:text> resolved as </xsl:text>
<xsl:if test="@type = 'error'">
<xsl:text>an </xsl:text>
</xsl:if>
<xsl:value-of select="@type"/>
<xsl:text> (</xsl:text>
<xsl:value-of select="."/>
<xsl:text>).&#10;</xsl:text>
</xsl:template>
<xsl:template name="max-width-symbol">
<xsl:param name="node"/>
<xsl:variable name="longest">
<xsl:for-each select="$node">
<xsl:sort data-type="number" select="string-length(@symbol)"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="string-length(@symbol)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="$longest"/>
</xsl:template>
<xsl:template name="lpad">
<xsl:param name="str" select="''"/>
<xsl:param name="pad" select="0"/>
<xsl:variable name="diff" select="$pad - string-length($str)" />
<xsl:choose>
<xsl:when test="$diff &lt; 0">
<xsl:value-of select="$str"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$diff"/>
</xsl:call-template>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="rpad">
<xsl:param name="str" select="''"/>
<xsl:param name="pad" select="0"/>
<xsl:variable name="diff" select="$pad - string-length($str)"/>
<xsl:choose>
<xsl:when test="$diff &lt; 0">
<xsl:value-of select="$str"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$diff"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="space">
<xsl:param name="repeat">0</xsl:param>
<xsl:param name="fill" select="' '"/>
<xsl:if test="number($repeat) &gt;= 1">
<xsl:call-template name="space">
<xsl:with-param name="repeat" select="$repeat - 1"/>
<xsl:with-param name="fill" select="$fill"/>
</xsl:call-template>
<xsl:value-of select="$fill"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

4
visualc/src/include/ngspice/config.h

@ -35,7 +35,7 @@
#define OSDI 1 #define OSDI 1
/* The CIDER enhancements */ /* The CIDER enhancements */
#define CIDER 1
/* #undef CIDER */
/* don't undef CIDER, otherwise compilation will fail */ /* don't undef CIDER, otherwise compilation will fail */
/* Define if we want debug sensitivity analysis */ /* Define if we want debug sensitivity analysis */
@ -456,7 +456,7 @@
/* #undef IPC_UNIX_SOCKETS */ /* #undef IPC_UNIX_SOCKETS */
/* Define if we want KLU linear systems solver */ /* Define if we want KLU linear systems solver */
#define KLU /**/
/* #undef KLU */
/* The NDEV interface */ /* The NDEV interface */
/* #undef NDEV */ /* #undef NDEV */

Loading…
Cancel
Save