From f67aad1e93664eea45ee1e407edc1de04d204845 Mon Sep 17 00:00:00 2001 From: xtof Date: Tue, 13 Oct 2026 11:00:47 +0200 Subject: [PATCH 1/2] new version with source references --- .gitmodules | 3 + CLAUDE.md | 92 ++- CMakeLists.txt | 7 +- LICENSES/OFL-1.1.txt | 43 ++ README.md | 39 +- REUSE.toml | 17 + docs/source_information.md | 232 +++++++ python/naja_schematic/protocol.py | 311 +++++++-- python/naja_schematic/server.py | 50 +- python/naja_schematic/session.py | 15 +- python/naja_schematic/widget.py | 13 +- python/notebooks/getting_started.ipynb | 40 +- python/notebooks/tinyrocket.ipynb | 49 +- python/pyproject.toml | 2 +- python/tests/protocol_cases.py | 158 +++++ python/tests/test_anonymous_instances.py | 108 +-- python/tests/test_notebook.py | 14 + python/tests/test_source_references.py | 209 ++++++ src/AppLogic.cpp | 75 +- src/AppLogic.h | 3 + src/CousineFont.h | 643 ++++++++++++++++++ src/EquipotentialView.cpp | 38 +- src/EquipotentialView.h | 3 +- src/LocalSNLProvider.cpp | 336 +++++++-- src/LocalSNLProvider.h | 28 +- src/NetlistTree.cpp | 127 ++-- src/NetlistTree.h | 39 +- src/SchematicLayout.cpp | 2 +- src/SchematicLayout.h | 4 +- src/SourceStore.cpp | 168 ++++- src/SourceStore.h | 80 ++- src/SourceView.cpp | 333 ++++++++- src/SourceView.h | 20 +- src/Types.cpp | 40 +- src/Types.h | 39 +- src/VerilogLanguage.cpp | 166 +++++ src/VerilogLanguage.h | 11 + src/main_native.cpp | 28 +- tests/SelectionTest.cpp | 65 ++ tests/SourceStoreTest.cpp | 157 ++++- tests/TypesJsonTest.cpp | 60 +- tests/VerilogLanguageTest.cpp | 63 ++ tests/data/source_protocol_cases.json | 150 ++++ tests/data/sources/gate/alu_syn.v | 22 + tests/data/sources/rtl/alu.v | 3 + .../provider/LocalSNLProviderProtocolTest.cpp | 244 +++++-- thirdparty/ImGuiColorTextEdit | 1 + thirdparty/imgui | 2 +- thirdparty/naja | 2 +- 49 files changed, 3845 insertions(+), 509 deletions(-) create mode 100644 LICENSES/OFL-1.1.txt create mode 100644 docs/source_information.md create mode 100644 python/tests/protocol_cases.py create mode 100644 python/tests/test_source_references.py create mode 100644 src/CousineFont.h create mode 100644 src/VerilogLanguage.cpp create mode 100644 src/VerilogLanguage.h create mode 100644 tests/VerilogLanguageTest.cpp create mode 100644 tests/data/source_protocol_cases.json create mode 100644 tests/data/sources/gate/alu_syn.v create mode 100644 tests/data/sources/rtl/alu.v create mode 160000 thirdparty/ImGuiColorTextEdit diff --git a/.gitmodules b/.gitmodules index 3427fc5..179b345 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "thirdparty/googletest"] path = thirdparty/googletest url = https://github.com/google/googletest.git +[submodule "thirdparty/ImGuiColorTextEdit"] + path = thirdparty/ImGuiColorTextEdit + url = https://github.com/goossens/ImGuiColorTextEdit diff --git a/CLAUDE.md b/CLAUDE.md index 9ec56d7..6969db0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,8 @@ remaining piece to actually produce the file this flag consumes. ## Building -Clone with submodules (`thirdparty/imgui`, `thirdparty/naja`) — if already cloned +Clone with submodules (`thirdparty/imgui`, `thirdparty/naja`, +`thirdparty/ImGuiColorTextEdit`) — if already cloned without them: `git submodule update --init --recursive`. ### Native standalone target (`naja-schematic-standalone`) @@ -91,12 +92,18 @@ Run directly, optionally with a netlist to load (dispatched by extension — directory), `--liberty ` (repeatable, once per file — defines the primitive cell library for a `.v` design; not supported for `.sv`, since `LocalSNLProvider::loadSystemVerilog()`/`SNLSVConstructor` has no liberty -hook, and the CLI fails loudly rather than silently ignoring it), and/or a -diagnosis JSON to pre-load (`--diagnosis `, any order, all optional — -see `loadDiagnosisFile()` in `AppLogic.cpp`): +hook, and the CLI fails loudly rather than silently ignoring it), a +diagnosis JSON to pre-load (`--diagnosis ` — see +`loadDiagnosisFile()` in `AppLogic.cpp`), and/or directories to look up +relative source file references in (`--source-root `, repeatable; see +"Source references" under Wire protocol), path prefixes to rewrite for +files recorded on another machine (`--source-map FROM=TO`, repeatable), +and `--yosys-src` (`.v` only: +decode Yosys `(* src *)` attributes into source origins; also a checkbox in +File > Open Verilog...) — any order, all optional: ```bash -../naja-schematic-build/native-debug/naja-schematic-standalone [path/to/netlist] [--liberty path/to/cells.lib]... [--diagnosis path/to/diagnosis.json] +../naja-schematic-build/native-debug/naja-schematic-standalone [path/to/netlist] [--liberty path/to/cells.lib]... [--diagnosis path/to/diagnosis.json] [--source-root dir]... [--source-map FROM=TO]... [--yosys-src] # gate-level Verilog + Liberty example: ../naja-schematic-build/native-debug/naja-schematic-standalone design.v --liberty cells.lib @@ -200,7 +207,11 @@ WASM viewer, so najaeda users get a viewer from `pip install` alone: the universe directly on the host's thread -- so a host holding its lock, or running on the thread `run` hops to, never deadlocks on itself. - `server.py` — the `naja-schematic` CLI (same flags as above, plus `--host`, - `--open`, `--diagnosis `, `--stdio`) and its two transports: a + `--open`, `--diagnosis `, `--stdio`, `--source-root ` + (repeatable; `source_roots=` on `ViewerSession`/`ViewerServer`/`show()`), + `--source-map FROM=TO` (repeatable; `source_maps=` there too), + `--yosys-src` (with `--verilog`: `VerilogConfig.yosys_source_attributes`)) + and its two transports: a WebSocket server that also serves the viewer page on the same port (`static/index.html` sets `Module.najaWsUrl` from `location`, query string included), and JSON @@ -532,7 +543,7 @@ the same `id_path`/`path` pair as diagnosis items: // path/id_path: the resolved instance's names and ids (the request's, as given, if not found) "instance": { // absent for the top design ([]) "path": [["u1", 3, "Mod"], ["u2", 7, "AND2"]], // [name, child_id, model] per level - "design_ref": {...}, "primitive_type": "and", "has_instances": false, "source_loc": null, + "design_ref": {...}, "primitive_type": "and", "has_instances": false, "has_sources": true, "terms": [ {"name": "A", "child_id": 0, "direction": 0}, ... ] } } // expanded_instance_terms shape ``` The viewer then: @@ -651,6 +662,48 @@ so unlike a term's properties there's no "Direction" entry, and nets don't offer a schematic-side "Show Properties" entry point (only terms/instances appear as boxes/pins there). +**Source references** (full design: `docs/source_information.md`; needs +najaeda >= 0.7.29). naja keeps, per object, an optional *declaration* +(where it's written in the loaded input: a gate's netlist line, an +instance's instantiation, a design's definition) and ordered *origins* +(upstream RTL, e.g. Yosys `src`), each qualified by provider/language/ +representation (`""` = unknown -- shown as is, never inferred, same rule as +gate types), plus the older unclassified legacy slot (`getSourceLoc()`). +SV and gate-level Verilog fill declarations; Yosys `src` becomes origins +only when asked (`--yosys-src`, `yosys_source_attributes`: naja never +guesses provenance). Entries carry a sparse `"has_sources": true` +(instances: own references -- `hasSourceReferences()`, allocation-free -- +or legacy, or the model's declaration; a bus: itself or any bit) -- this +replaced the old inline `source_loc`. `get_sources` +takes the `get_properties` address and answers `sources_response` with +`subject`, the address echoed, and `sources`: `declaration`, `origin`s +(with `index`), `model_declaration` (instances), `legacy` only when there's +no typed reference; a bus bit without its own falls back to its bus's. +`load_source` names one entry (address + `role` + `index`), **never a +path**: the host reads only files the design references -- first through +each source map whose FROM prefixes it at a path boundary (if a file is +there), then an absolute path as is, a relative one under each source root +then the current directory (`resolveSourceFile()`/`resolve_source_file()`, +Python groups roots and maps as `protocol.SourcePaths`), whole up to 4 MiB, else +the lines within 500 of the reference (`first_line`, `truncated`). Both +backends: `requestSources()` in `LocalSNLProvider.cpp`, +`request_sources()` in `protocol.py`; shared cases in +`tests/data/source_protocol_cases.json`, on a Yosys-style gate netlist +both runners load with naja's Verilog loader (add one when changing +either). The raw Python API is read-only for sources, so Python tests get +source data by loading files, never by setting it. +The tree (flagged rows) and schematic boxes (and module frames, always) +offer "Show Sources"; the reply fills the "Source" tab, opens the first +entry's file, and grows the bottom panel to 45% of the height once +(`AppState::growSourcePanel`; never shrinks it). A found `source_response` +also says `"stale"`: the file was modified after the design was loaded +(`LocalSNLProvider::loadedAt_`, stamped in `freshDB()`/`setDB()`; +`SourcePaths.loaded_at`, stamped by `ViewerSession` at start and on each +`design_changed()`), so naja's ranges may not match it -- the Source tab +shows a warning. naja keeps no file hashes, so modification time is the +check. A found `source_response` also carries `path`, the absolute +path the host read, used by the native "Open in Editor". + ### Core modules (`src/`) - **`NetlistTree`** — the design hierarchy tree (instances, terms, nets, bus @@ -680,6 +733,31 @@ appear as boxes/pins there). `root_response`/`root_loaded`. - **`PropertiesView`** — renders the current `PropertiesStore` contents as a two-column name/value table into the "Properties" bottom-panel tab. +- **`SourceStore`** — global static store (same pattern) for the "Source" + tab: the last `sources_response` (subject, the object's address, its + `SourceRef`s; `loadRequest(i)` builds the `load_source` for one) and the + shown file (`source_response`: lines, `first_line` offset, the reference + range; `highlightBytes()` gives the byte span to shade per line, by the + 1-based inclusive columns, never inventing an unknown end). Cleared on + every fresh `root_response`/`root_loaded`. +- **`SourceView`** — renders `SourceStore` into the "Source" tab: the + reference rows (click to load one) above the file, shown in a read-only + [ImGuiColorTextEdit](https://github.com/goossens/ImGuiColorTextEdit) + editor (submodule; `TextEditor.cpp` is in `CORE_SOURCES`) drawn with the + embedded monospace Cousine font (`CousineFont.h`, loaded in + `setupFonts()`): coloring by `VerilogLanguage`, find (Ctrl+F), copy, the + reference's range as a background squiggle (byte columns converted with + `SourceStore::glyphIndex()`), line markers for it and the object's other + references into the same file, line numbers offset by `first_line` for a + windowed file. Native only: "Open in Editor" runs + `$NAJA_SCHEMATIC_EDITOR`, else VS Code (`code` on PATH, else the CLI in + the app bundle), else `open`/`xdg-open` (`SourceStore::editorPattern()`, + `editorCommand()`, path shell-quoted) on a worker thread, and shows its + result or error next to the button. +- **`VerilogLanguage`** — the editor's Verilog/SystemVerilog language + definition: keywords, types, `` ` `` directive lines, comments, strings, + `$system` tasks, escaped identifiers and sized/based numbers (its own + tokenizers, `tests/VerilogLanguageTest.cpp`). Coloring only, not a parser. - **`TraceStore`** — global static store (same pattern) for the driver traces in the schematic: id, label (the start pin), palette color, visibility. A trace is registered when its `trace_driver` request is sent, diff --git a/CMakeLists.txt b/CMakeLists.txt index a6ff249..a74cc9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ set(CORE_SOURCES src/DiagnosisView.cpp src/SourceStore.cpp src/SourceView.cpp + src/VerilogLanguage.cpp src/PropertiesStore.cpp src/PropertiesView.cpp src/SelectionStore.cpp @@ -104,6 +105,8 @@ set(CORE_SOURCES src/TraceView.cpp src/AppLogic.cpp ${IMGUI_SOURCES} + # Read-only code view of the Source tab (SourceView). + ${CMAKE_SOURCE_DIR}/thirdparty/ImGuiColorTextEdit/TextEditor.cpp ) # ============================================================ @@ -267,10 +270,12 @@ if(NOT EMSCRIPTEN_BUILD) src/EquipotentialView.cpp src/SchematicView.cpp src/SvgPainter.cpp + src/VerilogLanguage.cpp ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_draw.cpp ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp + ${CMAKE_SOURCE_DIR}/thirdparty/ImGuiColorTextEdit/TextEditor.cpp ${NAJA_SCHEMATIC_TEST_SOURCES} ) target_link_libraries(naja-schematic-tests PRIVATE gtest_main) @@ -294,7 +299,7 @@ if(NOT EMSCRIPTEN_BUILD) naja_nl_dump ) target_compile_definitions(naja-schematic-provider-tests PRIVATE - NAJA_SCHEMATIC_PROTOCOL_CASES="${CMAKE_CURRENT_SOURCE_DIR}/tests/data/anonymous_protocol_cases.json" + NAJA_SCHEMATIC_TEST_DATA="${CMAKE_CURRENT_SOURCE_DIR}/tests/data" ) enable_testing() diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 0000000..6fe84ee --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,43 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/README.md b/README.md index e49ce95..034cb48 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ native desktop app and a WASM app that runs in a browser or a VSCode webview. - Diagnosis overlay: color-codes and annotates flagged instances/pins/nets from a `diagnosis_response` JSON payload, with hover tooltips and a flat diagnostics list panel +- Source cross-probing: **Show Sources** on an instance, term or net lists + where it is declared and, for synthesized logic, the RTL it came from + (naja source references, e.g. from Yosys `src` attributes), and opens + the file at the highlighted range - Loads Verilog, SystemVerilog, or pre-built SNL netlists natively; the browser build talks to a Python netlist server over WebSocket - Runs natively on macOS or Linux, or anywhere with a browser/VSCode via WASM @@ -54,7 +58,23 @@ Cmd in the native macOS app (Ctrl in a browser, on any OS). | Click a box | Select the instance (also in the design tree) | | Click a wire | Select (highlight) its net | | Click a box's hierarchy glyph | Show / hide what is inside it | -| Right-click | Context menu: trace to driver, properties, fit, clear | +| Right-click | Context menu: trace to driver, sources, properties, fit, clear | + +| Keys (Source tab, after clicking in the text) | Action | +|---|---| +| Ctrl `F` | Find in the shown file | +| Ctrl `G` | Find the next match | +| Ctrl `C` | Copy the selected text | +| Ctrl `A` | Select the whole file | + +The native app also has an **Open in Editor** button in the Source tab: it +runs `$NAJA_SCHEMATIC_EDITOR` (`{file}`, `{line}` and `{col}` are filled in, +e.g. `subl {file}:{line}:{col}`). Without it, VS Code is used at the line +(`code -g` if on PATH, else the CLI inside `Visual Studio Code.app`), else +the system's default app; the result (or the error) shows next to the +button. Edits made there reach the viewer only when the design is loaded +again: until then, the Source tab warns that the file changed after the +design was loaded and its highlighted lines may be off. In a notebook or a VSCode webview, the viewer takes keys only while its canvas has focus (click it first), and the host may keep some Ctrl chords @@ -104,15 +124,22 @@ Run (`../naja-schematic-build/native-debug/naja-schematic-standalone` on macOS, `build/naja-schematic-standalone` on Linux), optionally with a netlist to load (dispatched by extension — `.v` → Verilog, `.sv` → SystemVerilog, otherwise treated as an SNL directory), one or more -`--liberty` files defining the primitive cell library for a `.v` design, and/or -a diagnosis JSON to pre-load: +`--liberty` files defining the primitive cell library for a `.v` design, +a diagnosis JSON to pre-load, and/or directories to look up relative source +file paths in (Show Sources): ```bash -naja-schematic-standalone [path/to/netlist] [--liberty path/to/cells.lib]... [--diagnosis path/to/diagnosis.json] +naja-schematic-standalone [path/to/netlist] [--liberty path/to/cells.lib]... [--diagnosis path/to/diagnosis.json] [--source-root dir]... [--source-map FROM=TO]... [--yosys-src] ``` -``, `--liberty ` and `--diagnosis ` are all optional and -order-independent. `--liberty` is repeatable (once per file) and only +``, `--liberty `, `--diagnosis ` and `--source-root +` are all optional and order-independent. `--source-root` is +repeatable: a relative source path is looked up in each, in order, then in +the current directory. `--source-map FROM=TO` (repeatable) rewrites paths +recorded on another machine, e.g. Yosys `src` paths of a synthesis server: +`--source-map /home/ci/flow=/work/flow`. `--yosys-src` (`.v` only; also a checkbox in File > +Open Verilog...) decodes Yosys `(* src *)` attributes, so Show Sources on a +synthesized gate also lists the RTL it came from. `--liberty` is repeatable (once per file) and only supported for `.v` designs — a gate-level Verilog netlist that instantiates Liberty std cells needs it to resolve them; it errors out for `.sv`, since the SystemVerilog frontend has no liberty hook. Without `--diagnosis`, use File > diff --git a/REUSE.toml b/REUSE.toml index 29208f6..7d08e65 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -21,6 +21,7 @@ path = [ "CMakePresets.json", "README.md", "docs/demo.gif", + "docs/source_information.md", "python/README.md", "python/naja_schematic/__init__.py", "python/naja_schematic/__main__.py", @@ -35,11 +36,13 @@ path = [ "python/notebooks/tinyrocket.ipynb", "python/pyproject.toml", "python/tests/conftest.py", + "python/tests/protocol_cases.py", "python/tests/test_anonymous_instances.py", "python/tests/data/design.v", "python/tests/test_design_generations.py", "python/tests/test_notebook.py", "python/tests/test_protocol.py", + "python/tests/test_source_references.py", "python/tests/test_transports.py", "python/tests/test_viewer_server.py", "python/tests/test_widget.py", @@ -98,6 +101,8 @@ path = [ "src/TraceStore.h", "src/TraceView.cpp", "src/TraceView.h", + "src/VerilogLanguage.cpp", + "src/VerilogLanguage.h", "src/Types.cpp", "src/Types.h", "src/Version.h", @@ -119,7 +124,11 @@ path = [ "tests/SourceStoreTest.cpp", "tests/TraceTest.cpp", "tests/TypesJsonTest.cpp", + "tests/VerilogLanguageTest.cpp", "tests/data/anonymous_protocol_cases.json", + "tests/data/source_protocol_cases.json", + "tests/data/sources/gate/alu_syn.v", + "tests/data/sources/rtl/alu.v", "tests/provider/LocalSNLProviderProtocolTest.cpp", ] SPDX-FileCopyrightText = "2026 najaeda contributors" @@ -140,6 +149,14 @@ path = "src/DroidSansFont.h" SPDX-FileCopyrightText = "Google Inc." SPDX-License-Identifier = "Apache-2.0" +# Exported copy of thirdparty/imgui's misc/fonts/Cousine-Regular.ttf (by +# Steve Matteson, SIL Open Font License 1.1 per imgui's docs/FONTS.md), the +# Source tab's monospace font, embedded like DroidSansFont.h. +[[annotations]] +path = "src/CousineFont.h" +SPDX-FileCopyrightText = "2010 Google Corporation" +SPDX-License-Identifier = "OFL-1.1" + # Derived from Emscripten's html/shell_minimal.html template, which # Emscripten dual-licenses under MIT and the University of Illinois/NCSA # Open Source License; MIT is used here as the SPDX identifier. diff --git a/docs/source_information.md b/docs/source_information.md new file mode 100644 index 0000000..966e0fe --- /dev/null +++ b/docs/source_information.md @@ -0,0 +1,232 @@ +# Source information in naja-schematic + +How the viewer shows where a netlist object comes from: its declaration +in the loaded input and, for synthesized logic, the RTL it was produced +from. It is built on the source references of naja 0.7.29 +(`docs/source_information_design.md` in naja). Requires +**najaeda >= 0.7.29**. + +## What naja provides + +Each `SNLDesign` and design object (instance, term, net, bus bit) can carry +`SNLRTLInfos` holding: + +- an optional **declaration**: where the object is written in the input + that was loaded. For an instance, that's its instantiation; for a design, + its definition; for a gate in a gate-level netlist, its line in that + netlist. +- ordered **origins**: where it came from upstream, e.g. the RTL ranges of a + Yosys `src` attribute (several when logic was merged). +- the **legacy location** (`getSourceLoc()`), an older, unclassified slot. + +What fills them in najaeda 0.7.29: + +- **SystemVerilog** (slang): declarations for module definitions, ports, + named nets and instantiations; origins for lowered logic. Provider + `slang`, language `systemverilog`, representation `rtl`. The legacy slot + is still written too, and the viewer hides it when typed references exist. +- **Gate-level Verilog**: every design, instance, term and declared net + gets its netlist declaration (provider `naja-verilog`, language + `verilog`). The spans are partial: a module's keyword and name, an + instance's name, an anonymous gate's type. With Yosys decoding turned on + (`yosys_source_attributes`, off by default, see below), each + `(* src = "f:l.c-l.c|..." *)` attribute becomes ordered origins (provider + `yosys`). Origins can point at technology/model files as well as RTL. + Bus bits carry no reference of their own. +- **NajaIF** snapshots keep all of it, so a design loaded natively from an + SNL directory shows the same sources. +- **VHDL** doesn't fill anything yet. + +A reference is a range (file, 1-based line and byte column, inclusive end; +0 means unknown) plus three labels: `provider` (e.g. `slang`, `yosys`), +`language` (e.g. `systemverilog`, `verilog`) and `representation` (`rtl`, +`gate`). An empty label means unknown. The viewer shows labels as given and +never infers them, from a file extension or anything else. A file path is +an opaque string from whatever produced the reference. + +## Wire protocol + +Both backends implement it: `python/naja_schematic/protocol.py` and +`src/LocalSNLProvider.cpp`. The shared cases in +`tests/data/source_protocol_cases.json` run against both. + +### `has_sources` flags + +Instance entries (`instances_response`, `primitives_response`, `root`, +`instance_internals_response` children, equipotential occurrences, +`instance_resolved`'s `instance`), term entries (`terms_response`) and net +entries (`nets_response`) carry `"has_sources": true` when there is +something to show. Like `clock`, the flag is only sent when it is true: + +- an instance: its own references, or its model's declaration; +- a bus term or net: the bus itself or any of its bits (a bit row + inherits the flag). + +The flag replaces the old `source_loc` object. Sources are fetched on +demand rather than inlined, because a synthesized gate can have many +origins and a trace reply holds up to 500 nets. + +### `get_sources` → `sources_response` + +The object is addressed exactly as in `get_properties`: `kind` +(`instance`, `term` or `net`), the instance (for an instance) or the +containing instance (for a term or net) as `id_path` and/or `path` +(`[]` is the top), plus `terminal`/`net` and an optional `bit`. + +```json +{ "response": "sources_response", + "subject": "u_alu/g0", + "kind": "instance", "path": ["u_alu", "g0"], // the request's address, echoed + "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 4, "column": 3, + "end_line": 4, "end_column": 39, + "provider": "", "language": "verilog", "representation": "gate"}, + {"role": "origin", "index": 0, "file": "rtl/alu.v", "line": 2, "column": 14, + "end_line": 2, "end_column": 24, + "provider": "yosys", "language": "", "representation": ""} ] } +``` + +Entries come in this order: + +1. `declaration`; +2. each `origin`, in naja's order, numbered by `index`; +3. for an instance, `model_declaration`: the definition of its model; +4. `legacy`, only when the object has no declaration and no origins. + +The top design (`kind: "instance"`, `[]`) lists its own definition. A bus +bit with no references of its own lists its bus's. An object that doesn't +resolve, or has no source information, gives an empty `sources` list, not +an error. + +### `load_source` → `source_response` + +The request names one entry of an object: the same address as +`get_sources`, plus `role` (and `index` for an origin). The host finds the +file from the design itself, so **the viewer never sends a path**. A +`load_source` carrying only a `file` reads nothing. The old protocol let +any connected viewer read any file on the host. Now only files the +design's references name can be read. + +```json +{ "response": "source_response", "found": true, + "role": "origin", "index": 0, "file": "rtl/alu.v", "line": 2, ..., // the entry + "text": "...", "first_line": 1, "truncated": false } +``` + +- **Finding the file**: first, each *source map* (FROM, TO) whose FROM + starts the path at a path boundary (`/a/syn` maps `/a/syn/x.v`, not + `/a/synth/x.v`) rewrites it, in order, and is used if a file is there. + Then an absolute path is used as is, and a relative one is looked up under + each *source root* in order, then in the host's current directory. + Nothing is canonicalized. `found: false` (the entry is still echoed) + means the file isn't there. +- **Large files**: a file up to 4 MiB is sent whole. Past that (typically a + big gate-level netlist), only the lines within 500 of the reference are + sent: `truncated: true`, and `first_line` gives the file line number of + the first line in `text`. Bytes that aren't valid UTF-8 are replaced. + +Source roots are given as: + +- native: `naja-schematic-standalone --source-root --source-map FROM=TO` + (both repeatable); +- the `naja-schematic` CLI: the same two options; +- Python: `source_roots=` and `source_maps=[(FROM, TO), ...]` on + `ViewerSession`, `ViewerServer` and `naja_schematic.show()`. + +On `scripts/tinyrocket.v`, `--source-map /home/xtof=/Users/xtof` takes a +flop such as `buffer.Queue._T_1$_SDFFE_PP0P_` to its +`always @(posedge clock)` block, line 138,804 of the 20 MB tinyRocket RTL. + +Yosys `src` decoding is opt-in, as in naja, which never guesses provenance +from a banner, file name or attribute name: + +- native: `naja-schematic-standalone design.v --yosys-src`, or the + "Decode Yosys src attributes" checkbox in File > Open Verilog...; +- the `naja-schematic` CLI: `--verilog design.v --yosys-src`; +- notebooks: load with `VerilogConfig(yosys_source_attributes=True)` + (`config.yosys_source_attributes = True`) before `show()`. + +## User interface + +- **Show Sources** is a context-menu entry on flagged rows of the tree + (instances, terms, bus bits, nets, net bits) and on schematic boxes. + Module frames always offer it, since no flag is sent for them. +- The **Source** tab (`SourceView`, data in `SourceStore`) lists the + references, one row each: role, `file:line:col`, representation, + language and provider ("-" for unknown). The first reference's file + opens right away. Clicking another row opens that one. +- The file is shown in a read-only code editor (ImGuiColorTextEdit, as a + submodule) in a monospace font (Cousine), with Verilog/SystemVerilog + coloring, find (Ctrl+F, Ctrl+G), selection and copy. It scrolls to the + reference once, highlights its exact byte-column span (from the start + column on the first line to the end column on the last; an unknown end + is not made up, so the span stops at the end of the start line), marks + its lines in the gutter, and marks the object's other references into + the same file in another color. In a windowed large file, line numbers + stay those of the file. +- Native builds add **Open in Editor**: it runs `$NAJA_SCHEMATIC_EDITOR` + with `{file}`, `{line}` and `{col}` filled in, on the absolute path the + host read (`source_response`'s `path`). Unset, it uses VS Code at the line + (`code -g` on PATH, else the CLI inside the installed app, which macOS + doesn't put on PATH by default), else `open`/`xdg-open`. The result or + error shows next to the button. It is navigation: edits made there reach + the viewer only once the design is loaded again. +- **Stale files.** naja records ranges when the design is loaded, but the + viewer reads files later, so a file edited, checked out or re-synthesized + in between no longer matches them. Each host stamps when its design was + loaded (native: every load; Python: `ViewerSession` start and each + `design_changed()`), and a found `source_response` carries `"stale": true` + when the file's modification time is later. The Source tab then warns + that the highlighted lines may be off until the design is loaded again. + naja keeps no file hashes, so this is a modification-time check. +- The bottom panel grows to 45% of the window height when "Show Sources" + answers, once, and only if it was smaller. +- Everything is cleared when a new root loads, since the references name + objects of the old design. + +## Tests + +- `tests/data/source_protocol_cases.json`, with `tests/data/sources/`: a + Yosys-style gate netlist `gate/alu_syn.v` whose `(* src *)` attributes + point into `rtl/alu.v` (present) and `rtl/top.v` (missing). Both runners + load it with naja's own Verilog loader, Yosys decoding on, so the cases + check what naja really records: netlist declarations, ordered origins, + an anonymous gate's type span, an origin with no end, bus bits falling + back to their bus, a model definition. Run by + `python/tests/test_source_references.py` and + `tests/provider/LocalSNLProviderProtocolTest.cpp`. +- Python only: the flag and legacy fallback (stand-in objects: the raw + Python API is read-only), source-root order, the large-file window, the + session and CLI plumbing. +- C++ only: legacy fallback (`setSourceLoc`), the large-file window. +- `tests/SourceStoreTest.cpp`: the `load_source` request built from the + list, which row is shown, and highlight spans (single line, multi-line, + unknown end, windowed file, clamping). + +## Next steps + +1. **Hover**: show a box's first origin (`alu.v:42`) in the schematic + tooltip, so you can see which RTL line made a gate without opening + anything. This needs a compact origin summary inline, or a cached + `get_sources` per hovered box. +2. **Source history**: several files open, with back/forward. + **VSCode webview**: "Open in Editor" as a message to the extension. +3. **Source → netlist**: click a source line and highlight the objects + whose declaration or origins cover it. The host needs a file/line → + objects index, built once per design generation. +4. **Diagnosis items with source references**: once kepler-formal reports + point at RTL, a `diagnosis_response` item could carry a source reference + that opens directly. + +## Dependencies on naja + +naja 0.7.29 closed what this needed: SV declarations and origins, gate-level +declarations, Yosys `src` decoding, NajaIF persistence, an allocation-free +`hasSourceReferences()` in raw Python (used for the `has_sources` flags), +and wheels that build from a git checkout. Still open there: + +- **VHDL** writes no references yet. +- **Preprocessed Verilog** (`preprocess_enabled`) gets no declarations, + since the preprocessor keeps no source map. Yosys origins still work. +- **Path relocation**: recorded paths are kept as written; the viewer's + `--source-map` maps them to a local checkout. diff --git a/python/naja_schematic/protocol.py b/python/naja_schematic/protocol.py index 971ca40..b9fc74f 100644 --- a/python/naja_schematic/protocol.py +++ b/python/naja_schematic/protocol.py @@ -6,14 +6,18 @@ other. Nothing here knows about transports: see server.py (WebSocket and stdio) and widget.py (Jupyter/Colab comm channel). """ +import functools import json import logging +import os +from dataclasses import dataclass from najaeda import naja log = logging.getLogger("naja_schematic") -MIN_NAJAEDA_VERSION = (0, 7, 25) # keep in step with pyproject.toml +# 0.7.29: typed source references (getSourceDeclaration/getSourceOrigins). +MIN_NAJAEDA_VERSION = (0, 7, 29) # keep in step with pyproject.toml def check_najaeda_version(): @@ -97,7 +101,7 @@ def output_bit_count(model): def with_clock(entry, term): # Only sent when set: marks a flip-flop's clock pin (see drawDffInstance). - if term.is_clock(): + if term.isClock(): entry["clock"] = True return entry @@ -118,22 +122,71 @@ def has_nets(design): # short-circuit on the first visible net rather than materializing the whole list. return any(not is_anonymous_constant_net(net) for net in design.getNets()) -def get_source_loc(obj): - # RTL source location for an elaborated object (SNLRTLInfos), populated - # today only by the SystemVerilog/slang frontend. None means "no link - # available" -- not an error. - if not obj.hasSourceLoc(): - return None - file, line, column, end_line, end_column = obj.getSourceLoc() - return { - "file": file, - "line": line, - "end_line": end_line, - "column": column, - "end_column": end_column, - } +# --------------------------------------------------------------------------- +# Source references (see docs/source_information.md) +# --------------------------------------------------------------------------- +# naja keeps, per object, an optional *declaration* (where the object is +# written in the loaded input: a gate's line in the netlist, an instance's +# instantiation, a design's definition) and ordered *origins* (where it came +# from upstream, e.g. Yosys `src` RTL lines), each qualified by provider, +# language and representation ("" = unknown, never guessed). Older frontends +# only fill the unclassified legacy slot (getSourceLoc()), sent as role +# "legacy" when the object has no typed reference. LocalSNLProvider.cpp's +# objectSources() mirrors this. + +def _source_range_json(loc): + file, line, column, end_line, end_column = loc + return {"file": file, "line": line, "column": column, + "end_line": end_line, "end_column": end_column} + + +def _source_reference_json(role, reference, index=None): + loc, provider, language, representation = reference + entry = {"role": role, **_source_range_json(loc), "provider": provider, + "language": language, "representation": representation} + if index is not None: + entry["index"] = index + return entry + -def serialize_model(model, child_id, name, source_loc=None): +def object_sources(obj): + """The source entries of one design object or design: its declaration, + its origins in order, or else its legacy location.""" + entries = [] + declaration = obj.getSourceDeclaration() + if declaration is not None: + entries.append(_source_reference_json("declaration", declaration)) + for index, origin in enumerate(obj.getSourceOrigins()): + entries.append(_source_reference_json("origin", origin, index)) + if not entries and obj.hasSourceLoc(): + entries.append({"role": "legacy", **_source_range_json(obj.getSourceLoc()), + "provider": "", "language": "", "representation": ""}) + return entries + + +def has_sources(obj, model=None): + """Whether object_sources(obj) (plus the model's definition, for an + instance) would list anything -- the has_sources flag of tree rows and + schematic boxes.""" + # hasSourceReferences(): declaration or origins, without building them. + return (obj.hasSourceReferences() or obj.hasSourceLoc() + or (model is not None and model.getSourceDeclaration() is not None)) + + +def bus_has_sources(bus, bits): + # A bus row offers its sources if the bus or any of its bits has some + # (a bit row inherits the flag; a bit with none of its own shows the bus's). + return has_sources(bus) or any(has_sources(bit) for bit in bits) + + +def with_sources(entry, flag): + # Only sent when set, like "clock". + if flag: + entry["has_sources"] = True + return entry + + +def serialize_model(model, child_id, name, instance=None): result = { "name": name, "child_id": child_id, @@ -149,9 +202,8 @@ def serialize_model(model, child_id, name, source_loc=None): "has_instances": model.hasNonPrimitiveInstances(), "has_nets": has_nets(model), } - if source_loc is not None: - result["source_loc"] = source_loc - return result + return with_sources(result, has_sources(instance if instance is not None else model, + model if instance is not None else None)) def direction_to_int(direction): if direction == naja.SNLTerm.Direction.Input: @@ -300,7 +352,7 @@ def equipotential_to_json(equipotential, sinks=None): inst_model = instTerm.getInstance().getModel() has_instances = (inst_model.hasNonPrimitiveInstances() or has_visible_primitive_instances(inst_model)) - occurrences.append(with_clock({ + occurrences.append(with_sources(with_clock({ "path": path, "term_id": term.getID(), "name": term.getName(), @@ -316,11 +368,10 @@ def equipotential_to_json(equipotential, sinks=None): # Lets the view tell whether every pin of this instance is already # on screen (solid box) or only a subset (dashed, expandable). "bit_term_count": sum(1 for _ in inst_model.getBitTerms()), - "source_loc": get_source_loc(instTerm.getInstance()), # Net on this inst term inside the instance's parent design -- # shown in the schematic's pin hover tooltip. "net": net_display_name(instTerm.getNet()) - }, term)) + }, term), has_sources(instTerm.getInstance(), inst_model))) for term in equipotential.getTerms(): # A top-level output is a receiver of the net; an input/inout drives it. if (sinks is not None @@ -461,8 +512,7 @@ def _handle_design_request(u, request): if model.isAssign(): continue children.append(serialize_model( - model, instance.getID(), instance.getName(), - get_source_loc(instance))) + model, instance.getID(), instance.getName(), instance)) return [{ "response": req_type.replace("load_", "") + "_response", "gui_id": gui_id, @@ -470,14 +520,16 @@ def _handle_design_request(u, request): }] if req_type == "load_terms": - terms = [ - {"name": term.getName(), - "child_id": term.getID(), - "direction": direction_to_int(term.getDirection()), - "msb": term.getMSB() if isinstance(term, naja.SNLBusTerm) else None, - "lsb": term.getLSB() if isinstance(term, naja.SNLBusTerm) else None, - } for term in design.getTerms() - ] + terms = [] + for term in design.getTerms(): + is_bus = isinstance(term, naja.SNLBusTerm) + terms.append(with_sources({ + "name": term.getName(), + "child_id": term.getID(), + "direction": direction_to_int(term.getDirection()), + "msb": term.getMSB() if is_bus else None, + "lsb": term.getLSB() if is_bus else None, + }, bus_has_sources(term, term.getBits()) if is_bus else has_sources(term))) return [{"response": "terms_response", "gui_id": gui_id, "children": terms}] # load_nets @@ -489,6 +541,9 @@ def _handle_design_request(u, request): if isinstance(net, naja.SNLBusNet): entry["msb"] = net.getMSB() entry["lsb"] = net.getLSB() + with_sources(entry, bus_has_sources(net, net.getBits())) + else: + with_sources(entry, has_sources(net)) nets.append(entry) return [{"response": "nets_response", "gui_id": gui_id, "children": nets}] @@ -617,9 +672,9 @@ def _handle_resolve_instance(u, request): }, "has_instances": (design.hasNonPrimitiveInstances() or has_visible_primitive_instances(design)), - "source_loc": get_source_loc(chain[-1]), "terms": bit_terms_json(design), } + with_sources(reply["instance"], has_sources(chain[-1], design)) return [reply] @@ -647,15 +702,13 @@ def _handle_load_instance_internals(u, request): for instance in model.getNonPrimitiveInstances(): sub = instance.getModel() children.append(serialize_model( - sub, instance.getID(), instance.getName(), - get_source_loc(instance))) + sub, instance.getID(), instance.getName(), instance)) for instance in model.getPrimitiveInstances(): sub = instance.getModel() if sub.isAssign(): continue children.append(serialize_model( - sub, instance.getID(), instance.getName(), - get_source_loc(instance))) + sub, instance.getID(), instance.getName(), instance)) def emit_bit_net(bit_net, name, bit): pins = [] @@ -702,24 +755,165 @@ def emit_bit_net(bit_net, name, bit): }] -def _handle_load_source(u, request): - file = request.get("file", "") - line = request.get("line", 0) - text = "" - found = False +def request_sources(top, request): + """The source entries of the object a get_sources/load_source request + names -- same addressing as get_properties: "kind" (instance, term or + net), the (containing) instance by "id_path"/"path", "terminal"/"net" + and an optional "bit". Returns (subject, entries); entries is [] when + the object doesn't resolve or has no source information. + + An instance lists its own references (its instantiation, its origins) + and then its model's definition ("model_declaration"); the top design + ([]) its definition. A bus bit with no reference of its own lists its + bus's. Mirrors LocalSNLProvider.cpp's requestSources().""" + kind = request.get("kind", "instance") + design, chain = resolve_request_path(top, request) + if design is None: + return "", [] + at = display_path(chain) + if kind in ("term", "net"): + name = request.get("terminal" if kind == "term" else "net", "") + subject = f"{at}/{name}" if at else name + if not isinstance(name, str) or not name: + return subject, [] + obj = design.getTerm(name) if kind == "term" else design.getNet(name) + if obj is None: + return subject, [] + bit = request.get("bit") + if bit is None: + return subject, object_sources(obj) + bus_type = naja.SNLBusTerm if kind == "term" else naja.SNLBusNet + if not isinstance(obj, bus_type) or not isinstance(bit, int) or isinstance(bit, bool): + return subject, [] + bit_obj = obj.getBusTermBit(bit) if kind == "term" else obj.getBit(bit) + if bit_obj is None: + return subject, [] + return f"{subject}[{bit}]", object_sources(bit_obj) or object_sources(obj) + if not chain: + return top.getName(), object_sources(top) + entries = object_sources(chain[-1]) + model_declaration = design.getSourceDeclaration() + if model_declaration is not None: + entries.append(_source_reference_json("model_declaration", model_declaration)) + return at, entries + + +# Addressing keys of a get_sources/load_source request, echoed in replies so +# the viewer can name the same object again (load_source from the list). +_SOURCE_ADDRESS_KEYS = ("kind", "id_path", "path", "terminal", "net", "bit") + + +def _handle_get_sources(u, request): + top = u.getTopDesign() + subject, entries = request_sources(top, request) if top else ("", []) + reply = {"response": "sources_response", "subject": subject, "sources": entries} + reply.update({k: request[k] for k in _SOURCE_ADDRESS_KEYS if k in request}) + return [reply] + + +# A source file is sent whole up to this size; past it (a large gate-level +# netlist, typically) only the lines around the reference are, so a click +# doesn't push hundreds of megabytes to the viewer. Same values as +# LocalSNLProvider.cpp's kMaxSourceBytes/kSourceContextLines. +MAX_SOURCE_BYTES = 4 * 1024 * 1024 +SOURCE_CONTEXT_LINES = 500 + + +def _map_prefix(file, prefix, replacement): + # `file` with a leading `prefix` replaced, at a path boundary only + # (/a/syn maps /a/syn/x.v, not /a/synth/x.v); None if it doesn't start so. + if not prefix or not file.startswith(prefix): + return None + rest = file[len(prefix):] + if rest and not prefix.endswith(("/", "\\")) and rest[0] not in "/\\": + return None + return replacement + rest + + +@dataclass(frozen=True) +class SourcePaths: + """Where the host looks for referenced source files: `roots`, the + directories relative paths are tried under, and `maps`, (FROM, TO) + prefix substitutions for paths recorded on another machine (a synthesis + server's /home/... for a local checkout). See resolve_source_file(). + `loaded_at` (a time.time() value, None: unknown) is when the design was + loaded: naja recorded its source ranges then, so a file modified later + is reported "stale" -- its lines may have moved.""" + roots: tuple = () + maps: tuple = () + loaded_at: float = None + + +def resolve_source_file(file, source_paths=SourcePaths()): + """Where to read a referenced source file. First the path through each + source map whose FROM prefixes it, in order, if a file is there; then an + absolute path as is; a relative one under the first source root holding + it, else relative to the current directory. None if no file is there. + Paths are opaque producer strings (naja's contract): nothing is + canonicalized, prefixes compare as written. Mirrors + LocalSNLProvider::resolveSourceFile().""" + if not file: + return None + for prefix, replacement in source_paths.maps: + mapped = _map_prefix(file, prefix, replacement) + if mapped is not None and os.path.isfile(mapped): + return mapped + if os.path.isabs(file): + return file if os.path.isfile(file) else None + for root in source_paths.roots: + candidate = os.path.join(root, file) + if os.path.isfile(candidate): + return candidate + return file if os.path.isfile(file) else None + + +def read_source_window(path, line, end_line): + """(text, first_line, truncated): the whole file, or past + MAX_SOURCE_BYTES the lines within SOURCE_CONTEXT_LINES of + [line, end_line]. Undecodable bytes are replaced.""" + with open(path, "r", encoding="utf-8", errors="replace", newline="") as f: + if os.path.getsize(path) <= MAX_SOURCE_BYTES: + return f.read(), 1, False + first = max(1, line - SOURCE_CONTEXT_LINES) + last = max(line, end_line) + SOURCE_CONTEXT_LINES + lines = [text for number, text in enumerate(f, 1) if first <= number <= last] + return "".join(lines), first, True + + +def _handle_load_source(u, request, source_paths=SourcePaths()): + # The file of one source entry of a design object: the request names the + # object as get_sources does, plus the entry's "role" (and "index" for an + # origin). Only files the design references can be read this way -- the + # viewer never names a path itself. + top = u.getTopDesign() + _, entries = request_sources(top, request) if top else ("", []) + role = request.get("role") + entry = next((e for e in entries if e["role"] == role + and (role != "origin" or e.get("index") == request.get("index"))), None) + reply = {"response": "source_response", "found": False, "text": "", + "first_line": 1, "truncated": False} + if entry is None: + log.warning("load_source: no %s source reference for %s", role, + request.get("id_path", request.get("path"))) + return [reply] + reply.update({k: v for k, v in entry.items()}) + path = resolve_source_file(entry["file"], source_paths) + if path is None: + log.warning("Source file not found: %s", entry["file"]) + return [reply] try: - with open(file, "r") as f: - text = f.read() - found = True + text, first_line, truncated = read_source_window(path, entry["line"], entry["end_line"]) except OSError as e: - log.warning("Failed to read source file %s: %s", file, e) - return [{ - "response": "source_response", - "file": file, - "line": line, - "found": found, - "text": text - }] + log.warning("Failed to read source file %s: %s", path, e) + return [reply] + # "path": where the host read it, for a viewer on the same machine to + # open it in an editor. "stale": the file changed after the design was + # loaded, so the reference's lines may not match it any more. + stale = (source_paths.loaded_at is not None + and os.path.getmtime(path) > source_paths.loaded_at) + reply.update({"found": True, "text": text, "first_line": first_line, "truncated": truncated, + "path": os.path.abspath(path), "stale": stale}) + return [reply] def _handle_get_properties(u, request): @@ -818,22 +1012,25 @@ def _handle_get_properties(u, request): "trace_driver": _handle_trace_driver, "expand_instance_terms": _handle_expand_instance_terms, "load_instance_internals": _handle_load_instance_internals, - "load_source": _handle_load_source, + "get_sources": _handle_get_sources, "get_properties": _handle_get_properties, "resolve_instance": _handle_resolve_instance, "instance_selected": _handle_instance_selected, } -def handle_request(request): +def handle_request(request, source_paths=SourcePaths()): """Answer one decoded viewer request. Returns the list of response messages (dicts) to send back, in order. Transport-agnostic: the WebSocket server, the stdio server and the - notebook widget all wrap this. + notebook widget all wrap this. `source_paths`: where referenced source + files are looked up (SourcePaths, see resolve_source_file()). """ req_type = request.get("request") handler = _HANDLERS.get(req_type) + if req_type == "load_source": + handler = functools.partial(_handle_load_source, source_paths=source_paths) if handler is None: log.warning("Unknown request type: %s", req_type) return [] diff --git a/python/naja_schematic/server.py b/python/naja_schematic/server.py index ecd9644..7c37277 100644 --- a/python/naja_schematic/server.py +++ b/python/naja_schematic/server.py @@ -74,12 +74,14 @@ def _static_response(connection, request): return Response(http.HTTPStatus.OK, http.HTTPStatus.OK.phrase, headers, body) -async def serve(host="localhost", port=DEFAULT_PORT, diagnosis=None, open_browser=False): +async def serve(host="localhost", port=DEFAULT_PORT, diagnosis=None, open_browser=False, + source_roots=(), source_maps=()): """Serve the viewer page and its WebSocket on host:port, forever.""" from websockets.asyncio.server import serve as ws_serve from websockets.exceptions import ConnectionClosed - session = ViewerSession(diagnosis=diagnosis) + session = ViewerSession(diagnosis=diagnosis, source_roots=source_roots, + source_maps=source_maps) async def handle_connection(websocket): log.info("Client connected") @@ -149,10 +151,15 @@ class ViewerServer: connections, come from this server's own page or one of `allowed_origins` -- so another web page open in the same browser can't read the design. Pass token=None to turn the token off. + The viewer can only read source files the design's source references + name (Show Sources); relative ones are looked up in `source_roots`, + then the current directory, and `source_maps` rewrites (FROM, TO) path + prefixes for files recorded on another machine. """ def __init__(self, host="127.0.0.1", port=0, *, lock=None, run=None, - token=True, allowed_origins=(), diagnosis=None): + token=True, allowed_origins=(), diagnosis=None, source_roots=(), + source_maps=()): if lock is not None and run is not None: raise ValueError("pass lock or run, not both") if run is None: @@ -169,7 +176,8 @@ def __init__(self, host="127.0.0.1", port=0, *, lock=None, run=None, self._callbacks = ThreadPoolExecutor(max_workers=1, thread_name_prefix="naja-schematic-callbacks") self._session = ViewerSession(push=self._broadcast, run=run, diagnosis=diagnosis, - dispatch=self._dispatch_callback) + dispatch=self._dispatch_callback, + source_roots=source_roots, source_maps=source_maps) self._thread = None self._loop = None self._stopping = None @@ -445,12 +453,13 @@ def _send_all(self, message): # stdio server # --------------------------------------------------------------------------- -def serve_stdio(diagnosis=None, stdin=None, stdout=None): +def serve_stdio(diagnosis=None, stdin=None, stdout=None, source_roots=(), source_maps=()): """Answer one JSON request per stdin line with JSON lines on stdout, until stdin closes. Logging must not go to stdout in this mode.""" stdin = stdin or sys.stdin stdout = stdout or sys.stdout - session = ViewerSession(diagnosis=diagnosis) + session = ViewerSession(diagnosis=diagnosis, source_roots=source_roots, + source_maps=source_maps) for line in stdin: line = line.strip() if not line: @@ -464,6 +473,14 @@ def serve_stdio(diagnosis=None, stdin=None, stdout=None): # CLI # --------------------------------------------------------------------------- +def _source_map(value): + # FROM=TO, split at the first "=". + prefix, sep, replacement = value.partition("=") + if not sep or not prefix: + raise argparse.ArgumentTypeError(f"expected FROM=TO, got {value!r}") + return (prefix, replacement) + + def build_arg_parser(): parser = argparse.ArgumentParser( prog="naja-schematic", @@ -481,6 +498,15 @@ def build_arg_parser(): parser.add_argument("--diagnosis", metavar="FILE", help="Diagnosis JSON ({\"items\": [...]} or a bare array) to " "push to the viewer after the design loads") + parser.add_argument("--source-root", action="append", default=[], metavar="DIR", + dest="source_roots", + help="Directory to look up relative source file references in " + "(repeatable, tried in order, then the current directory)") + parser.add_argument("--source-map", action="append", default=[], metavar="FROM=TO", + dest="source_maps", type=_source_map, + help="Rewrite source paths starting with FROM to start with TO " + "(repeatable): for files recorded on another machine, e.g. " + "Yosys src paths of a synthesis server") parser.add_argument("--verbose", "-v", action="store_true", help="Log every request (to stderr)") parser.add_argument("--xilinx", action="store_true", @@ -490,6 +516,9 @@ def build_arg_parser(): parser.add_argument("--liberty", nargs="*", help="List of liberty files to load") parser.add_argument("--verilog", type=str, help="Verilog netlist to load") + parser.add_argument("--yosys-src", action="store_true", + help="Verilog only: decode Yosys (* src *) attributes into source " + "origins, so Show Sources leads from gates back to the RTL") parser.add_argument("--systemverilog", "--sv", nargs="+", metavar="FILE", help="SystemVerilog file(s) to load (elaborated with slang)") parser.add_argument("--flist", "-f", type=str, metavar="FILE", @@ -538,6 +567,7 @@ def load_design(args): log.info("Loading Verilog netlist: %s", args.verilog) config = netlist.VerilogConfig() config.allow_unknown_designs = args.allow_unknown_designs + config.yosys_source_attributes = args.yosys_src top = netlist.load_verilog(args.verilog, config=config) log.info("Design loaded: %s", top.get_name()) return top @@ -563,6 +593,8 @@ def main(argv=None): # Same restriction as the native standalone: the SystemVerilog loader # has no liberty hook, so fail loudly rather than silently ignore it. parser.error("--liberty is not supported with --systemverilog/--flist") + if is_sv and args.yosys_src: + parser.error("--yosys-src only applies to --verilog") if args.stdio and args.open: parser.error("--open does not apply to --stdio") @@ -590,10 +622,12 @@ def main(argv=None): load_design(args) if args.stdio: - serve_stdio(diagnosis, stdout=protocol_out) + serve_stdio(diagnosis, stdout=protocol_out, source_roots=args.source_roots, + source_maps=args.source_maps) return try: - asyncio.run(serve(args.host, args.port, diagnosis, args.open)) + asyncio.run(serve(args.host, args.port, diagnosis, args.open, args.source_roots, + args.source_maps)) except KeyboardInterrupt: pass diff --git a/python/naja_schematic/session.py b/python/naja_schematic/session.py index e2eea4b..6fc1d5f 100644 --- a/python/naja_schematic/session.py +++ b/python/naja_schematic/session.py @@ -21,9 +21,11 @@ the next design generation, and requests the viewer made for an older one are dropped (see ViewerSession). """ +import dataclasses import json import logging import threading +import time from najaeda import naja @@ -144,6 +146,9 @@ class ViewerSession: `dispatch(fn)` calls fn() -- a selection notification -- eventually, in the order given (default: right away, on the calling thread). `diagnosis` pre-loads diagnosis items, sent after the first root load. + `source_roots` lists the directories relative source file references + are looked up in, `source_maps` (FROM, TO) path prefixes to rewrite for + files recorded on another machine (see protocol.resolve_source_file()). Design generations: the session numbers the designs it has shown, starting at 0; design_changed() moves to the next one. Every message to @@ -158,8 +163,13 @@ class ViewerSession: predating generations) are answered for the current design. """ - def __init__(self, push=None, run=None, diagnosis=None, dispatch=None): + def __init__(self, push=None, run=None, diagnosis=None, dispatch=None, source_roots=(), + source_maps=()): self._push = push + # Stamped now and at each design_changed(): the design the viewer + # shows was loaded by then, so a source file modified later is stale. + self._source_paths = protocol.SourcePaths( + tuple(source_roots), tuple((str(a), str(b)) for a, b in source_maps), time.time()) self._run = run or lock_runner(threading.RLock()) self._dispatch = dispatch or (lambda fn: fn()) self._state_lock = threading.Lock() # generation, pushes, selection; not the universe @@ -220,7 +230,7 @@ def handle(): if kind == "svg_exported": svg = request.get("svg") return generation, svg if isinstance(svg, str) else "", pushes - return generation, protocol.handle_request(request), pushes + return generation, protocol.handle_request(request, self._source_paths), pushes generation, result, pushes = self._run(handle) if result is None: @@ -359,6 +369,7 @@ def design_changed(self, diagnosis=None, instance=None): with self._state_lock: self._generation += 1 generation = self._generation + self._source_paths = dataclasses.replace(self._source_paths, loaded_at=time.time()) self._diagnosis_push, self._focus_push = diagnosis_push, focus_push had_selection = self.selected_id_path is not None or self.selected_path is not None self.selected_path = self.selected_id_path = None diff --git a/python/naja_schematic/widget.py b/python/naja_schematic/widget.py index 0aebdb1..2e123ed 100644 --- a/python/naja_schematic/widget.py +++ b/python/naja_schematic/widget.py @@ -46,7 +46,7 @@ class Schematic(anywidget.AnyWidget): # Kernel-side only, like the selection. svg = traitlets.Unicode(default_value=None, allow_none=True) - def __init__(self, **kwargs): + def __init__(self, source_roots=(), source_maps=(), **kwargs): # Read before AnyWidget.__init__, which turns it into a synced trait. # Not a class attribute: that would read the bundle at import time # and fail the import when no bundle is installed. @@ -54,7 +54,8 @@ def __init__(self, **kwargs): super().__init__(**kwargs) # Requests are answered on the kernel thread, like the cells that # edit the design, so the session's default runner is enough. - self._session = ViewerSession(push=self._send_json) + self._session = ViewerSession(push=self._send_json, source_roots=source_roots, + source_maps=source_maps) self._session.on_select(self._on_viewer_selection) self.on_msg(self._on_viewer_message) @@ -146,7 +147,7 @@ def handler(_change): return handler -def show(instance=None, *, height=600, diagnosis=None): +def show(instance=None, *, height=600, diagnosis=None, source_roots=(), source_maps=()): """Display a view of the design currently loaded with najaeda. Evaluate it as the last expression of a cell (or pass it to @@ -154,9 +155,13 @@ def show(instance=None, *, height=600, diagnosis=None): so run show() again after editing the design to get a fresh view. `instance` starts the view on one instance, e.g. show(inst), show(["u1", "u2"]) or show([3, 0]) (see Schematic.show_instance). + `source_roots` lists the directories relative source file references + (Show Sources) are looked up in, before the current directory; + `source_maps`, (FROM, TO) path prefixes to rewrite for files recorded on + another machine, e.g. [("/home/ci/flow", "/work/flow")]. """ # (anywidget itself turns on Colab's custom widget manager.) - view = Schematic(height=height) + view = Schematic(height=height, source_roots=source_roots, source_maps=source_maps) if diagnosis is not None: view.annotate(diagnosis) if instance is not None: diff --git a/python/notebooks/getting_started.ipynb b/python/notebooks/getting_started.ipynb index 8f18fbf..cb712a4 100644 --- a/python/notebooks/getting_started.ipynb +++ b/python/notebooks/getting_started.ipynb @@ -113,12 +113,40 @@ "view" ] }, + { + "cell_type": "markdown", + "id": "1c1cac2e", + "metadata": {}, + "source": [ + "## 3. Back to the RTL\n", + "naja remembers where each object comes from in `rgb_pwm.sv`: an instance's instantiation and its module's definition, and for every gate slang generated, the RTL expression it implements.\n", + "\n", + "In the viewer: right-click `u_red` in the tree → *Show Sources*. The **Source** tab lists its references; the first opens with its range highlighted, click another to open it. Then trace the comparator cone (section 2), right-click one of its gates → *Show Sources*: it points at `cnt < duty`. Terms and nets have sources too.\n", + "\n", + "The same references from Python, through najaeda:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ea896dd", + "metadata": {}, + "outputs": [], + "source": [ + "where = lambda ref: f\"{ref.range.file}:{ref.range.line}:{ref.range.column}\"\n", + "red = top.get_child_instance(\"u_red\")\n", + "print(\"u_red is instantiated at\", where(red.get_source_declaration()))\n", + "print(\"its module is defined at\", where(red.get_model_source_declaration()))\n", + "gate = next(g for g in red.get_child_instances() if g.get_source_origins())\n", + "print(f\"a {gate.get_model_name()} gate of u_red comes from\", where(gate.get_source_origins()[0]))" + ] + }, { "cell_type": "markdown", "id": "c0ec9a0f", "metadata": {}, "source": [ - "## 3. Drive the view from Python\n", + "## 4. Drive the view from Python\n", "Point the view at an instance, by name path or with a najaeda `Instance`. It opens the tree down to the instance, selects it and draws it with all its pins open, ready to extend." ] }, @@ -148,7 +176,7 @@ "id": "c1a67ede", "metadata": {}, "source": [ - "## 4. Read the selection back\n", + "## 5. Read the selection back\n", "Click an instance in the tree or a box in the schematic. The callback prints on each click, and the next cell gives the selection as a najaeda `Instance`." ] }, @@ -179,7 +207,7 @@ "id": "f3c8f456", "metadata": {}, "source": [ - "## 5. Export the schematic as SVG\n", + "## 6. Export the schematic as SVG\n", "`export_svg()` asks the view for what its schematic shows, as a standalone SVG: the whole sheet, with text kept as text. It's ready to drop into a doc or a PR. The view answers after this cell has run, so the SVG appears below and in `rgb_pwm.svg` once the view has drawn something. Trace the comparator cone above (section 2), then run the cell again to export it.\n", "\n", "The viewer's own *File > Export Schematic as SVG...* menu downloads the same file." @@ -202,7 +230,7 @@ "id": "6bc2862a", "metadata": {}, "source": [ - "## 6. Open a view on one instance\n", + "## 7. Open a view on one instance\n", "A new view, started on the blue channel." ] }, @@ -221,7 +249,7 @@ "id": "d630c719", "metadata": {}, "source": [ - "## 7. Edit the design, then view it again\n", + "## 8. Edit the design, then view it again\n", "Views read the live netlist, so a fresh view shows the edit: `u_led_red` instead of `u_red`. To update an existing view instead, call `view.design_changed()`." ] }, @@ -241,7 +269,7 @@ "id": "bcbfaf10", "metadata": {}, "source": [ - "## 8. A real design: tinyrocket\n", + "## 9. A real design: tinyrocket\n", "A gate-level RISC-V core is in its own notebook, since najaeda holds one design at a time: [open the tinyrocket notebook in Colab](https://colab.research.google.com/github/keplertech/naja-schematic/blob/main/python/notebooks/tinyrocket.ipynb)." ] }, diff --git a/python/notebooks/tinyrocket.ipynb b/python/notebooks/tinyrocket.ipynb index 8ea981d..2886200 100644 --- a/python/notebooks/tinyrocket.ipynb +++ b/python/notebooks/tinyrocket.ipynb @@ -45,11 +45,18 @@ "outputs": [], "source": [ "!curl -sSfL https://github.com/najaeda/naja/archive/refs/heads/main.zip -o naja-main.zip && unzip -q -o naja-main.zip \"naja-main/tutorials/benchmarks/*\"\n", + "# The RTL tinyrocket was synthesized from: its gates' Yosys `src` attributes\n", + "# point there, relative to this directory (OpenROAD-flow-scripts, pinned).\n", + "orfs = \"https://raw.githubusercontent.com/The-OpenROAD-Project/OpenROAD-flow-scripts/43ca564bb3d4167cb62f5008c93b1ce96c7438b0/flow\"\n", + "!curl -sSfL -o freechips.rocketchip.system.TinyConfig.v {orfs}/designs/src/tinyRocket/freechips.rocketchip.system.TinyConfig.v\n", + "!curl -sSfL --create-dirs -o flow/platforms/nangate45/cells_adders.v {orfs}/platforms/nangate45/cells_adders.v\n", "bench = \"naja-main/tutorials/benchmarks\"\n", "from najaeda import netlist\n", "netlist.reset()\n", "netlist.load_liberty([f\"{bench}/liberty/tutorial_cells.lib\", f\"{bench}/liberty/fakeram45_64x32.lib\"])\n", - "top = netlist.load_verilog(f\"{bench}/verilog/tinyrocket/tinyrocket.v\")\n", + "config = netlist.VerilogConfig()\n", + "config.yosys_source_attributes = True # read the Yosys `src` attributes as source origins\n", + "top = netlist.load_verilog(f\"{bench}/verilog/tinyrocket/tinyrocket.v\", config=config)\n", "top.get_name()" ] }, @@ -61,7 +68,8 @@ "## Explore it\n", "- Expand the tree on the left: instances, terms and nets load on demand.\n", "- Right-click a flop's `D` pin → *Trace to Driver* to draw the logic cone feeding it. It stops at other flops and the top-level inputs.\n", - "- Click a hollow pin to pull in the net behind it. Scroll to zoom and drag to pan." + "- Click a hollow pin to pull in the net behind it. Scroll to zoom and drag to pan.\n", + "- Right-click a gate → *Show Sources*: its line in `tinyrocket.v`, then the Yosys origins it was synthesized from, in the RTL or the adder cells. Click one in the **Source** tab to open it at the highlighted range." ] }, { @@ -75,6 +83,43 @@ "view" ] }, + { + "cell_type": "markdown", + "id": "b374d481", + "metadata": {}, + "source": [ + "## Back to the RTL\n", + "Which RTL made a gate? Its Yosys origins say, file and range, through najaeda. A flop, for instance:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd1e2374", + "metadata": {}, + "outputs": [], + "source": [ + "from itertools import islice\n", + "where = lambda ref: f\"{ref.range.file}:{ref.range.line}:{ref.range.column}\"\n", + "\n", + "def first_gate_from_rtl(inst):\n", + " for child in inst.get_child_instances():\n", + " if not child.is_leaf():\n", + " found = first_gate_from_rtl(child)\n", + " if found:\n", + " return found\n", + " elif any(o.range.file.endswith(\"TinyConfig.v\") for o in child.get_source_origins()):\n", + " return child\n", + "\n", + "gate = first_gate_from_rtl(top)\n", + "rtl = next(o for o in gate.get_source_origins() if o.range.file.endswith(\"TinyConfig.v\"))\n", + "print(gate.get_name(), f\"({gate.get_model_name()})\")\n", + "print(\" declared in the netlist at\", where(gate.get_source_declaration()))\n", + "print(\" made from the RTL at \", where(rtl))\n", + "with open(rtl.range.file) as f:\n", + " print(\"\".join(islice(f, rtl.range.line - 1, rtl.range.line + 2)))" + ] + }, { "cell_type": "markdown", "id": "9ae318cd", diff --git a/python/pyproject.toml b/python/pyproject.toml index 52b051d..facf0f9 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -11,7 +11,7 @@ license = "Apache-2.0" authors = [{name = "Naja Authors", email = "contact@keplertech.io"}] requires-python = ">=3.10" dependencies = [ - "najaeda>=0.7.27", + "najaeda>=0.7.29", "websockets>=13", "anywidget>=0.9", ] diff --git a/python/tests/protocol_cases.py b/python/tests/protocol_cases.py new file mode 100644 index 0000000..c828d58 --- /dev/null +++ b/python/tests/protocol_cases.py @@ -0,0 +1,158 @@ +"""Runner for the wire-protocol case files shared with the C++ side +(tests/data/*_protocol_cases.json, also run by +tests/provider/LocalSNLProviderProtocolTest.cpp), so protocol.py and +LocalSNLProvider are held to the same answers. See a case file's "comment" +for the format. +""" +import json +import os +from pathlib import Path + +from najaeda import naja + +CASES_DIR = Path(__file__).resolve().parents[2] / "tests" / "data" + +_DIRECTIONS = {"input": naja.SNLTerm.Direction.Input, + "output": naja.SNLTerm.Direction.Output, + "inout": naja.SNLTerm.Direction.InOut} + + +def load_spec(name): + return json.loads((CASES_DIR / name).read_text()) + + +def _create_terms(design, terms): + # [name, direction] for a scalar term, [name, direction, msb, lsb] for a bus. + for term in terms: + if len(term) == 4: + naja.SNLBusTerm.create(design, _DIRECTIONS[term[1]], term[2], term[3], term[0]) + else: + naja.SNLScalarTerm.create(design, _DIRECTIONS[term[1]], term[0]) + + +def build_design(spec): + """Build the spec'd netlist with the raw naja API in a DB of its own; + returns {design name: design}. Instances are created in list order, so + their ids are their list indices.""" + u = naja.NLUniverse.get() + db = naja.NLDB.create(u) + designs = {} + prims = naja.NLLibrary.createPrimitives(db, "case_prims") + for p in spec["primitives"]: + d = naja.SNLDesign.createPrimitive(prims, p["name"]) + _create_terms(d, p["terms"]) + designs[p["name"]] = d + lib = naja.NLLibrary.create(db, "case_work") + for spec_d in spec["designs"]: + d = naja.SNLDesign.create(lib, spec_d["name"]) + _create_terms(d, spec_d["terms"]) + instances = [] + for index, (name, model) in enumerate(spec_d["instances"]): + inst = (naja.SNLInstance.create(d, designs[model], name) if name + else naja.SNLInstance.create(d, designs[model])) + assert inst.getID() == index + instances.append(inst) + for n in spec_d["nets"]: + if "msb" in n: + naja.SNLBusNet.create(d, n["msb"], n["lsb"], n["name"]) + continue + net = naja.SNLScalarNet.create(d, n["name"]) + if "term" in n: + d.getScalarTerm(n["term"]).setNet(net) + for index, term in n.get("pins", []): + inst = instances[index] + inst.getInstTerm(inst.getModel().getScalarTerm(term)).setNet(net) + designs[spec_d["name"]] = d + return designs + + +def load_verilog_design(spec, root): + """Load the spec'd gate-level Verilog (`spec["verilog"]`, relative to + `root`, which is the current directory while loading so recorded paths + stay relative) into a DB of its own, Yosys src decoding on; returns + {design name: design} for the top and every model below it.""" + db = naja.NLDB.create(naja.NLUniverse.get()) + previous = os.getcwd() + os.chdir(root) + try: + db.loadVerilog([spec["verilog"]], yosys_source_attributes=True) + finally: + os.chdir(previous) + top = next(d for lib in db.getLibraries() for d in lib.getSNLDesigns() + if d.getName() == spec["top"]) + designs = {} + pending = [top] + while pending: + design = pending.pop() + if design.getName() not in designs: + designs[design.getName()] = design + pending.extend(inst.getModel() for inst in design.getInstances()) + return designs + + +def design_ref(design): + return {"db_id": design.getDB().getID(), "library_id": design.getLibrary().getID(), + "design_id": design.getID()} + + +def substitute(value, designs): + if isinstance(value, str) and value.startswith("$design:"): + return design_ref(designs[value[len("$design:"):]]) + if isinstance(value, dict): + return {k: substitute(v, designs) for k, v in value.items()} + if isinstance(value, list): + return [substitute(v, designs) for v in value] + return value + + +def matches_unordered(expected, actual, where): + # Each expected element matched to a different actual element. + if not isinstance(actual, list) or len(actual) != len(expected): + return f"{where}: expected {expected!r}, got {actual!r}" + left = list(actual) + for e in expected: + hit = next((i for i, a in enumerate(left) if matches(e, a) is None), None) + if hit is None: + return f"{where}: nothing left matches {e!r} in {actual!r}" + left.pop(hit) + return None + + +def matches(expected, actual, where="reply"): + """Every key of `expected` is in `actual` with that value (objects + recursively, lists element-wise with the same length). Returns the + first mismatch as a string, or None.""" + if isinstance(expected, dict): + if not isinstance(actual, dict): + return f"{where}: expected an object, got {actual!r}" + for key, value in expected.items(): + if key not in actual: + return f"{where}: missing {key!r}" + if (err := matches(value, actual[key], f"{where}.{key}")): + return err + return None + if isinstance(expected, list): + if not isinstance(actual, list) or len(actual) != len(expected): + return f"{where}: expected {expected!r}, got {actual!r}" + for i, (e, a) in enumerate(zip(expected, actual)): + if (err := matches(e, a, f"{where}[{i}]")): + return err + return None + # bool is an int in Python: don't let True match 1. + if expected != actual or isinstance(expected, bool) != isinstance(actual, bool): + return f"{where}: expected {expected!r}, got {actual!r}" + return None + + +def check_case(case, replies): + """Assert that `replies` (dicts, already through JSON) are what `case` + expects.""" + expect = [dict(e) for e in case["expect"]] + for key in case.get("unordered", []): + expected_list = expect[0].pop(key) + err = matches_unordered(expected_list, replies[0].get(key) if replies else None, + f"replies[0].{key}") + assert err is None, err + assert (err := matches(expect, replies, "replies")) is None, err + for key in case.get("absent", []): + assert key not in replies[0], f"unexpected {key!r} in {replies[0]}" diff --git a/python/tests/test_anonymous_instances.py b/python/tests/test_anonymous_instances.py index 33f2ea4..e8fd6ca 100644 --- a/python/tests/test_anonymous_instances.py +++ b/python/tests/test_anonymous_instances.py @@ -6,54 +6,15 @@ and LocalSNLProvider are held to the same answers. """ import json -from pathlib import Path import pytest from najaeda import naja from naja_schematic import protocol from naja_schematic.protocol import handle_request +from protocol_cases import build_design, check_case, load_spec, substitute -CASES_FILE = Path(__file__).resolve().parents[2] / "tests" / "data" / "anonymous_protocol_cases.json" -SPEC = json.loads(CASES_FILE.read_text()) - -_DIRECTIONS = {"input": naja.SNLTerm.Direction.Input, - "output": naja.SNLTerm.Direction.Output, - "inout": naja.SNLTerm.Direction.InOut} - - -def build_design(spec): - """Build the spec'd netlist with the raw naja API in a DB of its own; - returns {design name: design}. Instances are created in list order, so - their ids are their list indices.""" - u = naja.NLUniverse.get() - db = naja.NLDB.create(u) - designs = {} - prims = naja.NLLibrary.createPrimitives(db, "anon_prims") - for p in spec["primitives"]: - d = naja.SNLDesign.createPrimitive(prims, p["name"]) - for name, direction in p["terms"]: - naja.SNLScalarTerm.create(d, _DIRECTIONS[direction], name) - designs[p["name"]] = d - lib = naja.NLLibrary.create(db, "anon_work") - for spec_d in spec["designs"]: - d = naja.SNLDesign.create(lib, spec_d["name"]) - for name, direction in spec_d["terms"]: - naja.SNLScalarTerm.create(d, _DIRECTIONS[direction], name) - instances = [] - for index, (name, model) in enumerate(spec_d["instances"]): - inst = (naja.SNLInstance.create(d, designs[model], name) if name - else naja.SNLInstance.create(d, designs[model])) - assert inst.getID() == index - instances.append(inst) - for n in spec_d["nets"]: - net = naja.SNLScalarNet.create(d, n["name"]) - d.getScalarTerm(n["term"]).setNet(net) - for index, term in n["pins"]: - inst = instances[index] - inst.getInstTerm(inst.getModel().getScalarTerm(term)).setNet(net) - designs[spec_d["name"]] = d - return designs +SPEC = load_spec("anonymous_protocol_cases.json") @pytest.fixture(scope="module") @@ -68,74 +29,11 @@ def anon(top): u.setTopDesign(previous) -def design_ref(design): - return {"db_id": design.getDB().getID(), "library_id": design.getLibrary().getID(), - "design_id": design.getID()} - - -def substitute(value, designs): - if isinstance(value, str) and value.startswith("$design:"): - return design_ref(designs[value[len("$design:"):]]) - if isinstance(value, dict): - return {k: substitute(v, designs) for k, v in value.items()} - if isinstance(value, list): - return [substitute(v, designs) for v in value] - return value - - -def matches_unordered(expected, actual, where): - # Each expected element matched to a different actual element. - if not isinstance(actual, list) or len(actual) != len(expected): - return f"{where}: expected {expected!r}, got {actual!r}" - left = list(actual) - for e in expected: - hit = next((i for i, a in enumerate(left) if matches(e, a) is None), None) - if hit is None: - return f"{where}: nothing left matches {e!r} in {actual!r}" - left.pop(hit) - return None - - -def matches(expected, actual, where="reply"): - """Every key of `expected` is in `actual` with that value (objects - recursively, lists element-wise with the same length). Returns the - first mismatch as a string, or None.""" - if isinstance(expected, dict): - if not isinstance(actual, dict): - return f"{where}: expected an object, got {actual!r}" - for key, value in expected.items(): - if key not in actual: - return f"{where}: missing {key!r}" - if (err := matches(value, actual[key], f"{where}.{key}")): - return err - return None - if isinstance(expected, list): - if not isinstance(actual, list) or len(actual) != len(expected): - return f"{where}: expected {expected!r}, got {actual!r}" - for i, (e, a) in enumerate(zip(expected, actual)): - if (err := matches(e, a, f"{where}[{i}]")): - return err - return None - # bool is an int in Python: don't let True match 1. - if expected != actual or isinstance(expected, bool) != isinstance(actual, bool): - return f"{where}: expected {expected!r}, got {actual!r}" - return None - - @pytest.mark.parametrize("case", SPEC["cases"], ids=[c["name"] for c in SPEC["cases"]]) def test_shared_protocol_case(anon, case): replies = handle_request(substitute(case["request"], anon)) # Through JSON, like the wire, so both sides compare the same thing. - replies = json.loads(json.dumps(replies)) - expect = [dict(e) for e in case["expect"]] - for key in case.get("unordered", []): - expected_list = expect[0].pop(key) - err = matches_unordered(expected_list, replies[0].get(key) if replies else None, - f"replies[0].{key}") - assert err is None, err - assert (err := matches(expect, replies, "replies")) is None, err - for key in case.get("absent", []): - assert key not in replies[0], f"unexpected {key!r} in {replies[0]}" + check_case(case, json.loads(json.dumps(replies))) def test_resolved_ids_address_the_same_instance(anon): diff --git a/python/tests/test_notebook.py b/python/tests/test_notebook.py index bf0fb30..b09c612 100644 --- a/python/tests/test_notebook.py +++ b/python/tests/test_notebook.py @@ -62,6 +62,20 @@ def _ask(request): assert view.selected_path == ["u_green"] assert view.selected.get_name() == "u_green" +# Show Sources, in packages that have it (from 0.2.0; the colab job plays +# the current PyPI release, which may predate it): u_red's instantiation +# and module definition, and its file read from the notebook's directory. +from naja_schematic import protocol as _protocol +if hasattr(_protocol, "request_sources"): + (_src,) = _ask({"request": "get_sources", "kind": "instance", "path": ["u_red"]}) + _roles = [s["role"] for s in _src["sources"]] + assert _roles[0] == "declaration" and "model_declaration" in _roles, _src + (_txt,) = _ask({"request": "load_source", "kind": "instance", "path": ["u_red"], + "role": "declaration"}) + assert _txt["found"] and not _txt["stale"], _txt + assert "u_red" in _txt["text"].splitlines()[_txt["line"] - _txt["first_line"]], _txt + del _sent[:] + _svgs = [] view.export_svg(callback=_svgs.append) assert [m["response"] for m in _sent] == ["export_svg"], _sent diff --git a/python/tests/test_source_references.py b/python/tests/test_source_references.py new file mode 100644 index 0000000..d02d421 --- /dev/null +++ b/python/tests/test_source_references.py @@ -0,0 +1,209 @@ +"""Source references across the protocol: the has_sources flags, get_sources +and load_source (see docs/source_information.md). + +The request/reply cases live in tests/data/source_protocol_cases.json, +shared with tests/provider/LocalSNLProviderProtocolTest.cpp, so protocol.py +and LocalSNLProvider are held to the same answers. +""" +import json +import os +import time + +import pytest +from najaeda import naja + +from naja_schematic import protocol +from naja_schematic.protocol import handle_request +from naja_schematic.server import build_arg_parser, main +from naja_schematic.session import ViewerSession +from protocol_cases import CASES_DIR, check_case, load_spec, load_verilog_design, substitute + +SPEC = load_spec("source_protocol_cases.json") +SOURCE_ROOT = str(CASES_DIR / SPEC["source_root"]) +SOURCE_PATHS = protocol.SourcePaths( + (SOURCE_ROOT,), tuple((prefix, str(CASES_DIR / to)) for prefix, to in SPEC["source_map"])) + + +@pytest.fixture(scope="module") +def sources(top): + # NLUniverse is process-global: build next to the session's design and + # make it the top design for this module only. + u = naja.NLUniverse.get() + previous = u.getTopDesign() + designs = load_verilog_design(SPEC["design"], SOURCE_ROOT) + u.setTopDesign(designs[SPEC["design"]["top"]]) + yield designs + u.setTopDesign(previous) + + +@pytest.mark.parametrize("case", SPEC["cases"], ids=[c["name"] for c in SPEC["cases"]]) +def test_shared_protocol_case(sources, case): + replies = handle_request(substitute(case["request"], sources), SOURCE_PATHS) + # Through JSON, like the wire, so both sides compare the same thing. + check_case(case, json.loads(json.dumps(replies))) + + +class _NoSources: + def hasSourceReferences(self): + return False + + def hasSourceLoc(self): + return False + + def getSourceDeclaration(self): + return None + + +def test_flag_is_only_sent_when_set(): + assert "has_sources" not in protocol.with_sources({}, protocol.has_sources(_NoSources())) + assert protocol.with_sources({}, True) == {"has_sources": True} + + +def test_resolved_instance_and_schematic_boxes_carry_the_flag(sources): + (resolved,) = handle_request({"request": "resolve_instance", "id_path": [0, 0]}) + assert resolved["instance"]["has_sources"] is True + # g0's output on net y: one box, for g0. + (net,) = handle_request({"request": "load_equipotential", "path": [0, 0], "term_id": 0}) + (occ,) = net["occurrences"] + assert occ["path"][-1][0] == "g0" and occ["has_sources"] is True + assert "source_loc" not in occ + + +class _LegacyOnly: + # What an object loaded by a frontend predating typed references + # answers: nothing but the unclassified legacy location. + def hasSourceReferences(self): + return False + + def getSourceDeclaration(self): + return None + + def getSourceOrigins(self): + return [] + + def hasSourceLoc(self): + return True + + def getSourceLoc(self): + return ("a.sv", 3, 5, 4, 0) + + +def test_legacy_location_when_no_typed_reference(): + assert protocol.object_sources(_LegacyOnly()) == [ + {"role": "legacy", "file": "a.sv", "line": 3, "column": 5, "end_line": 4, + "end_column": 0, "provider": "", "language": "", "representation": ""}] + assert protocol.has_sources(_LegacyOnly()) + + +def test_resolve_source_file(tmp_path, monkeypatch): + first, second = tmp_path / "first", tmp_path / "second" + (first / "rtl").mkdir(parents=True) + (second / "rtl").mkdir(parents=True) + (second / "rtl" / "a.v").write_text("second") + (first / "rtl" / "b.v").write_text("first") + (second / "rtl" / "b.v").write_text("second") + roots = protocol.SourcePaths((str(first), str(second))) + assert protocol.resolve_source_file("rtl/a.v", roots) == str(second / "rtl" / "a.v") + assert protocol.resolve_source_file("rtl/b.v", roots) == str(first / "rtl" / "b.v") # in order + absolute = str(second / "rtl" / "a.v") + assert protocol.resolve_source_file(absolute, protocol.SourcePaths((str(first),))) == absolute + assert protocol.resolve_source_file("rtl/c.v", roots) is None + assert protocol.resolve_source_file("", roots) is None + monkeypatch.chdir(second) + assert protocol.resolve_source_file("rtl/a.v") == "rtl/a.v" # the current directory last + + +def test_source_maps(tmp_path): + checkout = tmp_path / "checkout" + (checkout / "rtl").mkdir(parents=True) + (checkout / "rtl" / "a.v").write_text("x") + local = str(checkout / "rtl" / "a.v") + maps = lambda *m: protocol.SourcePaths(maps=m) # noqa: E731 + # A prefix of the recorded path, at a path boundary, with or without "/". + assert protocol.resolve_source_file("/synth/flow/rtl/a.v", maps(("/synth/flow", str(checkout)))) == local + assert protocol.resolve_source_file("/synth/flow/rtl/a.v", maps(("/synth/flow/", str(checkout) + "/"))) == local + # Not in the middle of a name: /synth/flowX is another directory. + assert protocol.resolve_source_file("/synth/flowX/rtl/a.v", maps(("/synth/flow", str(checkout)))) is None + # In order; a map that leads nowhere falls through to the next one. + assert protocol.resolve_source_file( + "/synth/flow/rtl/a.v", maps(("/synth", "/nowhere"), ("/synth/flow", str(checkout)))) == local + # Relative paths too, and a map is tried before roots. + assert protocol.resolve_source_file( + "build/rtl/a.v", protocol.SourcePaths(("/nowhere",), (("build", str(checkout)),))) == local + + +def test_large_file_sends_the_lines_around_the_reference(tmp_path, monkeypatch): + path = tmp_path / "big.v" + path.write_text("".join(f"line {n}\n" for n in range(1, 2001))) + monkeypatch.setattr(protocol, "MAX_SOURCE_BYTES", 100) + monkeypatch.setattr(protocol, "SOURCE_CONTEXT_LINES", 10) + text, first_line, truncated = protocol.read_source_window(str(path), 1000, 1002) + assert truncated and first_line == 990 + assert text.splitlines() == [f"line {n}" for n in range(990, 1013)] + # Near the top, the window starts at line 1; an unknown end is the line. + text, first_line, _ = protocol.read_source_window(str(path), 3, 0) + assert first_line == 1 and text.splitlines()[-1] == "line 13" + + +def test_session_reads_sources_under_its_roots(sources): + request = json.dumps({"request": "load_source", "kind": "instance", + "id_path": [0, 0], "role": "declaration"}) + (reply,) = ViewerSession(source_roots=[SOURCE_ROOT]).answer(request) + assert json.loads(reply)["found"] is True + (reply,) = ViewerSession().answer(request) + assert json.loads(reply)["found"] is False + + +def test_cli_source_roots(): + args = build_arg_parser().parse_args( + ["--verilog", "d.v", "--source-root", "rtl", "--source-root", "/abs"]) + assert args.source_roots == ["rtl", "/abs"] + assert build_arg_parser().parse_args(["--verilog", "d.v"]).source_roots == [] + + +def test_stale_files(sources, tmp_path): + # A file modified after the design was loaded is reported stale: its + # ranges were recorded before the change. + (tmp_path / "rtl").mkdir() + copy = tmp_path / "rtl" / "alu.v" + copy.write_text((CASES_DIR / "sources" / "rtl" / "alu.v").read_text()) + request = json.dumps({"request": "load_source", "kind": "instance", + "id_path": [0, 0], "role": "origin", "index": 0}) + session = ViewerSession(source_roots=[str(tmp_path)]) + stale = lambda: json.loads(session.answer(request)[0])["stale"] # noqa: E731 + assert stale() is False + later = time.time() + 5 + os.utime(copy, (later, later)) + assert stale() is True + # design_changed() starts a new design: its ranges are newer than the file. + os.utime(copy, (time.time(), time.time())) + session.design_changed() + assert stale() is False + # Unknown load time (a bare SourcePaths): never stale. + (reply,) = handle_request(json.loads(request), protocol.SourcePaths((str(tmp_path),))) + assert reply["found"] is True and reply["stale"] is False + + +def test_cli_source_maps(): + args = build_arg_parser().parse_args( + ["--verilog", "d.v", "--source-map", "/synth/flow=/work/flow", "--source-map", "a=b=c"]) + assert args.source_maps == [("/synth/flow", "/work/flow"), ("a", "b=c")] # split at the first "=" + for bad in ("nomap", "=/work"): + with pytest.raises(SystemExit): + build_arg_parser().parse_args(["--verilog", "d.v", "--source-map", bad]) + + +def test_session_source_maps(sources): + request = json.dumps({"request": "load_source", "kind": "instance", + "id_path": [0, 2], "role": "origin", "index": 0}) + (reply,) = ViewerSession(source_maps=[("/synth/machine", SOURCE_ROOT)]).answer(request) + assert json.loads(reply)["found"] is True + (reply,) = ViewerSession().answer(request) + assert json.loads(reply)["found"] is False + + +def test_cli_yosys_src_is_opt_in(): + assert build_arg_parser().parse_args(["--verilog", "d.v", "--yosys-src"]).yosys_src + assert not build_arg_parser().parse_args(["--verilog", "d.v"]).yosys_src + with pytest.raises(SystemExit): + main(["--sv", "a.sv", "--yosys-src"]) diff --git a/src/AppLogic.cpp b/src/AppLogic.cpp index 2ad69a6..35d52c3 100644 --- a/src/AppLogic.cpp +++ b/src/AppLogic.cpp @@ -27,6 +27,7 @@ using json = nlohmann::json; #include "TraceStore.h" #include "TraceView.h" #include "PropertiesView.h" +#include "CousineFont.h" #include "DroidSansFont.h" #ifndef __EMSCRIPTEN__ @@ -60,6 +61,9 @@ void setupFonts() { ImGuiIO& io = ImGui::GetIO(); io.Fonts->AddFontFromMemoryCompressedTTF( DroidSans_compressed_data, DroidSans_compressed_size, kBaseFontSizePx); + // Monospace, for code in the Source tab; DroidSans above stays the default. + SourceView::setFont(io.Fonts->AddFontFromMemoryCompressedTTF( + Cousine_compressed_data, Cousine_compressed_size, kBaseFontSizePx - 1.0f)); } // --------------------------------------------------------------------------- @@ -138,6 +142,12 @@ static void renderShortcutsWindow(bool& open) { {"= / - (hold)", "Zoom in / out about the canvas center"}, {"0 or Home", "Fit the schematic to the canvas"}, }; + static const Row kSource[] = { + {NAJA_SHORTCUT("F"), "Find in the shown file"}, + {NAJA_SHORTCUT("G"), "Find the next match"}, + {NAJA_SHORTCUT("C"), "Copy the selected text"}, + {NAJA_SHORTCUT("A"), "Select the whole file"}, + }; static const Row kMouse[] = { {"Wheel", "Zoom about the cursor"}, {"Right or middle drag", "Pan"}, @@ -147,7 +157,7 @@ static void renderShortcutsWindow(bool& open) { {"Click a box", "Select the instance (also in the tree)"}, {"Click a wire", "Select (highlight) its net"}, {"Click a box's hierarchy glyph","Show / hide what is inside it"}, - {"Right-click", "Context menu: trace, properties, fit, clear"}, + {"Right-click", "Context menu: trace, sources, properties, fit, clear"}, }; auto table = [](const char* id, const Row* rows, size_t n) { if (!ImGui::BeginTable(id, 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) return; @@ -168,6 +178,8 @@ static void renderShortcutsWindow(bool& open) { table("##canvaskeys", kCanvasKeys, IM_COUNTOF(kCanvasKeys)); ImGui::SeparatorText("Mouse"); table("##mouse", kMouse, IM_COUNTOF(kMouse)); + ImGui::SeparatorText("Source tab (text clicked first)"); + table("##source", kSource, IM_COUNTOF(kSource)); } ImGui::End(); } @@ -256,6 +268,7 @@ void setupProvider(AppState& state) { Console::Log("Root node data received"); DiagnosisStore::clear(); // stale diagnoses reference the old design PropertiesStore::clear(); // stale properties reference the old design + SourceStore::clear(); // so do source references SelectionStore::clear(); // so does a stale selection const auto& root = j["root"]; if (root.contains("has_terms") || root.contains("has_primitives") || root.contains("has_instances")) { @@ -263,7 +276,7 @@ void setupProvider(AppState& state) { state.guiData->netlist_->createRootNode( data.name, data.design_ref, data.has_terms, data.has_primitives, data.has_instances, data.has_nets, - data.source_loc); + data.has_sources); } else { DesignRef designRef{}; if (root.contains("design_ref")) { @@ -314,7 +327,7 @@ void setupProvider(AppState& state) { parent->createInstanceNode( instance.name, instance.model_name, instance.child_id, instance.design_ref, instance.has_terms, instance.has_primitives, instance.has_instances, instance.has_nets, - instance.source_loc); + instance.has_sources); } } else if (resp == "terms_response") { TermsResponseJson data = j.get(); @@ -330,7 +343,7 @@ void setupProvider(AppState& state) { parent->createChildren(); for (auto& term : data.children) { parent->createTermNode(term.name, term.child_id, - Direction(term.direction), term.msb, term.lsb); + Direction(term.direction), term.msb, term.lsb, term.has_sources); } } else if (resp == "nets_response") { NetsResponseJson data = j.get(); @@ -345,7 +358,7 @@ void setupProvider(AppState& state) { } parent->createChildren(); for (auto& net : data.children) { - parent->createNetNode(net.name, net.msb, net.lsb); + parent->createNetNode(net.name, net.msb, net.lsb, net.has_sources); } } else if (resp == "equipotential_response") { // A trace_id (echoed from a tree "Show Equipotential") makes the net a @@ -424,6 +437,7 @@ void setupProvider(AppState& state) { ic.designRef = c["design_ref"].get(); ic.primitiveType = primitiveTypeFromString(c.value("primitive_type", std::string("unknown"))); ic.hasInstances = c.value("has_instances", false) || c.value("has_primitives", false); + ic.hasSources = c.value("has_sources", false); data.children.push_back(std::move(ic)); } } @@ -450,16 +464,32 @@ void setupProvider(AppState& state) { } } EquipotentialView::applyInstanceInternals(*path, data); + } else if (resp == "sources_response") { + // The source references of the object a "Show Sources" asked about; + // the first one's file is opened right away. + std::vector refs; + for (const auto& entry : j.value("sources", json::array())) + refs.push_back(parseSourceRef(entry)); + json address = json::object(); + for (const char* key : {"kind", "id_path", "path", "terminal", "net", "bit"}) + if (j.contains(key)) address[key] = j[key]; + const bool any = !refs.empty(); + SourceStore::setReferences(j.value("subject", std::string("")), address, std::move(refs)); + state.focusSourceTab = true; + state.growSourcePanel = true; + if (any && state.provider) state.provider->send(SourceStore::loadRequest(0).dump()); } else if (resp == "source_response") { - std::string file = j.value("file", std::string("")); - bool found = j.value("found", false); - if (found) { - SourceStore::setSource(file, j.value("line", 0), j.value("text", std::string(""))); - state.focusSourceTab = true; - Console::Log("Loaded RTL source: " + file); + SourceRef ref = parseSourceRef(j); + if (j.value("found", false)) { + SourceStore::setSource(ref, j.value("text", std::string("")), + j.value("first_line", std::uint64_t(1)), j.value("truncated", false), + j.value("path", std::string("")), j.value("stale", false)); + Console::Log("Loaded source: " + ref.file); } else { - Console::Error("Failed to load RTL source: " + file); + SourceStore::setMissing(ref); + Console::Error("Failed to load source: " + (ref.file.empty() ? "no such reference" : ref.file)); } + state.focusSourceTab = true; } else if (resp == "properties_response") { PropertiesResponseJson data = j.get(); std::string subject = j.value("subject", std::string("")); @@ -583,6 +613,7 @@ bool appFrame(AppState& state) { static char snlPathBuf[1024] = {}; static char vrlFilesBuf[8192] = {}; static char vrlLibertyBuf[4096] = {}; + static bool vrlYosysSrc = false; static char svFilesBuf[8192] = {}; static char svTopBuf[256] = {}; #endif @@ -706,9 +737,14 @@ bool appFrame(AppState& state) { } ImGui::InputTextMultiline("##vrlliberty", vrlLibertyBuf, sizeof(vrlLibertyBuf), ImVec2(-1, 80)); ImGui::Spacing(); + ImGui::Checkbox("Decode Yosys src attributes", &vrlYosysSrc); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("For a Yosys-synthesized netlist: Show Sources then also\n" + "lists the RTL each gate came from"); + ImGui::Spacing(); if (ImGui::Button("Open", ImVec2(120,0)) && vrlFilesBuf[0]) { if (localProvider) { - localProvider->loadVerilog(splitLines(vrlFilesBuf), splitLines(vrlLibertyBuf)); + localProvider->loadVerilog(splitLines(vrlFilesBuf), splitLines(vrlLibertyBuf), vrlYosysSrc); reloadNetlist(state); } ImGui::CloseCurrentPopup(); @@ -771,6 +807,15 @@ bool appFrame(AppState& state) { const float minPanelH = 40.0f; float totalH = ImGui::GetContentRegionAvail().y; if (schematicHeight < 0.0f) schematicHeight = totalH * 0.65f; + // Show Sources: the reference list plus readable code need more than + // the default bottom panel. Grow it to this share of the height, once + // per Show Sources, and only if it's smaller: a panel the user made + // bigger stays as is, and later drags aren't undone. + constexpr float kSourcePanelShare = 0.45f; + if (state.growSourcePanel) { + state.growSourcePanel = false; + schematicHeight = std::min(schematicHeight, totalH * (1.0f - kSourcePanelShare)); + } auto vSplitter = [&](const char* id, float& width) { ImGui::SameLine(); @@ -870,8 +915,8 @@ bool appFrame(AppState& state) { // ImGui::EndTabItem(); // } ImGuiTabItemFlags srcFlags = state.focusSourceTab ? ImGuiTabItemFlags_SetSelected : 0; - if (ImGui::BeginTabItem("RTL Source", nullptr, srcFlags)) { - SourceView::render(); + if (ImGui::BeginTabItem("Source", nullptr, srcFlags)) { + SourceView::render(state.provider); ImGui::EndTabItem(); } ImGuiTabItemFlags propFlags = state.focusPropertiesTab ? ImGuiTabItemFlags_SetSelected : 0; diff --git a/src/AppLogic.h b/src/AppLogic.h index aaa7f01..afc85df 100644 --- a/src/AppLogic.h +++ b/src/AppLogic.h @@ -19,6 +19,9 @@ struct AppState { // cleared) by appFrame() to auto-select the matching bottom-panel tab for // that one frame. bool focusSourceTab {false}; + // Set by a sources_response: give the bottom panel room for the code + // once (see kSourcePanelShare in AppLogic.cpp). + bool growSourcePanel {false}; bool focusDiagnosisTab {false}; bool focusPropertiesTab {false}; // How many of the most recently added equipotentials the bottom diff --git a/src/CousineFont.h b/src/CousineFont.h new file mode 100644 index 0000000..79a04b4 --- /dev/null +++ b/src/CousineFont.h @@ -0,0 +1,643 @@ +// File: 'thirdparty/imgui/misc/fonts/Cousine-Regular.ttf' (43912 bytes) +// Exported using binary_to_compressed_c -u8 "thirdparty/imgui/misc/fonts/Cousine-Regular.ttf" Cousine +// +// The monospace font of the Source tab (SourceView), embedded like +// DroidSansFont.h so native and WASM builds have it without a filesystem. +// Cousine, by Steve Matteson, digitized data (c) 2010 Google Corporation, +// SIL Open Font License 1.1 (see REUSE.toml). Load with +// AddFontFromMemoryCompressedTTF(Cousine_compressed_data, Cousine_compressed_size, size). +#pragma once +static const unsigned int Cousine_compressed_size = 37630; +static const unsigned char Cousine_compressed_data[37630] = +{ + 87,188,0,0,0,0,0,0,0,0,171,136,0,4,0,0,37,0,1,0,0,0,17,130,4,38,4,0,16,68,83,73,71,130,13,130,12,33,171,68,130,7,44,8,70,70,84,77,78,228,103,200,0,0,171,108,130,15,40,28,71,68,69,70, + 1,5,0,4,130,15,32,76,130,15,44,32,79,83,47,50,249,38,158,79,0,0,1,152,130,15,60,96,99,109,97,112,93,8,173,159,0,0,3,176,0,0,2,122,99,118,116,32,77,22,94,181,0,0,17,160,130,15,39,60, + 102,112,103,109,115,211,35,130,27,45,6,44,0,0,7,5,103,97,115,112,0,24,0,9,130,79,32,52,130,63,56,16,103,108,121,102,57,84,93,127,0,0,21,144,0,0,137,68,104,101,97,100,246,221,165,136, + 130,95,32,28,130,31,33,54,104,130,16,35,11,120,2,98,130,15,32,84,130,15,40,36,104,109,116,120,79,10,70,202,130,15,32,248,130,3,44,184,108,111,99,97,108,26,77,210,0,0,19,220,130,15, + 40,178,109,97,120,112,4,124,2,103,130,11,32,120,130,47,8,50,32,110,97,109,101,17,229,56,249,0,0,158,212,0,0,10,50,112,111,115,116,236,124,255,199,0,0,169,8,0,0,2,42,112,114,101,112, + 217,152,247,148,0,0,13,52,0,0,4,106,0,1,130,53,130,3,43,189,51,22,165,95,15,60,245,0,31,8,0,131,0,35,201,111,45,213,135,7,39,255,246,254,57,4,214,6,168,130,15,34,8,0,2,130,5,131,2, + 130,51,45,6,169,253,153,0,0,4,205,255,246,255,247,4,214,132,73,131,25,135,3,32,4,132,17,40,216,0,121,0,5,0,106,0,4,130,57,44,16,0,47,0,90,0,0,3,45,1,82,0,3,130,29,36,3,4,205,1,144, + 130,29,40,8,5,154,5,51,0,0,1,29,133,7,58,3,97,0,102,2,18,8,5,2,7,4,9,2,2,5,2,4,4,160,0,2,175,64,0,120,251,0,134,0,63,49,65,83,67,0,64,0,32,34,25,4,231,254,126,2,38,6,169,2,103,96,0, + 0,159,223,215,0,0,4,58,5,69,130,38,38,32,0,1,4,205,0,68,130,9,130,161,32,0,132,3,39,2,3,1,21,0,54,0,66,130,19,60,43,1,246,1,86,1,80,0,248,0,116,1,3,1,78,1,240,0,114,0,124,0,148,0,144, + 0,128,0,103,130,3,44,151,0,158,0,133,0,141,1,240,1,96,0,116,132,1,34,94,0,44,130,55,36,162,0,113,0,162,130,1,32,194,132,7,34,202,0,176,130,11,34,237,0,129,130,5,32,102,134,3,38,79, + 0,76,0,142,0,10,130,43,48,36,0,36,0,73,1,154,0,115,1,14,0,133,255,251,1,146,130,87,36,179,0,130,0,138,130,89,36,138,0,143,0,185,130,3,39,117,0,236,0,134,0,99,0,131,23,32,179,130,25, + 36,242,0,167,0,190,130,25,36,69,0,21,0,94,130,169,36,147,0,227,2,19,130,19,34,108,2,3,130,53,32,56,130,149,32,52,130,15,40,159,1,79,0,31,0,206,0,127,130,153,32,31,130,89,32,71,130, + 197,34,56,1,50,130,97,34,144,0,127,130,203,46,119,1,29,0,226,0,107,0,27,0,17,0,11,0,153,130,139,137,2,32,1,132,185,131,195,34,162,0,202,134,1,32,27,132,187,33,102,0,133,1,36,157,0, + 56,0,142,134,1,32,36,130,25,32,142,130,183,32,128,136,3,32,20,130,193,32,133,130,139,131,3,32,143,130,193,131,3,32,125,132,191,32,130,130,25,131,3,34,116,0,110,130,193,32,185,132,3, + 32,66,130,5,32,66,130,37,8,44,20,0,28,1,27,1,116,0,255,0,170,255,246,1,141,1,143,1,93,0,177,0,178,0,178,1,80,1,90,1,110,0,204,0,91,1,240,1,37,0,250,0,234,0,197,130,172,32,3,134,3,34, + 28,0,1,130,9,32,0,130,57,32,3,132,9,36,28,0,4,1,88,130,17,8,40,34,0,32,0,4,0,2,0,126,0,255,1,49,1,83,2,198,2,218,2,220,32,20,32,26,32,30,32,34,32,58,32,68,32,172,34,21,34,25,255,255, + 130,43,34,32,0,160,130,35,32,82,134,35,36,19,32,24,32,28,130,35,32,57,137,35,63,255,227,0,0,255,143,255,111,253,253,253,234,253,233,224,179,224,176,224,175,224,172,224,150,224,141, + 224,38,222,188,222,186,132,121,32,32,130,73,155,2,8,195,3,0,98,0,99,0,100,0,101,0,102,0,103,0,104,0,105,0,106,0,107,0,108,0,109,0,16,0,110,0,111,0,112,0,113,0,114,0,115,0,116,0,117, + 0,118,0,119,0,120,0,121,0,122,0,123,0,124,0,125,0,126,0,127,0,128,0,129,0,130,0,131,0,132,0,133,0,134,0,135,0,136,0,137,0,138,0,139,0,140,0,141,0,142,0,143,0,144,0,145,0,146,0,147, + 0,148,0,149,0,150,0,151,0,152,0,153,0,154,0,155,0,156,0,157,0,158,0,159,0,160,0,161,0,162,0,163,0,164,0,165,0,166,0,167,0,168,0,169,0,170,0,171,0,172,0,173,0,174,0,175,0,176,0,177, + 0,178,0,179,0,180,0,181,0,182,0,183,0,184,0,185,0,186,0,187,0,188,0,189,0,190,0,191,0,0,1,6,0,132,232,131,228,33,1,2,130,5,33,2,0,141,0,8,152,1,0,0,3,4,5,6,7,8,9,10,11,12,13,14,15, + 16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75, + 76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,0,132,133,135,137,145,150,156,161,160,162,164,163,165,167,169,168,170,171,173,172,174,175,177,179,178,180,182,181, + 186,185,187,188,0,112,99,100,104,206,118,159,110,106,0,116,105,0,134,152,0,113,0,0,102,117,132,166,39,107,122,0,166,184,127,98,109,131,12,47,108,123,0,3,128,131,149,193,194,198,199, + 203,204,200,201,183,130,222,35,209,210,207,208,130,26,59,119,202,205,0,130,138,129,139,136,141,142,143,140,147,148,0,146,154,155,153,192,195,197,111,0,0,196,120,130,30,8,88,0,0,64, + 69,89,88,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,49,48,47,46,45,44,40,39,38,37,36,35,34,33,31,24,20,17,16,15,14,13,11,10, + 9,8,7,6,5,4,3,2,1,0,44,69,35,70,96,32,176,38,96,176,4,38,35,72,72,45,131,15,33,35,97,130,16,32,97,138,16,34,96,176,32,130,18,32,70,141,35,33,176,32,131,55,32,97,130,26,140,41,32,64, + 130,41,32,102,142,41,32,64,133,41,32,64,136,83,53,1,16,32,60,0,60,45,44,32,69,35,32,176,205,68,35,32,184,1,90,81,88,130,11,38,141,68,35,89,32,176,237,132,11,32,77,132,11,33,4,38,132, + 12,32,13,130,12,33,33,33,130,49,8,35,32,69,24,104,68,32,176,1,96,32,69,176,70,118,104,138,69,96,68,45,44,1,177,11,10,67,35,67,101,10,45,44,0,177,10,11,130,10,32,11,130,9,40,176,40, + 35,112,177,1,40,62,1,132,8,40,2,40,69,58,177,2,0,8,13,130,68,54,69,176,3,37,69,97,100,176,80,81,88,69,68,27,33,33,89,45,44,73,176,14,35,130,73,130,85,33,0,67,132,82,36,176,6,67,176, + 7,132,82,34,32,105,176,130,186,55,0,139,32,177,44,192,138,140,184,16,0,98,96,43,12,100,35,100,97,92,88,176,3,97,130,59,60,138,3,69,138,138,135,176,17,43,176,41,35,68,176,41,122,228, + 24,45,44,69,101,176,44,35,68,69,176,43,131,85,33,75,82,136,103,32,75,137,114,41,1,176,5,37,16,35,32,138,245,0,130,206,34,35,237,236,130,199,138,17,32,97,134,17,36,6,37,16,245,0,131, + 28,8,36,70,35,70,96,138,138,70,35,32,70,138,96,138,97,184,255,128,98,35,32,16,35,138,177,12,12,138,112,69,96,32,176,0,80,88,176,1,130,23,44,186,139,27,176,70,140,89,176,16,96,104,1, + 58,134,236,46,70,82,75,176,19,81,91,88,176,2,37,70,32,104,97,130,254,130,2,38,63,35,33,56,27,33,17,130,192,130,245,34,3,37,70,130,63,149,30,43,0,176,7,67,176,6,67,11,45,44,33,33,131, + 248,36,139,184,64,0,98,130,12,35,176,128,81,88,133,15,44,32,0,98,27,178,0,64,47,43,89,176,2,96,131,25,32,192,135,25,33,21,85,131,25,32,128,135,25,136,62,8,45,96,35,33,45,44,75,83,88, + 138,176,4,37,73,100,35,69,105,176,64,139,97,176,128,98,176,32,97,106,176,14,35,68,35,16,176,14,246,27,33,35,138,18,17,32,57,47,130,163,130,43,32,32,130,180,40,73,100,105,32,176,5,38, + 176,6,131,53,139,48,34,176,4,38,131,50,32,138,130,4,33,35,68,130,59,131,68,35,176,14,237,27,130,96,37,38,17,18,32,57,35,130,70,131,71,35,69,35,69,96,134,2,34,118,104,24,130,113,48, + 32,45,44,176,72,43,45,44,32,69,176,0,84,88,176,64,68,130,8,33,64,97,66,22,6,35,69,177,48,47,130,49,38,97,96,176,1,96,105,68,130,177,41,81,88,176,47,35,112,176,20,35,66,66,55,5,34,75, + 81,88,131,150,35,69,105,83,88,132,50,133,21,39,69,176,20,67,176,0,96,99,134,53,34,176,47,69,130,59,37,69,35,32,69,138,96,132,8,32,69,131,6,51,75,35,81,88,185,0,51,255,224,177,52,32, + 27,179,51,0,52,0,89,68,130,29,8,44,176,22,67,88,176,3,38,69,138,88,100,102,176,31,96,27,100,176,32,96,102,32,88,27,33,176,64,89,176,1,97,89,35,88,101,89,176,41,35,68,35,16,176,41,224, + 130,110,35,33,33,33,89,130,194,43,2,67,84,88,75,83,35,75,81,90,88,56,130,21,32,89,132,25,131,24,131,78,34,4,37,69,142,70,34,35,88,27,133,70,48,176,5,37,176,8,37,8,32,88,2,27,3,89,176, + 4,37,16,130,16,33,32,70,130,8,34,35,66,60,130,5,35,176,7,37,8,130,3,35,16,176,6,37,132,21,130,209,130,24,36,32,88,1,27,0,135,45,130,134,37,176,41,32,69,101,68,134,39,131,15,140,84, + 130,12,35,3,37,67,72,135,77,36,6,37,176,3,37,130,71,36,67,72,27,33,89,130,189,131,2,34,45,44,2,130,30,32,32,132,98,33,35,66,130,130,32,8,130,35,33,69,72,135,26,34,3,37,32,130,30,34, + 8,176,2,130,70,130,19,8,36,45,44,69,35,32,69,24,32,176,0,80,32,88,35,101,35,89,35,104,32,176,64,80,88,33,176,64,89,35,88,101,89,138,96,68,45,44,65,12,6,33,32,69,130,13,65,149,5,34, + 75,84,88,139,13,65,44,10,36,45,44,176,0,33,130,30,32,56,133,40,39,176,2,67,84,88,176,70,43,65,67,8,132,15,32,71,132,15,65,107,7,33,176,72,142,30,32,73,132,15,130,30,43,32,138,8,35, + 75,83,138,75,81,90,88,35,134,80,32,0,130,185,36,73,176,0,83,88,130,166,33,56,17,130,246,46,45,44,1,70,35,70,96,35,70,97,35,32,16,32,70,67,207,5,53,138,177,64,64,138,112,69,96,104,58, + 45,44,32,138,35,73,100,138,35,83,88,60,132,45,38,75,82,88,125,27,122,89,130,167,8,42,18,0,75,1,75,84,66,45,44,177,2,0,66,177,35,1,136,81,177,64,1,136,83,90,88,185,16,0,0,32,136,84, + 88,178,2,1,2,67,96,66,89,177,36,130,27,37,88,185,32,0,0,64,132,21,32,2,131,21,131,20,131,34,32,32,131,12,131,73,32,82,130,47,32,8,132,47,37,27,185,64,0,0,128,132,42,32,4,132,16,132, + 15,35,99,184,1,0,132,19,133,36,130,19,36,1,0,99,184,2,133,19,32,16,135,39,32,2,130,19,32,4,133,19,32,64,132,19,32,89,130,0,56,45,44,69,24,104,35,75,81,88,35,32,69,32,100,176,64,80, + 88,124,89,104,138,96,89,68,130,197,36,0,22,176,2,37,130,2,55,1,176,1,35,62,0,176,2,35,62,177,1,2,6,12,176,10,35,101,66,176,11,35,66,131,23,32,63,131,23,32,63,133,23,32,6,131,23,49, + 7,35,66,176,1,22,1,45,44,122,138,16,69,35,245,24,45,0,130,219,8,133,100,9,3,4,253,1,245,80,40,31,242,70,40,31,241,70,42,31,240,70,53,31,139,238,155,238,171,238,3,107,239,139,239,2, + 187,239,1,164,239,1,27,239,91,239,107,239,3,4,236,68,236,2,10,235,70,255,31,231,228,38,31,230,228,61,31,229,228,30,31,227,226,70,31,11,226,1,64,226,70,22,31,225,224,70,31,187,224,203, + 224,219,224,3,64,224,51,54,70,224,70,24,31,188,1,20,0,62,1,18,0,85,1,19,64,12,61,3,85,223,61,221,85,222,61,220,85,187,65,9,1,22,0,1,0,84,130,5,32,100,130,3,49,2,255,192,1,22,179,12, + 22,70,32,184,1,22,178,1,2,0,188,130,20,32,16,130,3,8,207,2,1,21,178,220,61,31,184,1,17,64,99,3,255,31,16,221,32,221,64,221,80,221,128,221,176,221,6,32,220,80,220,128,220,176,220,4, + 15,220,1,208,21,51,31,95,200,111,200,127,200,3,95,195,111,195,127,195,3,191,194,1,193,80,38,31,112,190,1,32,190,48,190,192,190,3,112,190,128,190,2,15,188,31,188,2,47,188,63,188,111, + 188,175,188,223,188,5,185,173,38,31,32,184,48,184,80,184,112,184,128,184,5,184,255,192,64,28,184,19,41,70,16,183,1,32,183,80,183,128,183,176,183,4,128,181,176,181,2,15,179,63,179,239, + 179,3,184,1,13,64,43,170,72,31,128,176,144,176,2,176,176,192,176,208,176,3,47,175,63,175,2,160,173,176,173,2,192,173,208,173,2,47,172,63,172,2,159,171,1,192,170,208,170,2,65,15,1,15, + 0,50,1,14,0,85,0,0,1,14,0,16,130,3,32,32,130,3,32,112,130,3,59,4,0,15,1,16,64,21,1,80,156,96,156,112,156,3,153,150,38,31,152,70,38,31,48,151,64,151,2,130,135,36,179,150,22,28,70,130, + 7,57,64,42,150,14,17,70,149,27,255,31,15,148,175,148,191,148,3,64,148,29,49,70,64,148,22,27,130,4,8,55,12,15,70,15,147,47,147,63,147,127,147,239,147,5,15,186,1,12,0,111,1,12,64,32, + 2,146,141,38,31,145,83,255,31,223,144,1,48,144,1,31,144,47,144,2,111,144,127,144,2,0,143,16,143,32,143,3,131,87,8,37,32,143,24,28,70,32,142,48,142,2,79,141,95,141,111,141,3,48,140, + 1,15,140,31,140,47,140,3,64,140,16,19,70,16,191,1,11,0,32,130,3,32,48,130,3,41,3,255,192,1,11,178,23,32,70,185,131,8,8,54,64,80,16,20,70,139,130,38,31,137,74,60,31,136,135,61,31,135, + 132,60,31,134,74,255,31,159,133,1,16,132,32,132,48,132,3,48,131,1,127,130,1,64,130,9,12,70,115,80,38,31,111,70,53,31,110,130,3,48,26,1,24,85,25,51,24,85,7,51,3,85,6,3,255,31,96,130, + 27,32,95,130,3,39,185,255,224,1,7,178,31,38,130,95,130,8,45,64,31,19,28,70,94,90,72,31,92,70,49,31,91,130,7,32,90,130,7,63,19,50,18,85,5,1,3,85,4,50,3,85,184,1,8,181,27,60,31,15,3, + 1,185,1,25,1,24,178,53,31,64,184,130,6,39,23,40,70,184,1,10,64,37,130,80,8,44,82,80,27,31,239,81,255,81,2,64,81,53,56,70,64,81,37,40,70,207,80,1,223,76,1,76,70,29,31,75,70,72,31,80, + 184,1,27,64,90,1,74,70,38,31,73,130,152,32,72,130,3,32,71,130,3,8,55,175,70,1,223,70,239,70,2,128,70,1,22,50,21,85,17,1,15,85,16,50,15,85,2,1,0,85,1,0,1,31,31,15,63,15,95,15,127,15, + 4,15,15,47,15,79,15,111,15,143,15,223,15,255,15,7,63,130,19,8,37,239,15,3,111,0,1,79,0,1,160,22,1,5,1,184,1,144,177,84,83,43,43,75,184,7,255,82,75,176,9,80,91,176,1,136,176,37,83,131, + 5,8,34,64,81,90,176,6,136,176,0,85,90,91,88,177,1,1,142,89,133,141,141,0,66,29,75,176,50,83,88,176,96,29,89,75,176,100,130,8,32,64,131,8,32,128,130,8,44,16,29,177,22,0,66,89,115,116, + 115,116,117,43,131,0,32,1,134,8,40,115,0,43,43,116,116,43,43,115,131,20,132,4,32,1,130,9,130,20,132,37,33,43,43,130,13,32,0,130,4,34,115,116,116,132,28,130,21,130,49,130,13,34,116, + 43,117,132,5,33,43,115,131,23,32,115,130,34,131,66,32,0,130,96,36,43,1,115,115,116,130,8,130,106,35,43,115,1,115,130,9,130,25,33,115,116,131,112,32,0,130,16,34,116,0,116,130,51,130, + 52,34,115,95,115,132,121,132,99,130,129,34,94,115,43,131,118,34,1,43,94,130,51,130,66,32,115,133,136,49,115,24,95,94,0,0,5,204,5,204,0,125,5,69,0,21,0,96,132,5,32,0,139,0,41,4,58,0, + 20,0,119,0,0,255,236,131,21,137,5,33,254,87,131,15,201,3,33,8,0,132,0,44,180,0,189,0,170,0,160,0,200,0,180,0,133,132,17,40,0,0,136,0,126,0,165,0,192,134,13,133,6,44,191,0,201,0,171, + 0,140,0,188,0,155,0,141,133,18,148,5,32,185,130,71,32,124,132,25,34,148,0,95,132,7,148,4,39,1,168,0,111,0,120,0,164,130,119,32,131,130,79,46,187,0,94,1,138,1,3,0,96,0,116,0,129,0,184, + 130,123,52,80,4,177,4,195,254,76,0,235,1,178,0,195,1,9,0,123,1,43,2,150,130,131,32,150,148,78,38,0,0,146,0,168,0,107,130,5,32,183,130,5,32,155,131,35,51,2,242,0,146,2,62,0,110,2,165, + 3,69,0,137,0,160,0,96,2,76,130,23,46,195,0,0,1,78,0,164,1,91,0,94,0,128,0,105,130,133,32,0,130,9,32,104,130,25,137,2,38,38,3,74,0,135,0,123,130,16,33,117,0,138,25,47,5,130,2,51,0,11, + 255,244,0,131,0,137,0,143,0,150,130,61,36,113,0,91,0,82,130,39,131,2,34,178,2,3,130,6,40,141,3,31,0,187,0,174,0,181,130,11,34,0,0,139,132,5,130,4,43,6,169,3,112,2,188,2,8,253,153,0, + 145,130,1,8,56,77,0,77,0,100,0,100,1,129,0,159,0,168,254,104,254,156,0,155,1,69,1,69,0,144,254,246,0,23,0,122,5,69,2,77,0,12,255,238,5,152,2,139,0,110,0,76,0,98,0,70,0,232,0,68,5,17, + 130,74,33,44,0,133,1,9,172,84,0,126,1,48,2,48,3,6,4,8,4,36,4,92,4,146,5,12,5,74,5,118,5,146,5,172,5,218,6,92,6,168,7,34,7,232,8,50,8,188,9,100,9,158,10,106,11,14,11,52,11,76,11,156, + 11,214,12,38,12,156,13,220,14,62,14,194,15,84,15,174,15,242,16,44,16,176,16,244,17,58,17,132,17,236,18,14,18,164,19,6,19,120,19,208,20,94,20,218,21,138,21,194,22,26,22,126,23,116,23, + 248,24,90,24,172,24,216,25,8,25,48,25,120,25,140,25,170,26,88,26,214,27,92,27,222,28,104,28,200,29,118,29,198,30,6,30,90,30,206,30,254,31,148,31,234,32,78,32,214,33,92,33,176,34,66, + 34,154,34,244,35,94,36,78,36,202,37,86,37,152,38,24,38,50,38,178,39,20,39,62,39,196,40,94,41,40,41,172,41,212,42,220,43,8,44,10,44,156,44,236,45,14,45,228,45,238,46,78,46,162,46,254, + 47,134,47,164,48,18,48,84,48,92,48,176,48,240,49,76,49,154,49,192,49,222,50,0,50,122,50,146,50,170,50,194,50,218,50,244,51,12,51,124,51,144,51,168,51,192,51,216,51,242,52,10,52,34, + 52,58,52,84,52,208,52,232,53,0,53,24,53,48,53,72,53,98,53,204,54,122,54,146,54,170,54,194,54,220,54,244,55,82,56,10,56,34,56,56,56,82,56,112,56,140,56,164,57,132,57,152,57,176,57,198, + 57,220,57,244,58,16,58,38,58,68,58,104,59,26,59,48,59,72,59,94,59,116,59,138,59,162,59,238,60,172,60,196,60,218,60,242,61,12,61,34,61,166,61,190,61,238,62,104,63,32,63,72,63,200,64, + 32,64,52,64,72,64,110,64,152,64,198,65,10,65,86,65,164,65,224,66,14,66,60,66,94,67,0,67,34,67,100,67,188,68,74,68,162,0,0,0,2,0,68,130,4,59,100,5,85,0,3,0,7,0,46,177,1,0,47,60,178, + 7,4,28,237,50,177,6,5,220,60,178,3,2,130,10,34,0,177,3,131,22,32,5,131,22,39,178,7,6,29,252,60,178,1,131,23,55,51,17,33,17,37,33,17,33,68,2,32,254,36,1,152,254,104,5,85,250,171,68, + 4,205,131,87,33,2,3,130,87,34,201,5,68,132,87,8,74,41,64,21,3,7,152,2,0,4,16,4,2,4,4,8,9,1,1,2,5,157,4,2,3,0,63,47,237,17,57,47,17,18,1,57,47,93,51,237,50,49,48,1,35,3,51,3,53,51,21, + 2,177,148,24,196,198,194,1,141,3,183,250,188,201,201,0,2,1,21,3,77,3,182,5,204,132,79,8,37,39,64,20,3,150,2,2,9,7,150,47,6,1,0,6,1,6,5,0,0,6,2,0,0,63,51,51,47,50,1,47,93,93,237,18, + 57,47,237,133,77,132,81,49,140,142,40,224,254,21,141,41,224,3,77,2,127,253,129,2,127,0,130,0,8,196,2,0,54,0,0,4,139,5,69,0,27,0,31,0,240,185,0,4,255,240,64,153,13,17,72,6,29,22,29, + 38,29,3,6,1,22,1,38,1,3,18,15,14,11,4,10,19,8,29,28,21,4,20,9,4,1,0,25,4,24,5,22,31,30,7,4,6,23,19,172,20,5,172,6,23,172,24,26,47,3,63,3,2,3,26,3,24,48,6,64,6,80,6,3,16,24,32,24,2, + 224,24,240,24,2,20,6,24,24,6,20,3,10,33,17,17,12,12,9,172,0,10,1,10,8,4,12,175,13,29,1,13,28,0,16,175,17,25,21,17,79,17,1,79,17,95,17,239,17,3,17,64,31,34,72,127,13,143,13,159,13,3, + 79,13,95,13,2,13,17,13,17,5,23,19,3,10,5,0,47,51,63,51,18,57,57,47,47,93,113,43,93,113,17,51,51,16,237,50,50,134,6,130,244,39,237,51,47,51,47,17,18,23,130,32,130,33,131,31,36,47,93, + 47,16,237,131,1,131,21,139,3,44,49,48,93,93,43,1,3,33,21,33,3,35,19,131,3,41,35,53,51,19,35,53,33,19,51,3,131,3,32,51,130,23,8,46,33,19,3,162,67,1,4,254,229,88,110,86,254,149,84,110, + 84,201,225,67,241,1,7,89,110,88,1,107,88,110,88,211,253,64,69,1,106,67,3,65,254,195,108,254,104,1,152,131,3,35,108,1,61,108,133,9,8,116,254,104,108,254,195,1,61,0,0,0,3,0,66,255,115, + 4,126,5,196,0,57,0,68,0,79,1,25,64,199,68,61,84,61,2,101,78,1,110,71,1,101,60,1,106,47,1,117,42,133,42,2,118,41,134,41,2,118,34,134,34,2,101,14,1,121,10,137,10,2,121,9,137,9,2,121, + 3,137,3,2,122,31,138,31,2,44,111,45,45,69,75,51,39,6,63,36,17,7,7,31,117,0,133,0,2,0,111,0,69,16,69,32,69,96,69,112,69,5,131,8,32,48,130,8,8,80,144,69,160,69,176,69,7,69,69,81,12,111, + 11,64,18,32,72,142,11,1,11,11,58,111,0,31,64,31,2,0,31,16,31,240,31,3,31,50,64,115,36,74,18,115,51,63,63,36,45,45,39,36,37,64,32,39,72,37,64,18,23,72,37,37,36,6,75,17,115,8,0,12,16, + 12,64,12,80,12,96,12,5,131,6,56,160,12,176,12,240,12,5,12,12,8,144,7,1,7,7,5,8,24,0,63,51,51,47,93,17,130,3,41,113,16,237,50,63,51,47,43,43,17,130,17,37,17,57,47,51,237,50,130,17,36, + 1,47,93,113,237,130,29,33,43,237,132,35,34,237,93,18,130,25,37,51,51,205,50,50,50,130,16,37,237,93,49,48,0,93,137,0,51,1,93,1,20,14,2,7,21,35,53,46,1,39,55,30,3,23,17,38,39,130,1,42, + 35,46,5,53,52,62,2,55,53,51,21,130,19,8,215,7,46,3,39,17,30,5,1,20,30,2,23,17,14,3,1,52,46,2,39,17,62,3,4,126,56,114,175,119,128,215,243,34,170,11,45,77,112,77,8,7,4,6,3,2,49,98,88, + 76,56,31,64,114,157,93,128,102,145,102,64,20,174,10,39,63,88,59,58,114,103,88,65,36,252,176,40,69,93,54,71,98,60,27,2,164,48,80,106,58,65,107,77,43,1,124,75,128,95,57,5,161,161,7,185, + 167,37,54,91,68,41,4,1,233,2,2,1,2,1,12,29,39,55,73,97,63,82,119,79,40,3,130,130,4,44,80,117,76,33,48,79,57,33,4,254,86,14,31,43,58,81,108,2,54,54,71,47,30,14,1,157,3,33,53,69,253, + 94,63,81,52,33,15,254,41,4,30,56,82,0,0,5,0,0,255,244,4,206,5,81,0,3,0,23,0,43,0,63,0,83,0,193,185,0,62,255,216,64,14,9,14,72,56,40,9,12,72,52,131,4,36,46,184,255,216,179,130,17,32, + 22,130,7,132,26,32,16,131,21,32,12,131,4,32,6,131,18,32,22,130,27,43,121,2,137,2,2,2,3,86,0,118,0,134,130,79,8,77,1,3,16,1,186,255,240,0,3,255,192,64,58,10,14,72,31,1,47,1,63,1,3,3, + 1,3,1,19,49,74,180,59,181,49,180,112,64,128,64,2,64,64,19,85,24,180,9,181,34,180,19,69,182,44,183,79,182,54,25,39,182,14,183,29,182,4,7,2,6,0,24,0,63,63,63,237,244,237,131,3,33,1,47, + 130,8,39,17,18,57,47,93,253,244,237,130,7,41,57,47,47,93,43,56,56,17,51,93,130,2,33,49,48,73,174,8,54,51,35,1,51,37,50,30,2,21,20,14,2,35,34,46,2,53,52,62,2,19,52,46,130,11,32,14,130, + 19,38,30,2,51,50,62,2,1,158,31,8,40,221,145,3,164,147,252,160,58,103,77,45,45,78,104,60,60,105,78,44,45,78,106,198,18,35,51,33,35,54,36,18,19,36,53,34,32,51,36,19,2,8,159,33,8,37,5, + 73,8,31,77,129,99,96,130,80,35,35,79,131,96,99,129,77,31,254,176,66,88,53,21,22,53,87,66,64,87,54,23,23,54,87,254,216,160,34,8,52,0,0,3,0,43,255,236,4,161,5,77,0,61,0,79,0,95,0,244, + 64,24,115,78,131,78,2,121,81,137,81,2,101,88,117,88,133,88,3,105,83,1,57,37,1,32,184,255,232,179,9,14,72,28,132,7,8,148,12,72,46,184,255,240,64,61,9,12,72,37,18,1,6,18,22,18,2,72,73, + 25,35,73,62,48,62,1,48,59,1,59,59,49,73,48,107,80,1,12,20,28,20,108,20,3,40,75,83,20,4,62,45,80,52,5,4,25,112,62,128,62,144,62,3,62,62,48,25,184,255,192,64,63,16,19,72,63,48,1,25,48, + 25,48,97,86,72,15,64,11,23,72,15,96,45,112,45,128,45,3,101,80,1,41,80,1,5,80,52,45,4,10,48,69,75,85,75,101,75,3,75,20,48,40,83,5,91,67,81,30,3,55,91,81,0,10,22,0,63,51,237,50,63,237, + 18,23,57,93,47,131,4,37,93,93,1,47,43,237,69,50,5,39,43,18,57,47,93,17,23,57,132,23,53,16,237,50,47,93,93,16,237,16,237,49,48,0,93,93,43,43,43,93,1,93,0,132,3,38,5,34,46,2,39,14,3, + 65,213,7,39,55,46,3,53,52,62,2,51,65,204,6,52,7,30,3,23,62,1,55,23,14,1,7,30,1,51,50,54,55,21,14,1,1,65,248,8,43,22,23,62,3,3,46,1,39,14,1,21,20,66,4,5,8,173,4,48,41,76,67,58,22,28, + 71,86,103,59,109,158,102,49,45,78,107,62,15,27,21,12,43,87,132,89,69,118,86,48,68,114,147,78,31,62,68,74,42,42,63,20,145,26,87,49,43,97,40,26,48,21,24,56,254,187,24,45,64,39,44,73, + 53,30,38,23,64,113,85,49,74,89,161,66,85,94,32,64,97,65,39,71,60,49,12,21,35,47,26,25,50,38,24,58,102,138,80,77,126,103,81,32,28,68,73,76,37,62,109,81,46,35,68,100,64,71,108,86,70, + 33,54,100,96,93,46,87,198,121,43,139,229,94,54,51,7,9,135,11,11,4,73,34,56,39,22,25,46,67,42,65,116,43,27,50,58,71,252,203,100,225,116,46,149,101,52,92,68,39,19,30,38,0,130,0,62,1, + 1,246,3,77,2,214,5,204,0,3,0,28,64,13,3,150,0,2,1,2,2,4,5,0,0,2,0,0,63,51,71,170,6,32,237,71,90,5,33,2,172,71,81,6,40,0,0,1,1,86,254,87,3,123,130,55,8,34,21,0,40,64,22,16,6,240,17, + 15,5,31,5,2,5,5,23,0,240,0,11,1,11,16,0,6,27,0,63,63,1,47,93,237,18,71,236,8,36,20,30,2,23,35,65,97,5,8,44,55,51,14,3,2,17,41,88,137,96,190,92,135,89,43,43,89,135,92,190,96,137,88, + 41,2,18,138,244,230,225,118,113,219,230,251,144,144,249,229,217,113,118,225,230,244,131,111,32,80,130,111,32,117,132,111,42,36,64,19,0,240,11,11,23,17,5,240,130,101,38,16,6,2,6,16, + 0,5,134,108,34,51,237,50,72,10,6,8,40,20,14,2,7,35,62,3,53,52,46,2,39,51,30,3,3,117,42,88,135,92,192,96,138,88,42,42,88,138,96,192,92,135,88,42,2,20,144,251,230,219,132,97,33,138,137, + 132,117,34,217,229,249,130,107,8,51,0,248,2,252,3,212,5,203,0,14,0,175,64,23,91,6,107,6,2,0,3,80,3,96,3,3,15,9,95,9,111,9,3,11,3,7,8,184,255,192,64,76,11,17,72,8,8,10,13,5,4,64,130, + 9,8,62,4,4,2,14,14,10,13,14,112,10,128,10,2,10,6,14,0,13,16,13,2,13,13,15,16,2,10,10,1,11,0,12,117,12,133,12,2,3,9,4,8,127,11,143,11,2,11,8,6,9,12,11,5,13,5,112,7,128,7,144,7,3,7,130, + 80,55,182,9,12,72,7,7,13,0,0,63,51,47,43,93,51,18,23,57,47,93,17,51,17,51,132,4,130,7,73,56,5,46,205,57,196,93,43,1,24,16,77,228,57,47,43,51,17,130,239,8,63,43,51,49,48,95,94,93,93, + 0,93,1,37,23,5,23,7,11,1,39,55,37,55,5,3,51,2,159,1,8,45,254,230,185,119,150,156,119,189,254,232,45,1,11,12,136,4,164,103,132,73,250,72,1,2,255,0,72,248,73,134,107,1,41,0,131,243,47, + 116,0,180,4,87,4,158,0,11,0,73,64,37,11,14,1,130,204,62,4,9,1,170,64,6,0,2,1,10,3,2,2,12,13,2,14,4,7,5,14,48,7,1,7,0,4,173,9,5,179,130,165,37,237,50,198,93,43,0,130,138,32,230,132, + 154,130,131,33,51,26,130,19,132,157,41,230,49,48,1,17,35,17,33,53,33,130,194,55,33,21,2,175,147,254,88,1,168,147,1,168,2,96,254,84,1,172,146,1,172,254,84,146,130,122,63,0,1,1,3,254, + 149,2,181,1,43,0,3,0,57,64,39,9,0,25,0,2,0,16,14,17,72,9,2,25,2,2,2,131,9,42,1,153,2,151,3,154,47,0,111,0,127,130,36,55,0,156,1,155,4,0,16,246,237,1,47,93,237,253,237,49,48,43,93,43, + 93,1,19,33,130,72,36,169,1,9,254,201,130,77,34,150,253,106,130,87,38,78,1,208,3,126,2,112,130,50,40,29,64,15,3,3,5,0,0,16,130,90,38,0,186,47,1,1,1,0,130,59,130,63,34,18,57,47,130,167, + 43,53,33,21,1,78,2,48,1,208,160,160,0,130,55,36,240,0,0,2,219,132,143,36,26,64,12,3,150,130,18,41,0,0,4,5,1,155,0,0,47,237,132,232,32,93,130,116,131,214,38,1,240,235,1,43,254,213,131, + 32,38,114,255,236,4,89,5,204,130,107,8,34,61,64,28,138,1,1,57,1,105,1,121,1,3,1,2,16,2,2,5,133,3,1,54,3,102,3,118,3,3,3,0,184,255,240,183,131,48,35,3,25,1,0,67,33,5,35,56,50,93,93, + 130,135,58,56,51,93,93,49,48,23,1,51,1,114,3,53,178,252,207,20,5,224,250,32,0,0,0,3,0,124,130,91,8,99,79,5,90,0,19,0,39,0,43,0,121,64,82,54,32,1,70,17,86,17,2,70,13,86,13,2,73,7,89, + 7,2,73,3,89,3,2,16,42,32,42,2,64,42,80,42,2,0,41,1,42,41,42,41,30,0,110,64,20,80,20,2,0,20,1,20,20,45,30,111,0,10,16,10,2,10,79,40,1,79,40,95,40,2,40,41,41,35,25,115,15,7,35,115,5, + 25,0,63,237,63,237,130,132,34,205,93,113,67,179,7,32,113,69,74,6,34,93,113,49,72,187,6,52,1,20,2,14,1,35,34,46,1,2,53,52,18,62,1,51,50,30,1,18,7,71,7,14,8,82,37,53,51,21,4,79,77,133, + 180,102,103,178,131,75,75,132,180,106,101,177,132,76,183,40,78,113,72,76,116,79,40,41,79,114,73,71,114,79,43,254,88,235,2,163,195,254,247,164,71,71,163,1,10,195,204,1,12,159,64,64, + 159,254,244,204,159,212,126,52,53,126,211,159,154,210,128,56,56,128,210,34,249,249,0,130,0,8,71,1,0,148,0,0,4,106,5,69,0,18,0,89,64,57,0,17,1,17,17,16,111,2,8,8,31,1,1,15,1,63,1,79, + 1,95,1,143,1,159,1,175,1,7,1,1,14,16,2,1,16,2,112,2,128,2,3,2,2,19,20,8,115,9,9,3,14,6,16,1,115,0,24,130,230,35,50,63,51,51,65,198,7,32,113,130,10,8,57,93,113,51,47,16,237,50,47,93, + 49,48,51,53,33,17,14,3,35,53,50,62,2,55,51,17,33,21,157,1,183,19,96,125,142,66,74,143,124,96,26,166,1,97,145,3,253,40,71,52,31,148,34,61,84,50,251,76,145,132,151,32,144,130,151,8,61, + 60,5,90,0,38,0,135,64,92,117,6,133,6,2,57,32,1,116,27,132,27,2,118,26,134,26,2,119,21,135,21,2,119,22,135,22,2,105,10,1,107,16,1,89,16,1,5,26,1,38,38,29,110,31,8,63,8,2,111,8,191,8, + 130,197,50,0,40,18,110,19,19,0,96,36,112,36,128,36,3,36,36,207,0,1,66,171,5,39,18,18,13,115,24,7,1,36,135,186,38,237,51,47,1,47,93,93,74,137,5,38,237,17,18,57,47,93,113,130,187,74, + 92,12,36,51,53,62,5,53,65,149,6,35,7,39,62,3,70,204,6,8,71,4,7,33,21,144,44,144,163,165,133,84,39,71,97,59,54,93,70,44,7,184,9,64,111,162,106,105,167,117,62,86,135,164,156,128,32,2, + 211,117,97,164,145,132,129,133,74,60,87,57,27,30,60,89,59,17,76,134,101,58,48,93,138,89,94,161,144,130,128,129,70,146,0,130,154,36,128,255,236,4,75,130,243,8,71,59,0,230,64,99,117, + 57,133,57,2,99,57,1,64,57,80,57,2,120,52,136,52,2,108,52,1,75,52,91,52,2,116,47,132,47,2,6,46,118,46,134,46,3,119,41,135,41,2,108,37,1,90,37,1,99,18,1,100,13,1,85,13,1,121,8,137,8, + 2,121,7,137,7,75,246,5,8,52,123,2,139,2,2,54,26,49,110,63,32,95,32,2,32,32,0,110,19,64,19,1,0,19,1,19,184,255,192,64,60,21,24,72,26,19,26,19,10,61,38,110,39,39,11,110,207,10,223,10, + 239,10,3,66,209,5,52,54,25,115,26,26,16,44,38,38,35,115,44,7,16,115,5,64,11,144,11,2,131,4,60,240,11,3,11,11,5,25,0,63,51,47,93,113,16,237,63,237,51,47,17,18,57,47,237,57,1,47,93,93, + 130,12,73,245,6,32,43,131,28,37,51,47,113,237,17,57,65,74,12,75,176,7,32,1,73,248,6,55,39,55,30,3,51,50,54,53,52,46,2,43,1,53,51,50,62,2,53,52,38,35,34,6,65,102,10,8,106,2,7,21,30, + 3,4,75,63,121,178,116,130,180,116,58,9,186,8,43,74,109,73,136,155,69,103,121,51,102,98,51,110,91,59,133,131,119,147,12,181,11,80,123,158,89,119,169,108,51,37,73,111,74,82,125,84,43, + 1,114,91,145,100,54,65,107,137,73,17,56,92,66,36,126,121,68,90,53,22,156,25,55,89,63,106,119,122,111,14,93,138,91,45,56,96,129,73,55,103,86,64,15,4,9,60,87,106,0,130,0,8,37,2,0,103, + 0,0,4,101,5,69,0,10,0,13,0,85,64,53,105,6,121,6,137,6,3,127,9,1,9,9,8,1,111,13,6,144,2,160,2,130,0,8,40,15,12,111,16,4,1,4,0,4,115,8,5,15,12,31,12,47,12,111,12,4,12,12,1,11,6,6,1,24, + 0,63,63,51,18,57,47,93,51,51,237,50,71,177,8,130,11,37,50,47,93,49,48,93,69,197,5,8,39,1,51,17,51,21,9,1,33,3,169,180,253,114,2,123,199,188,254,144,254,12,1,244,1,63,254,193,1,63,140, + 3,122,252,136,142,3,67,253,75,0,66,31,9,8,71,69,0,44,0,142,64,94,105,8,121,8,137,8,3,105,7,121,7,137,7,3,74,23,90,23,2,69,19,85,19,2,34,64,13,17,72,34,34,0,110,21,37,36,33,31,111,32, + 32,10,64,21,1,0,21,192,21,208,21,3,21,21,46,11,110,240,10,1,207,10,223,10,2,65,226,5,50,37,115,32,32,26,115,111,40,1,40,40,16,36,115,33,6,16,115,5,65,217,7,32,16,68,186,5,36,93,237, + 51,47,237,130,190,32,93,72,114,5,32,113,130,209,40,237,51,50,50,16,237,50,47,43,65,213,6,65,199,13,33,62,2,67,38,8,8,198,35,19,33,21,33,3,62,1,51,50,30,2,4,75,64,126,187,123,111,165, + 114,67,14,182,11,40,69,101,72,70,114,81,44,42,78,113,72,45,76,65,53,23,176,47,3,33,253,133,29,48,144,99,105,168,118,64,1,188,100,171,123,70,52,91,122,70,21,40,75,59,35,43,80,117,73, + 60,103,76,43,16,28,37,20,2,216,145,254,87,37,53,64,113,156,0,0,0,2,0,151,255,236,4,72,5,90,0,36,0,56,0,171,64,125,122,3,138,3,2,122,2,138,2,2,104,3,120,3,136,3,3,103,35,119,35,135, + 35,3,103,34,119,34,135,34,3,69,55,85,55,2,67,49,83,49,2,74,45,90,45,2,75,39,91,39,2,28,27,44,27,60,27,3,21,110,20,20,0,110,175,37,1,64,37,1,0,37,192,37,208,37,3,37,37,58,29,47,110, + 144,10,160,65,44,5,61,224,10,240,10,4,10,42,115,111,32,1,16,32,32,32,2,32,32,52,24,115,15,143,21,1,21,21,15,7,52,69,236,6,48,51,47,93,16,237,18,57,47,93,93,237,1,47,93,113,237,50,131, + 11,32,113,130,7,32,47,75,36,5,66,250,15,69,241,7,41,2,23,7,46,1,35,34,14,2,21,65,45,5,69,255,15,8,98,4,72,59,115,170,111,123,184,122,61,69,130,187,118,72,126,103,78,23,172,28,123,81, + 74,120,84,45,49,178,115,96,157,111,60,183,36,72,106,70,49,100,81,51,40,75,106,66,65,103,72,38,1,190,100,171,125,70,93,172,245,152,177,1,16,184,95,30,67,110,80,31,91,81,69,134,197,129, + 91,95,62,114,160,107,67,112,80,45,33,65,98,65,72,129,98,57,45,82,116,0,130,0,8,55,1,0,158,0,0,4,45,5,69,0,14,0,53,64,16,5,110,6,6,12,0,111,11,11,16,0,12,16,12,2,12,184,255,192,64,11, + 20,24,72,12,0,12,115,13,6,5,24,0,63,63,237,50,1,47,43,93,130,228,82,43,7,8,45,6,10,2,21,35,52,26,2,55,33,53,33,4,45,96,171,130,76,188,80,134,176,97,253,31,3,143,4,186,138,254,218,254, + 207,254,198,159,158,1,59,1,50,1,36,133,145,130,115,8,81,3,0,133,255,236,4,70,5,90,0,41,0,61,0,81,0,182,64,126,117,40,133,40,2,118,12,134,12,2,120,2,136,2,2,120,8,136,8,2,119,29,135, + 29,2,121,34,137,34,2,120,18,136,18,2,106,70,1,106,64,1,101,80,1,101,74,1,101,60,1,101,54,1,36,16,31,52,110,21,31,110,42,21,130,1,54,10,0,110,64,62,1,0,62,192,62,208,62,3,62,62,83,72, + 110,224,10,240,10,2,66,248,10,54,36,16,16,67,115,57,57,77,118,26,134,26,2,47,115,26,7,121,5,137,5,2,77,65,198,5,34,93,63,237,132,211,34,57,17,51,66,249,10,130,226,37,57,47,47,16,237, + 16,131,8,68,208,15,67,3,7,37,53,52,62,2,55,53,76,239,14,35,21,30,3,3,71,208,14,78,232,15,8,141,4,70,57,117,181,125,124,181,119,57,47,79,101,54,59,93,63,33,57,112,166,109,115,169,111, + 54,33,63,93,61,61,104,77,43,222,27,62,100,73,70,99,63,28,22,58,102,80,86,102,55,17,35,28,68,115,86,79,111,69,32,32,70,114,81,83,111,68,29,1,122,84,145,108,61,61,107,145,83,71,115,84, + 52,9,4,14,62,84,100,53,68,125,96,57,58,96,126,68,52,100,84,61,12,4,10,53,84,113,2,51,47,82,60,35,35,60,82,47,37,82,69,45,46,69,82,253,187,45,89,71,44,44,72,90,47,59,101,74,42,41,75, + 103,0,130,0,38,2,0,141,255,236,4,63,67,91,6,57,165,64,119,122,8,138,8,2,122,7,138,7,2,103,32,119,32,135,32,3,104,26,120,26,136,26,67,84,5,34,69,49,85,67,84,11,8,70,19,17,35,17,51,17, + 3,19,0,110,63,37,143,37,159,37,175,37,4,192,37,208,37,2,37,37,29,58,11,110,10,10,47,110,224,29,240,29,2,207,29,1,0,29,16,29,2,29,52,115,19,24,64,19,22,72,24,24,14,42,115,34,7,14,115, + 5,112,11,128,11,2,70,100,8,68,139,6,33,43,51,68,137,6,33,51,47,71,165,7,32,51,65,136,11,73,73,7,41,2,39,55,30,1,51,50,62,2,55,78,131,9,33,51,50,73,84,16,8,119,4,63,70,132,189,119,81, + 130,102,72,22,172,28,119,91,74,120,85,49,1,21,73,93,107,56,96,155,108,59,63,120,175,111,236,241,196,37,73,107,70,65,104,72,39,35,70,104,69,50,103,83,53,2,191,177,254,242,183,93,33, + 70,112,79,27,91,85,68,132,196,129,47,74,51,27,68,122,169,101,104,169,120,66,254,180,176,73,131,99,59,45,83,115,70,66,115,87,50,34,67,101,0,2,1,240,0,0,2,219,4,58,0,3,0,7,0,36,64,18, + 6,5,74,251,6,37,9,8,4,155,5,15,74,255,5,32,63,75,1,7,40,51,50,49,48,33,17,51,17,3,130,3,42,1,240,235,235,235,1,43,254,213,3,15,131,5,130,70,37,1,96,254,149,3,18,134,75,33,0,1,131,35, + 34,19,33,1,130,39,38,254,133,169,1,9,254,201,133,40,8,49,251,134,2,150,253,106,0,0,0,1,0,116,0,154,4,87,4,170,0,6,0,119,181,0,1,82,5,4,5,184,1,13,64,16,6,0,20,6,0,5,6,1,2,1,82,3,4, + 1,4,3,131,20,47,40,2,1,20,2,1,3,2,6,8,4,0,0,16,0,2,130,61,56,4,4,2,5,63,6,111,6,127,6,3,6,64,18,21,72,6,3,15,2,1,2,0,47,93,77,72,5,38,57,61,47,51,51,1,24,130,14,40,16,222,212,193,135, + 4,43,16,1,132,5,32,196,132,7,63,24,43,135,43,196,49,48,19,53,1,21,9,1,21,116,3,227,252,166,3,90,2,59,205,1,162,154,254,146,254,145,153,130,159,55,2,0,116,1,88,4,87,3,236,0,3,0,7,0, + 76,64,55,7,2,9,4,0,1,16,130,151,62,5,173,31,4,127,4,159,4,175,4,191,4,5,4,64,18,22,72,4,0,173,48,1,80,1,96,1,112,1,4,80,130,4,39,128,1,176,1,208,1,5,15,76,117,5,39,93,113,237,47,43, + 113,237,1,131,140,33,206,50,131,118,36,33,21,1,53,33,132,119,42,29,3,227,3,88,148,148,254,0,148,148,65,19,15,34,6,1,82,130,108,131,254,47,16,0,6,20,0,6,1,0,5,4,1,82,3,2,5,2,65,19,5, + 38,4,5,20,4,5,3,4,65,20,9,42,6,5,2,2,3,15,4,1,4,1,63,77,55,6,41,64,18,21,72,0,0,47,43,93,50,130,123,65,18,8,36,196,16,206,50,16,65,19,23,37,55,53,9,1,53,1,130,155,47,90,252,166,3,227, + 154,153,1,111,1,110,154,254,94,205,0,130,0,48,2,0,94,0,0,4,49,5,90,0,39,0,43,0,109,64,73,72,123,6,49,105,4,121,4,137,4,3,74,27,90,27,2,7,37,23,37,2,102,83,192,5,8,58,9,43,152,10,40, + 40,30,0,152,0,19,1,19,19,45,29,152,30,10,64,15,20,72,10,10,35,41,157,40,102,35,118,35,134,35,3,29,29,24,158,35,4,0,63,237,51,47,93,47,237,17,57,47,43,1,47,237,18,57,80,69,5,35,51,237, + 50,93,67,68,6,39,1,20,14,6,7,35,62,7,74,231,15,8,59,1,53,51,21,4,49,37,62,79,82,79,63,39,1,175,2,39,62,78,80,77,60,37,41,77,109,68,68,111,82,50,7,184,12,74,126,176,112,108,177,126, + 68,253,143,195,3,234,69,104,82,66,59,59,68,83,55,67,102,81,65,131,8,52,53,55,84,57,30,38,71,101,62,12,90,151,108,61,48,93,137,251,188,201,201,132,235,8,80,44,254,229,4,165,5,204,0, + 95,0,120,1,59,64,145,1,68,17,68,2,53,63,85,63,117,63,3,1,62,17,62,2,108,52,124,52,140,52,3,23,24,11,14,72,108,8,124,8,140,8,3,108,102,124,102,2,74,102,90,102,2,38,93,1,106,83,1,101, + 73,117,73,133,73,3,127,63,143,63,2,63,64,130,39,48,126,57,1,3,111,42,127,42,143,42,3,2,79,42,95,42,2,132,61,8,106,106,55,122,55,138,55,3,105,81,1,75,15,76,31,76,2,76,76,86,0,211,55, + 41,47,211,10,96,40,10,106,210,26,55,64,20,24,72,16,55,1,47,26,63,26,2,55,10,26,26,10,55,3,122,65,184,255,232,181,13,17,72,65,211,86,184,255,192,64,38,23,32,72,86,50,111,214,21,15,5, + 21,99,214,33,40,40,25,38,1,38,33,15,21,31,21,47,21,159,21,175,21,5,144,33,160,33,2,33,131,42,45,23,9,12,72,75,21,33,33,21,75,3,70,60,16,130,66,63,60,213,91,0,70,213,81,184,1,15,0,63, + 237,63,237,43,18,23,57,47,47,47,43,93,93,17,51,93,51,47,16,237,88,130,5,34,1,47,43,135,26,34,93,93,43,135,21,130,7,45,57,47,93,51,0,93,1,93,49,48,0,43,93,95,130,1,130,57,32,93,130, + 0,33,1,93,132,8,70,126,8,37,60,1,62,1,53,35,68,246,8,48,4,51,50,30,2,23,51,55,51,3,14,3,21,20,22,51,50,73,157,8,32,1,85,99,8,33,55,23,132,46,38,1,2,53,52,26,1,54,130,48,40,1,18,5,52, + 38,35,34,14,4,83,128,7,8,217,55,62,3,4,165,41,78,114,72,30,52,39,22,1,1,6,15,46,59,73,42,63,86,53,23,23,44,65,81,98,56,42,62,43,28,9,5,32,116,99,8,15,11,6,29,20,40,65,47,25,53,101, + 148,96,120,187,127,66,59,111,162,102,59,102,89,81,38,71,43,97,111,126,73,127,204,142,77,84,161,233,150,131,195,128,63,254,98,70,59,37,64,52,40,27,14,11,27,44,32,39,71,58,46,13,3,8, + 7,4,2,243,144,239,172,96,16,37,60,44,2,11,12,10,3,39,71,54,31,65,107,139,74,76,150,136,117,85,48,27,47,63,36,151,254,5,42,81,73,61,21,52,36,80,145,201,121,129,224,166,96,134,231,254, + 200,177,152,250,179,98,25,42,58,33,87,37,68,53,31,113,208,1,38,182,209,1,99,1,4,146,118,200,254,248,118,106,109,44,75,100,113,119,57,60,103,75,43,55,100,143,87,19,52,54,49,0,0,0,2, + 0,130,0,53,4,204,5,69,0,7,0,20,0,114,64,66,19,2,3,5,1,82,3,13,3,94,67,248,6,8,63,20,1,0,6,1,82,0,13,0,94,7,6,20,7,6,13,4,16,7,1,7,7,22,127,4,143,4,2,4,7,1,2,95,20,19,19,90,13,1,75, + 13,1,13,6,5,3,4,0,18,0,63,50,63,51,51,93,93,57,47,51,237,50,47,1,88,219,5,47,18,57,135,43,135,43,196,16,192,192,16,0,193,135,5,24,134,12,8,75,49,48,33,3,33,3,35,1,51,9,1,46,3,39,14, + 3,7,3,33,4,10,138,253,205,137,196,1,254,217,1,245,253,241,13,28,23,17,4,4,16,23,25,13,151,1,207,1,126,254,130,5,69,250,187,3,186,38,78,70,53,12,12,54,70,78,37,254,89,0,3,0,162,0,0, + 4,128,130,195,8,86,22,0,33,0,46,0,119,64,79,101,45,1,106,36,1,102,32,1,123,2,139,2,2,116,11,132,11,148,11,3,7,11,1,111,18,1,75,18,91,18,2,18,41,13,90,23,64,13,20,72,23,23,6,0,90,0, + 34,1,34,34,48,28,41,90,0,6,16,6,160,6,176,6,4,6,18,40,95,28,28,41,27,95,7,3,41,95,6,130,210,76,9,5,33,237,57,130,206,74,209,5,40,237,18,57,47,43,237,17,57,93,89,128,9,39,1,20,14,2, + 35,33,17,33,85,227,9,39,1,52,38,43,1,17,51,50,87,213,5,130,10,32,33,130,10,8,85,4,128,84,142,188,104,254,40,1,156,117,184,128,67,33,67,100,68,85,131,88,46,254,238,151,151,223,225,81, + 114,72,33,81,51,93,130,80,252,1,10,73,125,91,51,1,124,103,144,91,42,5,69,38,79,124,86,56,101,81,59,14,9,56,87,113,2,40,107,91,254,96,31,57,80,253,217,63,88,57,26,254,34,23,56,94,0, + 130,0,8,148,1,0,113,255,236,4,118,5,90,0,41,0,164,64,45,57,39,1,70,29,86,29,102,29,3,102,28,1,54,3,1,32,31,48,31,2,96,31,112,31,2,160,31,1,31,31,32,64,29,36,72,32,32,10,11,11,176,10, + 1,10,184,255,192,64,69,9,12,72,10,10,43,118,0,134,0,2,0,91,48,21,64,21,80,21,3,0,21,16,21,2,21,70,26,86,26,2,37,95,26,32,32,26,4,16,10,32,10,48,10,96,10,112,10,5,48,10,128,10,208,10, + 224,10,4,10,10,73,16,89,16,2,5,95,16,19,0,63,237,93,51,47,93,113,63,51,47,16,237,93,1,130,9,37,237,93,18,57,47,43,130,20,35,17,51,47,43,131,26,32,114,69,108,8,37,30,2,51,50,62,2,67, + 126,10,32,18,75,252,5,8,85,23,7,46,3,35,34,14,2,1,58,40,84,131,92,63,101,79,58,21,159,28,82,116,155,102,141,205,135,65,64,132,202,138,101,152,111,77,25,168,15,52,73,97,60,89,127,82, + 38,2,169,131,203,140,72,48,78,102,54,65,70,133,105,64,101,184,1,2,158,166,1,1,175,91,55,93,121,66,65,46,88,68,42,68,135,199,0,130,0,35,2,0,162,0,78,215,5,8,72,12,0,23,0,97,64,69,117, + 2,133,2,2,122,16,138,16,2,112,23,128,23,2,20,23,36,23,100,23,3,127,15,143,15,2,27,15,43,15,107,15,3,52,11,1,52,10,1,59,3,1,0,90,0,13,160,13,2,13,13,25,20,90,0,6,16,6,2,6,19,95,7,3, + 20,66,31,7,78,224,7,76,230,12,73,143,5,66,20,5,8,56,7,52,46,2,43,1,17,51,50,18,4,101,80,152,221,140,254,142,1,55,158,243,166,85,192,56,114,172,117,121,162,212,206,2,176,168,254,255, + 174,89,5,69,77,162,250,172,135,192,121,57,251,243,1,10,0,0,1,132,179,60,98,5,69,0,11,0,90,64,59,7,64,10,20,72,7,7,0,10,3,3,0,10,1,10,10,13,5,9,90,72,174,5,43,8,95,15,5,63,5,127,5,143, + 5,4,111,130,4,37,159,5,191,5,223,5,130,0,39,9,4,95,1,3,9,95,0,66,199,8,34,113,114,237,66,200,8,44,51,47,17,18,57,47,43,49,48,51,17,33,21,130,168,132,3,51,162,3,151,253,40,2,156,253, + 100,3,1,5,69,156,254,90,154,254,51,156,130,135,36,194,0,0,4,61,130,135,44,9,0,71,64,45,2,2,6,8,8,11,1,5,65,11,6,60,4,95,63,1,127,1,143,1,3,63,1,111,1,159,1,3,1,64,21,41,72,1,1,5,0, + 95,7,3,5,130,121,34,63,237,18,130,102,32,93,137,121,85,33,5,132,116,32,35,130,5,47,1,129,2,164,253,92,191,3,123,4,169,254,18,158,253,227,130,119,66,211,8,32,79,130,100,8,64,41,0,136, + 64,74,8,2,1,21,30,37,30,117,30,133,30,4,85,18,1,39,39,10,37,175,21,1,21,21,20,20,0,90,192,37,208,37,2,127,37,143,37,2,0,37,1,37,37,43,54,29,118,29,134,29,182,29,4,29,91,48,10,64,10, + 80,81,211,7,8,33,39,95,40,40,32,15,184,255,240,64,14,13,17,72,24,95,15,21,21,15,4,32,95,5,19,0,63,237,63,51,47,16,237,43,130,153,78,187,5,32,93,130,9,41,93,93,93,237,50,47,51,47,93, + 17,132,176,32,0,130,15,32,37,70,45,8,66,174,9,78,186,5,8,86,16,18,51,50,62,2,55,17,33,53,33,4,77,36,95,116,137,78,134,198,130,64,62,130,200,137,100,151,111,75,24,171,38,139,111,90, + 126,79,35,169,172,48,83,67,49,13,254,219,1,223,133,24,54,45,30,105,186,1,1,153,167,1,1,175,90,52,89,121,69,56,110,121,69,135,199,130,254,244,254,234,17,23,25,9,1,80,160,66,3,7,47,42, + 5,69,0,11,0,87,64,60,11,90,8,31,0,47,0,73,128,5,8,41,5,111,0,127,0,143,0,191,0,4,0,0,13,7,3,90,112,4,1,0,4,16,4,160,4,176,4,4,4,2,95,80,7,1,160,7,176,7,2,7,7,9,69,159,9,35,57,47,93, + 113,133,227,79,159,5,42,51,237,49,48,33,17,33,17,35,17,51,130,5,8,32,51,17,3,107,253,246,191,191,2,10,191,2,111,253,145,5,69,253,202,2,54,250,187,0,0,0,1,0,202,0,0,4,1,132,135,46,89, + 64,56,6,6,1,14,4,7,7,0,0,9,14,112,130,27,47,4,90,15,9,63,9,143,9,159,9,4,79,9,95,9,127,130,10,57,207,9,223,9,6,16,9,1,9,9,12,13,4,8,95,7,18,3,11,95,0,3,0,63,237,50,130,2,34,17,18,1, + 130,136,130,137,47,196,93,43,17,1,51,24,47,16,77,228,50,47,49,48,19,66,137,6,32,53,130,146,53,202,3,55,254,196,1,60,252,201,1,60,254,196,5,69,156,251,243,156,156,4,13,130,138,130,111, + 36,176,255,236,3,218,130,139,61,21,0,75,183,101,16,117,16,133,16,3,11,184,255,232,64,34,13,17,72,19,19,8,0,90,17,17,8,23,128,131,124,59,0,8,16,8,2,8,19,95,20,3,14,95,5,9,9,5,19,0,63, + 51,47,16,237,63,237,1,47,93,65,219,6,32,237,65,223,5,40,43,93,1,20,14,2,35,34,38,83,147,7,8,42,17,33,53,33,3,218,51,101,150,98,171,204,35,187,10,42,58,73,41,104,105,254,201,1,245,1, + 164,100,163,115,62,178,192,31,64,93,60,29,143,138,3,9,156,65,167,7,32,203,130,147,8,72,11,0,151,64,52,116,10,132,10,2,70,10,86,10,102,10,3,100,7,116,7,132,7,3,141,0,1,107,0,123,0,2, + 73,0,89,0,2,57,10,1,20,10,1,1,32,11,17,72,10,1,11,114,9,130,9,2,9,184,255,224,64,45,11,15,72,8,9,16,9,9,0,11,16,130,2,57,11,2,11,11,13,7,3,90,0,4,16,4,2,4,2,7,10,1,118,7,134,7,2,7, + 1,8,65,209,10,37,57,93,17,51,17,51,130,207,34,237,50,17,130,210,53,56,51,51,47,56,51,43,93,17,57,57,0,43,49,48,93,93,1,93,93,93,0,131,5,36,33,1,7,17,35,130,43,8,33,1,51,9,1,3,235,254, + 25,163,191,191,2,60,225,254,8,2,69,2,126,169,254,43,5,69,253,95,2,161,253,196,252,247,0,131,207,36,237,0,0,4,76,130,207,38,5,0,38,64,22,4,4,130,133,36,47,0,63,0,2,68,113,5,34,1,3,3, + 68,86,6,130,119,32,93,65,65,5,130,132,43,17,33,21,237,191,2,160,5,69,251,87,156,131,67,32,129,130,67,32,75,130,67,51,44,0,153,185,0,30,255,232,64,19,13,17,72,38,30,54,30,2,42,24,130, + 9,47,41,42,57,42,2,12,184,255,240,64,25,11,17,72,13,16,131,4,47,12,36,36,28,44,92,42,0,64,25,28,72,0,0,46,30,131,29,52,13,10,17,0,76,30,27,92,0,28,16,28,2,28,184,255,192,179,40,45, + 72,131,7,42,64,9,24,30,72,28,6,21,21,29,36,130,21,43,64,12,14,17,72,36,13,13,42,29,3,28,72,142,6,35,57,47,51,43,65,29,5,8,89,43,43,93,237,50,43,18,57,47,43,51,237,17,57,17,51,51,43, + 43,49,48,93,43,93,43,33,17,52,54,55,54,55,6,7,14,1,7,3,35,3,46,3,39,38,39,22,23,30,1,21,17,35,17,51,19,30,1,23,22,23,54,55,62,1,55,19,51,17,3,169,1,2,2,1,17,17,14,33,14,164,137,166, + 6,14,15,16,8,18,19,1,130,0,8,60,160,237,185,9,23,11,12,13,14,12,11,24,9,185,225,3,100,51,106,44,51,49,55,53,45,100,38,254,74,1,182,15,41,47,49,24,55,60,48,52,45,105,51,252,156,5,69, + 254,23,23,77,38,44,49,47,43,37,78,25,1,234,67,95,6,8,33,162,0,0,4,42,5,69,0,19,0,120,64,36,18,92,0,24,9,17,72,0,128,17,144,17,208,17,224,17,4,63,17,1,192,130,9,8,68,2,111,17,127,17, + 143,17,3,17,17,21,10,184,255,232,64,45,11,17,72,10,7,92,112,8,1,0,8,16,8,160,8,176,8,4,8,140,1,1,122,1,1,105,1,1,17,1,9,3,131,11,1,117,11,1,102,11,1,11,0,8,18,0,63,51,51,93,93,93,133, + 5,35,1,47,93,113,65,17,5,42,93,93,113,113,51,43,237,49,48,33,1,136,248,38,1,38,39,46,1,53,17,130,243,8,35,68,253,248,4,4,3,5,170,222,2,16,5,4,4,5,172,4,106,43,45,38,89,39,252,148,5, + 69,251,142,40,46,39,98,51,3,96,132,194,8,55,0,2,0,102,255,236,4,102,5,90,0,17,0,29,0,121,64,86,118,29,134,29,2,52,29,1,118,25,134,25,2,52,25,1,121,23,137,23,2,59,23,1,121,19,137,19, + 2,59,19,1,86,16,1,69,16,1,90,115,14,42,0,91,95,18,1,18,64,25,28,72,0,130,7,47,18,31,24,91,80,10,1,16,10,1,10,21,95,15,4,27,69,174,6,68,194,5,37,18,57,47,93,43,113,71,119,12,130,222, + 32,93,90,85,16,8,67,0,3,16,2,35,34,2,17,16,18,51,50,18,4,102,70,132,191,120,127,192,128,64,68,130,191,124,247,1,8,201,158,152,156,156,158,153,163,148,2,169,173,254,250,177,89,94,179, + 1,5,167,173,1,2,172,86,254,165,254,170,1,14,1,7,254,249,254,242,130,1,34,236,1,24,130,227,8,37,162,0,0,4,95,5,69,0,14,0,25,0,91,64,62,102,23,1,118,0,134,0,2,73,15,89,15,105,15,3,0, + 90,127,15,143,15,2,15,132,191,39,15,1,15,15,27,20,7,90,65,155,9,46,6,95,20,64,9,17,72,20,20,7,19,95,9,3,7,71,30,8,71,150,9,34,43,93,237,74,90,5,32,1,74,85,5,38,35,17,33,50,30,2,7,74, + 80,8,8,45,4,95,59,121,182,123,254,231,191,1,204,125,186,124,62,192,164,164,246,254,82,121,79,38,3,177,84,155,120,72,253,254,5,69,58,106,149,94,125,129,253,237,43,74,102,0,130,175,8, + 76,102,254,125,4,113,5,90,0,32,0,44,0,139,64,98,53,44,117,44,133,44,3,53,40,117,40,133,40,3,58,38,122,38,138,38,3,58,34,122,34,138,34,3,105,17,121,17,137,17,3,74,4,90,4,2,70,31,86, + 31,2,70,28,86,28,2,5,90,20,11,11,0,91,33,95,33,1,33,132,214,59,33,1,20,33,20,33,46,39,91,80,25,1,16,25,1,25,36,95,30,4,5,42,95,20,19,8,96,95,130,242,39,0,47,93,237,63,237,51,63,65, + 163,7,41,57,47,47,93,43,113,16,237,50,47,97,46,6,32,93,132,0,131,223,97,4,9,39,35,34,46,2,39,46,3,53,92,6,5,65,176,14,53,51,98,141,90,41,134,101,28,64,23,38,91,49,85,129,97,70,27,112, + 168,113,56,65,190,17,52,147,235,172,109,22,126,114,8,5,134,9,13,51,95,138,87,11,103,178,249,156,65,201,23,32,0,65,203,6,8,78,153,5,69,0,17,0,27,0,151,185,0,26,255,240,179,15,0,77,16, + 184,255,224,64,35,15,17,0,76,137,1,1,11,0,1,0,32,14,17,72,0,17,16,17,17,1,4,16,1,16,16,3,118,11,134,11,2,11,90,18,184,255,192,64,54,21,24,72,127,18,143,18,2,0,18,1,18,18,29,22,69,172, + 5,71,131,5,53,16,2,95,31,22,127,22,2,63,22,111,22,159,22,239,22,4,22,22,0,21,95,69,186,8,37,237,18,57,47,93,113,89,33,5,32,50,131,11,36,93,43,237,93,17,130,19,48,51,50,47,56,51,43, + 93,93,49,48,43,0,43,33,1,35,17,66,4,5,8,203,21,20,14,2,7,1,3,52,41,1,17,33,50,62,2,3,189,254,146,238,191,1,226,120,179,119,59,39,84,130,91,1,144,248,254,208,254,240,1,24,83,113,69, + 31,2,63,253,193,5,69,51,97,138,87,62,123,103,76,15,253,171,3,205,223,254,42,40,67,90,0,0,0,1,0,79,255,236,4,104,5,90,0,63,0,176,64,121,52,61,1,109,45,1,75,29,91,29,2,107,16,1,96,12, + 1,116,62,132,62,2,116,61,1,64,61,80,61,2,134,60,1,68,60,84,60,2,118,55,134,55,2,116,39,132,39,2,4,33,116,33,132,33,3,84,11,1,137,7,1,122,7,1,139,2,1,121,2,1,42,90,41,41,0,90,0,19,1, + 19,19,31,65,9,90,127,8,143,8,2,8,52,90,0,31,16,31,2,31,52,19,5,47,96,36,42,42,36,4,14,95,5,64,9,80,9,2,71,138,7,33,93,16,73,123,5,44,18,57,57,1,47,93,237,214,93,237,17,18,57,130,8, + 32,50,88,53,14,32,93,70,199,5,130,5,37,20,14,2,35,34,36,71,164,5,79,210,5,32,39,102,228,5,35,51,50,30,2,76,63,7,33,21,20,130,11,8,123,30,5,4,104,62,127,195,134,234,254,251,36,185,12, + 48,84,122,85,72,119,85,48,65,106,133,68,50,102,94,81,61,34,72,126,172,100,116,163,113,68,20,188,11,43,71,100,68,80,108,66,29,53,91,121,69,55,112,103,89,66,38,1,114,85,143,104,58,184, + 174,37,55,90,65,36,27,58,90,63,70,88,56,35,17,13,29,39,53,75,101,67,96,137,87,40,41,82,121,80,33,51,80,54,28,33,57,78,45,60,75,49,32,18,14,31,42,57,82,110,0,0,1,0,76,0,78,151,5,37, + 7,0,74,64,49,7,96,251,5,51,1,90,15,2,63,2,143,2,159,2,191,2,207,2,6,79,2,95,2,127,130,14,52,207,2,223,2,6,16,2,1,2,2,8,9,0,4,95,5,3,1,18,0,63,73,34,11,32,198,96,252,14,47,21,2,197, + 190,254,69,4,52,4,169,251,87,4,169,156,156,131,111,8,43,142,255,236,4,61,5,69,0,25,0,94,64,68,101,21,117,21,133,21,3,101,15,117,15,133,15,3,0,90,128,23,144,23,208,23,224,23,240,23, + 5,63,23,1,192,130,11,52,2,127,23,143,23,2,23,23,27,13,90,143,10,159,10,175,10,3,207,10,1,91,21,5,35,24,11,3,18,69,99,6,40,51,1,47,93,93,113,237,18,57,131,6,32,113,69,101,5,33,1,20, + 102,176,6,34,17,51,17,100,162,6,8,47,53,17,51,4,61,58,119,181,122,116,174,115,58,191,25,61,105,80,83,111,68,29,190,1,234,137,194,123,56,53,117,187,135,3,109,252,173,97,138,88,40,40, + 89,144,103,3,70,0,131,175,36,10,0,0,4,194,130,175,8,88,16,0,130,64,85,15,32,14,17,72,41,15,57,15,2,43,15,1,118,3,134,3,2,84,3,100,3,2,54,3,70,3,2,36,3,1,0,1,82,15,9,15,94,16,0,20,16, + 16,0,1,1,82,3,9,3,94,2,1,20,2,1,3,2,9,2,16,16,1,16,16,18,127,2,143,2,2,2,15,2,3,85,9,1,68,9,1,9,0,1,70,234,6,32,63,131,195,49,17,51,47,93,18,57,16,0,193,135,5,43,135,43,196,135,24, + 16,85,158,5,33,1,93,132,0,37,43,33,35,1,51,1,71,231,9,8,38,1,51,2,201,198,254,7,201,1,64,16,29,12,14,13,12,14,12,30,16,1,62,201,5,69,252,122,45,97,42,48,47,45,48,41,98,47,3,134,132, + 199,81,67,7,44,50,1,76,64,17,33,8,12,0,77,2,24,16,131,4,40,11,12,0,76,16,184,255,232,180,131,8,48,26,184,255,240,64,16,16,17,0,76,49,24,17,0,77,49,24,131,20,32,20,130,20,132,29,43, + 35,184,255,224,64,28,11,15,0,76,34,24,132,5,38,16,10,0,77,1,32,11,130,46,33,0,32,131,52,32,0,130,32,36,180,13,14,0,76,132,8,36,9,10,0,76,18,130,59,33,64,17,131,27,33,18,40,131,24,33, + 18,40,131,21,32,17,130,39,33,64,19,131,55,63,34,9,50,9,2,2,4,9,1,1,1,82,35,43,35,184,1,7,64,12,9,1,20,9,1,35,17,1,82,34,26,34,131,16,61,72,9,17,20,9,9,17,0,1,82,49,43,49,94,50,0,20, + 50,50,0,18,1,82,20,26,20,94,19,18,20,131,2,8,47,43,9,26,3,19,16,50,1,50,50,52,19,9,47,34,79,34,95,34,3,34,34,26,49,19,3,43,26,0,1,36,26,1,26,17,1,3,18,18,0,63,23,51,93,17,51,17,51, + 93,242,6,39,1,47,17,51,47,93,18,23,65,113,16,65,120,6,82,6,8,37,0,93,95,93,1,43,141,0,33,49,48,131,15,32,33,73,134,6,39,6,7,14,3,7,3,35,3,73,134,9,34,3,55,19,130,12,38,3,23,22,23,50, + 62,2,130,11,8,38,3,250,208,104,5,14,15,15,7,17,17,18,17,7,15,15,14,5,106,208,211,189,109,8,11,5,5,4,16,16,7,14,15,14,6,109,175,109,6,130,7,8,113,7,16,16,1,6,10,12,6,104,189,1,131,19, + 52,59,63,30,70,76,77,70,30,63,59,51,19,254,125,5,69,252,189,65,124,49,57,51,69,67,29,61,60,55,23,1,143,254,113,22,56,59,61,29,67,70,68,102,122,54,3,67,0,0,1,0,36,0,0,4,169,5,69,0,11, + 0,200,64,92,9,9,1,11,6,1,9,3,89,3,2,41,1,1,4,1,1,3,2,7,0,1,8,11,4,11,6,2,7,9,1,8,10,5,10,6,130,18,8,96,2,9,0,1,7,2,1,82,1,8,7,8,1,94,2,7,20,2,2,7,5,10,1,82,11,4,5,4,11,94,10,5,20,10, + 5,11,10,16,10,32,10,2,2,10,2,10,3,6,0,9,4,8,4,184,255,192,64,24,10,28,72,4,4,13,8,64,11,27,72,8,0,9,3,6,4,1,8,4,18,10,1,3,0,63,51,63,51,18,23,57,1,47,43,17,51,47,43,130,9,36,57,57, + 47,47,113,66,221,5,134,5,36,196,135,24,16,43,135,11,36,16,192,192,16,135,131,3,34,8,192,8,130,9,131,5,46,49,48,1,93,93,0,93,93,93,9,1,51,9,1,35,132,2,8,37,51,2,102,1,77,205,254,77, + 1,220,205,254,138,254,139,205,1,220,254,77,205,3,68,2,1,253,124,253,63,2,61,253,195,2,193,2,132,0,65,7,7,8,61,168,5,69,0,8,0,150,64,104,52,6,68,6,84,6,3,92,7,1,59,7,75,7,2,9,7,25,7, + 41,7,3,83,5,1,52,5,68,5,2,3,0,5,16,5,32,5,3,12,5,7,8,14,1,4,2,14,5,47,4,1,4,6,69,9,22,51,4,16,2,1,2,2,10,9,6,3,64,0,80,0,96,0,3,63,0,1,130,112,33,7,4,69,18,5,39,51,18,57,47,93,93,51, + 51,78,59,8,35,57,198,93,50,69,27,5,39,50,49,48,95,94,93,95,93,131,0,37,0,93,1,17,35,17,131,216,55,51,2,196,188,254,28,205,1,118,1,116,205,2,72,253,184,2,72,2,253,253,157,2,99,130,85, + 36,73,0,0,4,131,130,195,8,50,9,0,117,64,38,137,8,1,105,8,1,87,8,119,8,2,41,8,57,8,73,8,3,4,8,20,8,2,8,1,82,3,2,3,94,7,8,20,7,8,3,7,4,184,255,192,179,15,20,72,0,130,7,62,64,29,9,14, + 72,4,7,0,0,7,4,3,11,47,1,63,1,79,1,3,1,7,4,95,5,3,2,8,95,1,18,78,227,6,34,1,47,93,86,232,6,65,139,6,36,135,43,196,49,48,68,116,5,63,41,1,53,1,33,53,33,21,1,33,4,131,251,198,3,50,253, + 23,3,194,252,206,3,97,143,4,26,156,139,251,226,0,130,246,61,1,154,254,87,3,189,5,204,0,7,0,47,64,28,2,7,7,9,5,242,127,0,143,0,2,63,0,111,0,2,130,32,40,0,4,245,1,0,5,245,0,27,130,111, + 32,63,96,166,9,32,51,130,99,35,17,33,21,33,130,3,48,1,154,2,35,254,145,1,111,254,87,7,117,139,249,161,139,0,130,0,38,1,0,115,255,236,4,90,130,87,45,3,0,63,64,29,139,0,1,122,0,1,57, + 0,105,130,82,63,3,16,3,3,5,133,2,1,54,2,102,2,118,2,3,2,1,184,255,240,183,0,1,1,1,2,0,0,25,0,63,63,130,208,33,56,51,102,88,8,45,93,49,48,5,1,51,1,3,164,252,207,178,3,53,102,90,7,130, + 46,36,14,254,87,3,49,132,183,46,41,64,23,7,242,96,2,1,2,2,9,4,0,0,48,130,98,36,4,245,5,0,1,138,178,37,51,18,57,47,93,237,130,177,32,53,130,175,130,3,33,1,14,130,173,32,145,130,181, + 37,87,139,6,95,139,248,130,177,61,1,0,133,1,186,4,70,5,69,0,6,0,99,64,47,133,5,1,121,4,137,4,2,5,4,1,140,0,1,41,130,183,51,121,0,3,0,1,3,6,16,0,6,16,6,112,6,128,6,4,6,6,8,131,191,32, + 134,130,193,32,3,130,193,36,64,15,0,3,1,130,34,32,16,130,134,35,0,0,1,4,130,246,36,51,51,47,50,93,133,205,131,127,34,56,18,57,130,214,33,17,51,130,4,36,49,48,9,2,35,131,213,53,172, + 254,183,254,186,152,1,122,203,1,124,1,186,3,35,252,221,3,139,252,117,0,130,0,40,1,255,251,255,36,4,209,255,132,130,118,56,14,180,3,0,0,187,1,0,47,237,1,47,47,49,48,7,53,33,21,5,4,214, + 220,96,96,131,39,61,1,146,4,177,3,58,5,180,0,5,0,26,64,14,116,3,132,3,2,128,4,1,4,1,3,140,0,147,0,63,130,49,54,205,93,49,48,93,1,37,53,51,23,21,2,190,254,212,197,227,4,177,230,29,239, + 20,130,59,8,59,2,0,128,255,236,4,136,4,78,0,50,0,65,0,165,64,63,138,36,1,122,30,138,30,2,105,61,121,61,137,61,3,37,54,53,54,2,19,32,9,12,72,6,23,22,23,2,29,9,45,9,61,9,3,5,45,21,45, + 2,0,130,235,8,81,3,48,70,27,12,79,56,95,56,2,56,56,21,67,38,71,39,184,255,248,64,45,192,39,1,39,39,63,71,0,21,1,21,57,81,27,27,51,44,15,38,31,38,2,47,38,143,38,255,38,3,38,38,33,80, + 44,16,51,80,18,0,81,7,7,12,18,22,0,63,51,51,47,237,16,237,63,237,51,47,93,113,17,83,23,6,131,12,32,56,73,159,5,36,51,51,237,50,47,87,1,6,33,43,93,130,0,55,37,50,54,55,21,14,1,35,34, + 46,2,39,35,14,3,35,34,38,53,52,62,2,63,1,91,79,13,8,218,22,21,17,20,22,37,50,62,2,61,1,7,14,3,21,20,22,4,77,15,30,14,34,67,38,51,73,46,24,3,6,28,68,88,114,74,158,160,79,128,163,84, + 236,27,55,84,56,50,81,58,37,6,188,10,55,100,151,107,200,201,42,253,218,83,122,81,40,190,55,111,89,56,89,111,4,3,112,8,8,27,55,81,54,52,84,59,32,172,150,107,137,78,31,1,4,59,67,94,58, + 27,15,39,67,51,17,64,107,78,43,187,177,254,46,80,81,6,63,96,116,53,89,4,1,15,48,91,76,82,100,0,2,0,179,255,236,4,66,5,204,0,32,0,52,0,97,64,66,122,2,138,2,2,68,51,84,51,100,51,3,75, + 35,91,35,107,35,3,58,5,1,53,28,1,5,31,21,31,117,31,133,31,4,0,71,96,33,1,33,33,54,43,6,20,70,0,19,128,19,2,19,38,80,26,30,16,19,0,13,21,48,80,6,3,22,0,63,51,237,63,63,130,4,37,1,47, + 93,237,50,50,66,210,6,33,0,93,132,0,41,1,16,2,35,34,38,39,35,20,14,108,61,5,45,17,51,17,20,6,7,6,7,51,62,1,51,50,18,97,26,15,50,4,66,203,189,116,170,48,2,2,3,3,1,174,1,2,2,1,180,1, + 130,0,8,74,4,48,169,117,195,196,186,30,63,95,66,66,106,73,40,40,73,105,65,63,96,64,33,2,34,254,228,254,230,86,98,26,55,46,33,4,9,43,60,72,39,4,237,254,89,29,57,22,26,23,105,95,254, + 235,254,225,112,161,103,49,45,103,169,124,119,158,95,39,46,102,163,0,1,0,130,130,251,8,70,56,4,78,0,37,0,157,64,82,100,22,1,100,16,1,121,30,137,30,2,100,26,1,68,21,84,21,2,75,17,91, + 17,2,117,8,133,8,2,11,71,10,10,28,71,63,27,79,27,143,27,159,27,175,27,5,207,27,223,27,255,27,3,96,27,1,27,27,39,19,71,0,130,77,8,42,112,27,1,0,27,16,27,96,27,112,27,128,27,192,27,6, + 27,184,255,192,64,28,27,30,72,27,27,24,80,33,22,14,80,5,31,11,127,11,143,11,3,11,64,130,20,41,11,11,5,16,0,63,51,47,43,93,66,87,5,32,43,106,24,9,34,93,113,237,75,239,10,38,1,93,93, + 19,52,62,2,75,206,6,85,95,5,33,20,30,130,15,32,54,88,47,6,8,157,2,130,86,141,180,95,98,154,111,70,13,192,16,128,118,86,117,71,31,31,71,117,85,108,142,17,190,9,68,114,158,100,137,191, + 119,54,2,30,163,216,128,53,50,87,118,68,14,91,105,55,105,155,100,100,159,110,58,106,109,12,67,124,94,57,86,151,205,0,2,0,138,255,230,4,25,5,204,0,31,0,51,0,107,64,75,68,34,84,34,100, + 34,3,75,50,91,50,107,50,3,58,1,106,1,2,53,10,1,5,7,21,7,117,7,133,7,4,10,4,26,4,122,4,138,4,4,19,70,42,0,80,18,1,144,18,160,18,2,18,18,53,32,71,0,6,1,6,26,21,18,0,47,80,11,8,16,37, + 80,0,66,16,6,33,51,237,110,172,9,34,113,51,51,80,164,9,33,37,14,130,210,47,2,17,16,33,50,22,23,51,52,38,52,38,53,17,51,17,130,223,37,23,35,46,3,53,1,132,232,92,169,8,8,94,2,3,94,49, + 162,121,202,190,1,136,121,163,49,2,1,1,180,1,2,2,1,172,1,4,3,2,253,225,29,61,96,67,69,107,73,37,37,72,106,69,64,96,63,32,174,105,95,1,22,1,24,2,54,86,98,5,42,50,45,9,1,163,251,19,39, + 72,60,43,9,4,36,50,58,26,1,108,112,160,104,49,48,105,168,120,116,157,96,42,46,102,163,0,0,0,2,100,127,5,8,65,4,78,0,32,0,43,0,144,64,101,100,36,1,100,3,1,73,41,89,41,105,41,3,73,36, + 89,36,2,117,29,133,29,2,119,28,135,28,2,120,14,136,14,2,122,13,138,13,2,68,2,84,2,100,2,3,39,71,31,31,11,73,207,10,223,10,255,87,97,5,8,45,96,10,3,10,10,45,38,0,71,0,21,224,21,2,21, + 0,80,38,38,5,33,80,26,16,5,80,16,223,10,239,10,2,128,10,1,10,10,16,22,0,63,51,47,93,93,16,91,88,6,88,142,8,32,93,66,2,11,34,93,93,1,130,2,65,11,6,65,245,7,32,53,66,20,6,8,157,29,1, + 1,34,14,2,7,33,46,3,1,66,38,76,113,74,55,93,74,50,12,158,16,64,106,152,104,115,182,126,67,79,134,175,96,131,181,114,51,254,37,46,101,86,59,3,2,72,8,52,77,98,1,247,85,143,103,57,25, + 44,61,36,45,45,91,73,47,72,142,213,141,156,212,130,56,84,151,212,128,24,1,210,29,74,127,98,94,126,76,32,0,0,0,1,0,138,0,0,4,75,5,204,0,31,0,94,185,0,10,255,240,64,54,9,12,72,29,1,70, + 2,63,5,1,5,5,6,2,30,30,17,47,2,63,2,111,2,127,2,143,2,5,2,64,20,37,72,0,2,1,2,17,2,17,32,33,23,80,12,130,61,43,80,29,5,15,1,21,0,63,63,51,237,50,98,221,5,44,57,47,47,93,43,93,17,51, + 47,17,51,51,47,130,251,43,50,49,48,0,43,1,17,35,17,33,53,33,135,214,33,23,21,90,250,5,8,57,29,1,33,21,2,68,180,254,250,1,6,45,100,159,114,29,80,80,71,21,18,72,79,72,19,66,98,64,31, + 1,235,3,172,252,84,3,172,142,25,101,143,91,42,3,6,8,4,145,3,6,5,3,20,54,94,75,10,142,130,190,8,131,0,2,0,143,254,88,4,30,4,75,0,49,0,69,0,156,64,62,122,48,138,48,2,102,63,1,69,12,85, + 12,101,12,3,75,58,91,58,107,58,3,53,33,1,5,29,21,29,117,29,133,29,4,10,23,26,23,122,23,138,23,4,47,70,50,34,80,14,1,144,14,160,14,2,14,14,26,71,6,70,5,186,255,240,0,5,255,192,64,40, + 15,21,72,5,5,60,71,176,26,1,0,26,208,26,2,26,40,15,55,80,34,31,16,65,80,15,21,21,9,80,0,32,6,48,6,2,6,6,0,27,0,63,50,131,224,130,250,131,2,32,1,118,195,5,32,43,70,53,6,67,7,12,37,93, + 1,34,46,2,39,100,173,6,33,61,1,95,184,9,48,2,51,50,22,23,51,52,62,2,55,51,14,3,21,17,20,6,102,48,15,8,124,2,103,90,137,99,61,13,184,17,113,92,57,91,65,35,2,19,59,83,110,72,102,144, + 92,43,43,95,152,110,113,167,45,3,3,3,5,1,171,1,2,2,1,212,33,48,79,101,53,67,98,64,30,30,63,97,66,53,102,80,49,254,88,38,71,98,60,25,75,81,34,75,120,86,194,40,76,58,35,66,134,202,135, + 131,204,141,74,105,97,25,62,55,40,3,9,43,60,73,39,252,198,227,229,3,207,113,158,99,44,45,99,157,113,117,156,94,39,43,96,155,0,1,0,185,0,0,68,51,6,8,47,63,64,39,112,14,128,14,2,4,13, + 20,13,116,13,132,13,4,17,70,18,18,33,5,2,30,70,0,31,128,31,2,31,31,17,21,24,80,5,11,16,0,0,0,63,63,51,237,63,87,159,5,34,50,18,57,80,215,6,33,19,51,130,216,43,7,51,62,3,51,50,30,2, + 21,17,35,17,103,36,7,8,54,17,35,185,181,6,3,3,24,64,82,105,64,83,130,90,47,181,33,59,80,48,59,103,77,44,180,5,204,254,122,50,101,46,47,76,53,29,44,92,144,101,253,47,2,183,80,103,61, + 24,45,85,125,81,253,141,65,251,6,8,38,0,0,4,101,5,204,0,9,0,13,0,72,64,40,7,7,5,4,4,5,11,74,12,12,1,1,0,70,160,5,1,5,5,14,15,11,48,10,96,67,140,5,40,7,80,8,15,0,4,80,3,21,74,177,5, + 34,47,93,113,115,158,5,41,113,237,50,47,50,47,237,17,51,47,130,174,40,49,48,37,33,21,33,53,33,17,130,3,58,39,53,51,21,2,233,1,124,252,42,1,166,254,193,1,243,200,200,142,142,142,3,30, + 142,210,192,192,133,127,36,117,254,87,3,68,130,127,8,49,25,0,29,0,75,64,44,117,19,133,19,2,10,3,26,3,2,24,24,10,21,26,74,29,29,0,70,0,21,1,21,21,30,31,10,48,26,1,128,26,1,26,27,23, + 80,24,15,16,80,5,74,197,5,32,47,130,131,32,1,130,132,82,1,6,91,121,8,32,5,80,118,6,40,39,53,30,3,51,50,62,2,53,135,141,8,45,3,68,71,118,156,86,41,82,76,65,24,22,60,72,79,40,56,97,72, + 41,254,105,2,75,200,200,28,109,151,95,42,7,12,15,9,140,5,12,10,7,28,61,95,67,3,197,134,168,34,1,0,236,65,39,6,8,76,11,0,175,64,44,102,7,118,7,134,7,3,100,9,116,9,132,9,3,123,1,139, + 1,2,73,1,89,1,105,1,3,73,8,89,8,2,107,0,123,0,139,0,3,73,0,89,0,2,10,184,255,232,64,40,11,17,72,1,10,8,9,16,68,9,84,9,2,9,9,0,159,11,175,11,191,11,3,89,175,5,49,16,11,11,13,7,3,70, + 0,4,1,4,184,255,192,179,31,37,72,132,7,33,22,27,132,7,57,64,17,13,17,72,4,2,7,10,1,7,1,8,15,5,0,4,0,21,0,63,50,63,63,57,57,88,165,7,59,43,93,237,50,17,51,47,56,93,93,51,51,47,93,56, + 51,57,57,49,48,43,93,93,93,0,93,93,1,130,4,89,195,12,8,33,146,254,146,132,180,180,1,219,211,254,73,1,206,1,243,98,254,111,5,204,252,97,2,13,254,47,253,151,0,0,0,1,0,134,130,231,46, + 92,5,204,0,9,0,51,64,26,8,8,5,4,4,5,66,9,8,36,10,11,7,80,8,130,31,65,255,8,40,17,18,1,57,47,113,237,50,47,65,248,17,33,2,224,65,244,6,40,223,1,213,142,142,142,4,176,142,130,94,130, + 95,32,99,130,63,8,117,100,4,78,0,57,0,142,185,0,42,255,208,179,9,15,72,32,184,255,208,64,89,9,13,72,34,57,72,0,0,13,46,72,191,47,207,47,255,47,3,96,47,112,47,2,31,47,47,47,63,47,3, + 79,47,95,47,127,47,143,47,4,16,47,32,47,48,47,3,47,47,59,25,12,72,0,13,16,13,2,240,13,1,15,13,63,13,2,207,13,223,13,2,16,13,1,13,53,6,80,25,34,40,3,31,16,19,15,47,13,0,21,0,63,50,50, + 63,63,23,110,88,5,35,93,113,113,114,97,150,5,130,9,44,113,237,18,57,47,237,57,49,48,0,43,43,33,67,79,10,131,10,43,39,51,30,3,21,51,62,3,51,50,22,23,67,116,15,50,6,21,17,2,19,12,27, + 43,32,33,52,38,20,169,1,2,2,1,149,131,4,51,2,13,36,51,70,46,88,87,19,2,18,43,56,72,47,61,84,52,23,168,131,37,8,35,66,77,2,174,79,106,65,27,51,93,131,81,253,161,3,83,34,75,67,48,7,5, + 41,53,56,20,39,71,54,31,96,100,47,73,50,26,67,159,6,132,39,8,51,168,168,253,141,0,0,1,0,179,0,0,4,25,4,78,0,37,0,61,64,38,112,34,128,34,2,4,33,20,33,116,33,132,33,4,37,70,0,0,39,25, + 12,70,0,13,128,13,2,13,25,6,80,131,236,133,235,34,63,63,237,68,58,5,68,57,8,153,218,37,30,2,21,17,3,100,68,47,8,131,196,46,170,1,2,3,2,3,24,65,85,106,66,83,128,87,45,68,49,11,135,185, + 35,44,57,59,20,68,83,9,32,0,130,0,38,2,0,130,255,236,4,73,130,171,8,67,15,0,35,0,89,64,61,100,34,1,70,34,86,34,2,100,28,1,70,28,86,28,2,107,24,1,73,24,89,24,2,107,18,1,73,18,89,18, + 2,0,71,175,16,1,0,16,16,16,96,16,3,16,16,37,26,71,0,10,1,10,21,80,13,16,31,80,5,22,78,158,7,32,237,86,237,5,106,180,10,84,76,8,33,16,18,75,11,19,8,116,73,67,126,181,114,109,177,125, + 68,253,232,244,238,189,43,77,106,64,65,110,79,45,47,79,105,57,65,111,80,45,2,30,142,211,140,69,68,140,211,143,1,23,1,25,254,234,254,230,126,164,98,39,41,99,164,123,126,165,98,40,39, + 98,166,0,0,2,0,179,254,87,4,66,4,80,0,40,0,60,0,101,64,68,100,59,1,70,59,86,59,2,107,43,1,73,43,89,43,2,6,38,22,38,118,38,134,38,4,52,33,1,59,8,1,121,3,137,3,130,203,59,96,41,1,41, + 41,62,51,30,17,70,0,18,128,18,2,18,46,80,30,36,16,24,15,17,27,56,80,10,131,206,75,237,26,86,158,7,37,46,2,39,35,30,3,69,214,6,66,123,9,33,30,2,110,220,16,43,66,40,92,150,110,55,100, + 86,67,23,5,1,130,0,8,89,181,1,2,2,1,175,1,3,3,3,4,25,63,80,100,62,110,150,92,40,186,24,59,97,73,59,104,77,45,37,72,106,68,73,98,59,25,2,34,123,208,150,85,20,44,70,50,2,31,46,58,29, + 254,89,5,3,40,74,59,42,9,3,37,52,60,26,52,76,49,23,80,147,206,125,100,156,107,56,37,97,172,135,115,157,97,42,57,110,162,0,130,0,8,91,2,0,138,254,87,4,26,4,74,0,36,0,56,0,102,64,71, + 75,45,91,45,107,45,3,68,49,84,49,100,49,3,58,28,1,53,3,1,5,36,21,36,117,36,133,36,4,10,32,26,32,122,32,138,32,4,18,70,37,5,80,19,1,144,19,160,19,2,19,19,58,47,71,0,34,1,34,52,80,25, + 31,22,18,27,42,80,5,0,16,0,63,50,237,76,255,7,35,18,57,47,93,71,230,12,35,1,50,30,2,71,205,10,32,35,93,109,5,34,35,14,3,91,144,5,122,252,9,100,18,5,50,2,17,66,102,79,61,24,2,3,3,5, + 1,175,1,2,2,1,181,1,130,0,8,59,2,25,63,81,101,65,202,190,195,2,18,40,74,105,65,68,97,61,29,28,60,96,69,59,104,78,45,4,74,22,46,69,47,25,57,49,35,3,9,40,68,98,67,251,54,1,183,26,58, + 26,30,28,51,78,53,26,1,25,1,21,130,3,8,78,29,253,224,119,158,94,40,51,105,161,110,106,159,106,54,39,99,171,0,1,0,242,0,0,4,21,4,78,0,31,0,70,64,43,120,6,136,6,2,4,26,20,26,2,4,25,20, + 25,2,31,31,33,22,9,70,10,16,16,31,10,111,10,127,10,143,10,4,10,22,3,82,28,16,16,15,9,21,0,63,67,147,6,38,51,124,47,24,16,237,50,130,241,66,211,5,32,1,112,198,6,71,204,5,35,39,51,30, + 3,68,106,5,8,59,22,23,4,21,47,103,62,84,134,92,49,180,12,16,18,6,171,7,15,14,10,2,5,28,62,85,116,82,56,108,42,3,150,8,11,68,117,156,88,254,4,2,189,56,116,103,82,24,24,62,67,68,29,62, + 100,70,38,10,7,0,130,167,46,167,255,236,4,36,4,75,0,56,0,133,64,88,108,24,93,68,5,8,80,108,23,1,106,54,1,115,49,131,49,2,71,49,87,49,2,36,55,1,42,29,1,10,3,26,3,2,5,32,21,32,2,40,73, + 39,39,0,72,21,21,31,58,11,73,10,10,47,72,0,31,96,31,112,31,128,31,4,31,47,21,5,45,80,34,32,40,144,40,2,40,40,34,16,16,80,5,11,11,5,22,0,97,21,5,36,51,47,93,16,237,89,138,6,35,51,47, + 237,17,130,219,75,150,13,32,93,66,225,10,32,55,71,139,6,32,52,91,185,6,32,54,77,177,6,34,3,35,34,89,126,5,8,141,3,4,36,59,112,163,105,94,151,114,77,18,159,12,53,75,98,57,58,99,72,41, + 51,88,121,71,64,126,99,61,212,201,79,138,108,73,14,162,7,46,67,84,46,245,43,78,108,65,65,137,113,73,1,53,76,122,85,46,27,62,103,76,31,55,65,34,11,17,41,65,48,50,61,42,30,19,17,40,65, + 100,77,147,166,29,63,99,69,20,45,56,33,12,151,46,57,38,27,17,16,42,70,109,0,0,1,0,190,255,240,3,249,5,84,0,27,0,88,64,54,106,24,122,24,138,24,3,24,40,9,14,72,6,6,18,18,29,5,9,70,26, + 144,1,130,0,8,42,2,26,64,18,37,72,26,64,11,14,72,0,26,1,26,12,80,23,22,8,0,80,1,3,3,5,1,15,0,63,51,51,47,16,237,50,63,237,1,47,93,43,43,75,167,6,60,18,57,47,51,47,49,48,0,43,93,19, + 53,51,19,51,17,33,21,33,17,20,22,51,50,62,2,55,21,14,130,232,8,50,38,53,17,190,170,58,120,1,176,254,80,85,97,40,85,79,69,24,22,70,87,102,54,162,162,3,172,142,1,26,254,230,142,253,116, + 79,76,7,10,12,5,137,6,16,14,10,137,148,2,159,0,131,175,8,55,185,255,236,4,31,4,58,0,37,0,69,64,44,127,34,143,34,2,123,33,139,33,2,10,33,26,33,42,33,3,57,28,1,25,14,70,11,11,39,1,70, + 0,36,128,36,2,36,25,6,80,31,22,20,21,12,0,130,165,32,50,66,125,6,32,237,130,154,33,237,51,66,121,5,34,93,1,17,89,140,9,78,75,8,32,35,131,160,46,46,2,53,17,1,109,23,53,85,63,64,103, + 73,39,181,70,19,9,56,26,62,82,106,70,90,130,84,39,4,58,253,82,79,106,65,27,45,85,125,81,2,115,252,173,70,207,5,70,21,9,35,145,100,2,209,131,179,36,69,0,0,4,135,130,179,8,35,16,0,148, + 64,66,86,4,1,73,14,89,14,2,27,15,91,15,2,9,15,1,37,3,85,3,2,6,3,22,3,2,40,1,104,1,2,89,179,5,32,75,89,179,11,33,75,2,89,179,7,57,112,16,128,16,2,16,184,255,192,64,30,10,14,72,16,16, + 18,2,64,11,14,72,2,15,2,15,89,190,5,40,42,9,58,9,2,9,0,1,21,96,174,8,86,236,6,89,197,27,32,93,89,197,5,59,3,23,62,3,55,1,51,2,203,213,254,79,202,1,12,8,18,20,17,6,6,19,20,21,8,1,20, + 201,130,232,48,64,22,63,68,63,21,21,63,66,63,22,2,194,0,1,0,21,130,211,32,184,130,211,8,50,42,1,82,64,16,116,30,132,30,2,70,30,86,30,2,36,30,52,30,2,29,184,255,224,64,74,19,24,72,144, + 29,1,101,29,117,29,133,29,3,36,29,52,29,2,6,29,22,29,2,28,32,130,24,58,159,28,1,107,28,123,28,139,28,3,29,28,45,28,61,28,3,11,28,1,123,27,139,27,2,27,32,130,208,47,14,32,13,17,72,26, + 14,42,14,58,14,3,11,14,1,13,131,78,32,31,130,53,44,144,13,1,37,13,53,13,117,13,133,13,4,1,132,70,41,1,1,42,1,58,1,122,1,3,0,131,35,32,112,130,54,8,111,21,0,37,0,53,0,3,3,6,0,1,14,1, + 82,16,22,16,75,15,14,20,15,15,14,13,1,82,28,22,28,76,7,13,20,7,13,1,1,82,29,35,29,76,7,1,20,7,1,29,7,0,1,82,41,35,41,75,42,0,20,42,0,35,7,22,3,15,20,42,116,42,132,42,3,42,42,44,15, + 42,102,7,1,50,7,1,2,7,28,28,22,41,42,16,3,15,15,35,22,117,22,133,22,2,22,13,1,0,14,21,0,63,51,130,0,60,93,17,51,63,23,51,18,57,47,51,95,93,93,47,1,47,17,51,47,93,18,23,57,135,43,135, + 43,196,16,89,179,8,33,135,24,91,66,14,35,95,93,43,93,135,2,130,9,32,93,132,12,133,4,43,33,35,3,46,1,39,38,39,6,7,14,1,89,197,6,40,3,21,54,55,62,1,55,19,51,99,87,5,33,52,62,89,195,5, + 38,204,143,8,20,9,11,9,130,2,8,133,20,10,148,203,183,178,93,3,11,11,8,11,11,9,20,7,135,193,130,6,19,9,11,10,8,12,12,3,101,176,1,215,27,69,32,37,37,37,36,31,71,31,254,45,4,58,253,161, + 24,85,96,92,29,45,41,35,73,20,1,168,254,87,20,71,35,41,46,30,90,95,86,25,2,95,0,0,0,1,0,94,0,0,4,109,4,58,0,11,0,185,64,112,71,4,1,118,7,134,7,2,57,1,121,1,137,1,3,118,8,1,25,8,89, + 8,2,10,2,9,7,3,8,6,11,6,1,2,9,4,3,8,5,0,5,10,130,9,54,2,7,4,3,2,9,1,82,8,3,2,3,8,75,9,2,20,9,9,2,0,1,82,130,38,38,75,5,0,20,5,0,6,130,37,35,7,4,4,3,130,181,8,38,16,11,112,11,128,11, + 3,11,11,13,5,5,3,7,10,4,1,4,8,5,15,3,0,21,0,63,50,63,51,23,57,1,47,51,47,17,51,47,93,130,5,91,20,15,89,163,34,40,1,93,33,9,1,35,9,1,51,132,2,8,36,3,164,254,191,254,189,194,1,159,254, + 115,199,1,44,1,42,201,254,115,1,164,1,188,254,68,2,44,2,14,254,91,1,165,253,244,253,210,130,247,36,66,254,87,4,137,130,247,8,129,31,0,175,64,82,30,24,15,18,72,101,18,117,18,133,18, + 3,73,29,89,29,2,3,32,13,17,72,45,3,61,3,2,43,2,59,2,2,0,72,12,73,117,0,133,0,2,38,0,102,0,2,106,16,122,16,138,16,3,73,16,89,16,2,0,16,24,105,24,121,24,137,24,3,24,16,9,12,72,24,31, + 8,8,17,30,31,16,31,184,255,192,183,10,17,72,31,31,33,18,17,184,255,240,64,10,17,64,11,15,72,17,130,24,1,24,184,255,224,64,13,13,16,72,24,16,16,30,17,15,12,80,77,216,5,44,51,57,47,51, + 43,93,1,47,43,56,51,17,51,131,5,42,18,57,47,18,57,43,93,17,51,51,93,130,0,39,49,48,0,43,93,93,43,1,130,3,8,88,33,14,3,35,34,38,39,53,30,1,51,50,54,63,1,1,51,1,30,3,23,62,3,55,19,51, + 2,193,37,80,98,120,78,34,58,32,19,48,17,79,139,48,18,254,42,192,1,8,11,32,30,23,2,3,23,30,31,12,252,190,98,157,111,59,4,7,135,3,3,118,129,49,4,47,253,126,27,79,77,62,9,11,61,75,79, + 29,2,129,0,130,0,8,68,1,0,147,0,0,4,44,4,58,0,9,0,88,64,55,6,7,22,7,38,7,86,7,4,7,1,82,2,1,2,75,6,7,20,6,7,2,6,96,6,1,3,6,8,8,6,3,3,11,159,1,175,1,191,1,3,0,1,1,1,6,3,80,4,15,1,7,80, + 0,79,44,6,36,50,1,47,93,93,89,223,5,32,93,65,198,5,32,135,89,222,5,33,51,53,89,217,6,8,41,21,147,2,169,253,127,3,76,253,86,2,207,137,3,38,139,137,252,218,139,0,0,1,0,227,254,87,4,37, + 5,204,0,49,0,117,64,18,118,15,134,15,2,122,208,6,42,2,24,9,12,72,20,184,255,224,64,55,130,8,8,53,24,128,49,1,49,49,51,32,44,241,5,37,5,63,12,1,12,12,17,127,5,1,0,5,1,5,37,11,245,15, + 12,79,12,111,12,3,143,12,175,12,2,12,12,48,25,245,24,0,48,245,0,27,0,63,83,85,5,35,93,113,237,57,131,159,37,51,51,47,93,18,57,72,163,5,53,93,51,49,48,0,43,43,93,93,1,34,46,2,53,17, + 52,46,2,39,53,62,3,130,8,49,62,2,51,33,21,35,34,14,2,21,17,20,14,2,7,21,30,3,130,8,8,95,22,59,1,21,3,28,65,108,78,43,47,78,99,51,51,99,78,47,43,78,108,65,1,9,213,45,65,42,19,41,66, + 83,41,43,83,65,40,80,91,213,254,87,44,80,111,68,1,95,63,87,54,26,2,137,2,26,55,86,62,1,96,70,112,79,42,139,21,46,74,54,254,166,57,92,69,44,10,2,10,45,68,93,58,254,165,106,89,139,0, + 1,2,19,254,87,2,185,130,255,41,3,0,23,64,10,3,171,0,0,4,130,214,131,187,63,63,17,18,1,57,47,237,49,48,1,17,51,17,2,19,166,254,87,7,117,248,139,0,0,0,1,0,167,254,87,3,233,130,51,59, + 49,0,115,64,13,118,34,134,34,2,105,41,121,41,137,41,3,28,184,255,224,64,61,9,13,72,47,24,130,4,52,11,48,37,1,37,37,32,44,241,17,5,5,51,25,79,0,127,0,143,0,3,131,63,54,11,38,245,15, + 37,47,37,79,37,111,37,4,143,37,175,37,2,37,37,1,24,245,25,90,139,9,34,18,57,47,65,52,8,130,11,37,51,237,50,50,47,93,78,74,5,47,93,93,19,53,51,50,54,53,17,52,62,2,55,53,46,3,130,8,49, + 46,2,43,1,53,33,50,30,2,21,17,20,30,2,23,21,14,3,130,8,53,14,2,35,167,213,91,81,40,65,82,43,41,82,66,41,20,42,65,45,213,1,9,65,66,15,8,47,254,87,139,89,106,1,91,58,93,68,45,10,2,10, + 44,69,92,57,1,90,54,74,46,21,139,42,79,112,70,254,160,62,86,55,26,2,137,2,26,54,87,63,254,161,68,111,80,44,130,254,130,255,8,32,108,2,41,4,96,3,39,0,32,0,93,64,26,22,4,38,4,54,4,102, + 4,150,4,166,4,6,16,48,9,20,0,76,17,132,5,37,30,184,255,192,64,37,131,15,32,31,132,15,8,50,13,34,16,29,1,29,24,173,13,0,64,23,28,72,0,64,9,12,72,0,29,10,173,15,19,1,19,0,47,93,237,51, + 221,43,43,50,237,1,47,93,16,206,49,48,43,0,43,43,43,93,1,130,211,34,23,30,3,130,238,72,222,6,8,68,39,38,35,34,14,2,7,53,62,1,1,115,41,79,77,75,37,21,50,51,51,23,69,123,52,31,60,61, + 68,40,69,145,73,129,88,38,65,60,56,29,50,132,3,39,13,20,26,13,7,16,14,8,50,42,149,22,31,19,8,44,26,45,12,23,32,21,143,38,46,131,195,51,2,2,3,254,246,2,201,4,58,0,3,0,7,0,44,64,21,2, + 4,152,130,9,52,16,7,2,7,7,8,9,0,0,3,6,157,7,15,3,184,1,14,0,63,63,24,77,210,15,8,106,51,19,35,19,21,35,53,2,27,148,24,196,198,194,2,173,252,73,5,68,201,201,0,0,1,0,133,255,195,4,70, + 5,99,0,41,0,147,64,96,104,16,136,16,2,68,40,84,40,2,74,38,90,38,2,27,12,73,24,13,13,19,6,32,71,33,33,6,70,0,5,16,5,2,5,5,43,39,70,0,19,16,19,224,19,240,19,4,19,36,80,24,31,33,127,33, + 143,33,3,33,33,27,24,11,14,32,5,1,131,38,46,96,5,112,5,128,5,192,5,6,5,5,0,80,14,24,130,1,47,12,15,25,1,25,12,0,47,47,93,18,57,57,47,47,16,91,21,5,33,51,17,74,36,5,111,255,8,75,49, + 6,41,51,237,50,49,48,0,93,93,93,1,112,226,6,32,7,130,188,8,189,46,3,53,52,62,2,55,53,51,21,30,3,23,7,46,1,35,34,6,21,20,22,2,123,56,93,72,47,9,182,9,58,99,140,90,128,110,164,109,54, + 58,112,163,104,128,86,133,97,61,13,185,17,131,122,156,161,168,1,13,28,54,81,54,12,62,116,92,62,8,195,195,8,83,137,184,110,118,191,138,83,9,184,184,7,55,86,111,63,14,91,108,201,189, + 198,201,0,1,0,56,0,0,4,147,5,90,0,54,0,156,64,98,3,24,12,15,72,6,20,1,27,111,34,41,45,111,16,79,34,95,34,2,127,12,143,12,2,15,42,1,12,42,34,34,42,12,3,6,0,111,0,54,1,54,54,56,50,110, + 6,15,15,6,64,23,36,72,0,6,16,6,2,6,44,14,115,41,31,15,1,63,15,111,15,159,15,207,15,223,15,239,15,130,32,47,50,37,115,22,34,34,22,7,7,50,115,6,54,54,6,24,76,49,5,36,50,63,51,47,16,79, + 217,5,130,246,38,1,124,47,93,43,51,24,132,17,36,125,47,93,24,237,69,32,6,33,93,93,130,28,103,252,5,45,43,37,14,3,35,33,53,62,3,61,1,35,53,51,86,251,8,36,50,14,2,7,6,65,22,5,38,29,1, + 33,21,33,21,20,130,16,9,22,33,50,54,55,4,147,9,32,54,81,58,252,143,43,72,51,29,186,186,51,104,159,108,74,126,101,72,18,1,14,24,31,17,39,50,22,117,78,122,120,1,152,254,104,15,36,60, + 44,2,88,59,57,11,240,43,86,68,43,154,22,48,70,102,75,141,142,220,92,147,102,55,29,58,86,57,6,8,10,5,12,16,63,64,115,125,216,142,139,59,96,77,60,23,53,53,0,2,0,158,0,225,4,47,4,115, + 0,35,0,55,0,234,64,157,73,34,89,34,2,45,34,61,34,2,73,29,89,29,2,45,29,61,29,2,73,25,89,25,2,45,25,61,25,2,70,20,86,20,2,34,20,50,20,2,70,16,86,16,2,34,16,50,16,2,70,11,86,11,2,34, + 11,50,11,2,70,7,86,7,2,34,7,50,7,2,73,2,89,2,2,45,2,61,2,2,3,33,6,30,12,24,15,21,21,24,30,33,4,0,14,22,22,18,170,111,46,1,16,46,1,46,46,0,57,4,32,32,36,170,0,0,16,0,128,0,3,0,12,6, + 15,3,21,33,24,30,30,33,3,6,4,9,27,23,31,31,41,176,64,27,96,27,2,63,27,1,27,13,5,5,51,176,9,0,47,237,51,47,51,47,93,93,131,6,37,17,18,23,57,17,51,133,1,33,1,47,134,18,32,57,131,28,35, + 50,47,51,17,137,27,37,49,48,93,93,0,93,130,0,32,1,97,27,5,133,9,8,36,19,52,54,55,39,55,23,62,1,51,50,22,23,55,23,7,30,1,21,20,6,7,23,7,39,14,1,35,34,38,39,7,39,55,46,1,55,90,137,14, + 8,80,182,41,37,100,104,99,54,127,73,71,126,54,97,104,96,37,43,44,38,100,102,101,54,126,71,72,128,52,105,102,102,37,41,154,45,76,102,58,57,102,77,44,44,77,102,57,58,102,76,45,2,172, + 71,127,54,100,103,101,39,43,42,38,97,105,96,54,127,71,71,128,53,100,105,101,37,41,42,38,105,105,102,130,74,130,37,33,44,44,130,53,32,58,133,53,32,0,130,0,8,86,1,0,52,0,0,4,152,5,69, + 0,22,0,179,64,111,93,21,1,75,21,1,25,21,41,21,57,21,3,82,19,1,3,64,19,1,20,19,36,19,52,19,3,0,0,64,0,80,0,3,15,17,79,17,95,17,3,12,4,1,6,6,22,8,16,11,11,18,9,21,22,14,8,18,9,14,19, + 47,18,1,18,20,4,0,8,92,17,13,112,149,7,53,4,16,9,1,9,9,23,24,7,11,96,12,4,12,3,15,96,16,20,0,16,12,130,1,53,8,21,18,3,8,18,0,63,63,51,18,57,57,47,47,17,51,51,16,237,50,17,132,4,33, + 18,1,98,120,5,50,237,50,50,57,196,93,50,43,1,24,16,77,228,50,17,18,57,47,51,132,4,36,49,48,95,94,93,130,0,33,95,93,130,0,34,1,33,21,132,1,89,219,5,56,55,33,53,33,1,51,9,1,51,2,252, + 1,65,254,129,1,127,254,129,178,254,131,1,125,2,130,11,8,63,64,254,101,199,1,105,1,109,199,2,167,142,137,144,255,0,1,0,144,137,142,2,158,253,151,2,105,0,0,0,2,2,19,254,57,2,185,5,174, + 0,3,0,7,0,37,64,17,3,7,171,0,4,4,8,9,5,0,5,0,1,4,27,1,0,130,171,133,170,32,18,130,159,34,51,237,50,71,55,5,32,3,71,59,5,45,166,166,2,164,3,10,252,246,251,149,3,11,252,245,131,79,8, + 197,0,159,255,84,4,44,5,204,0,77,0,97,1,5,64,115,115,16,131,16,2,105,86,1,117,96,133,96,2,67,95,83,95,2,67,91,83,91,2,67,90,83,90,115,90,131,90,4,67,62,83,62,2,106,21,122,21,138,21, + 3,42,20,74,20,90,20,3,42,81,74,81,90,81,106,81,4,42,80,1,80,24,13,17,72,42,72,1,108,57,124,57,140,57,3,42,57,1,5,31,21,31,2,10,36,26,36,2,5,77,21,77,2,93,29,34,88,73,65,70,83,78,14, + 73,75,6,73,5,55,73,34,75,184,255,192,64,64,18,25,72,65,75,5,34,34,5,75,65,4,44,24,73,0,78,16,78,2,78,78,99,45,73,0,44,1,44,83,19,70,93,60,29,118,70,134,70,2,106,70,1,70,29,0,50,81, + 39,32,45,128,45,2,45,45,39,11,81,0,6,6,130,201,37,63,50,47,16,237,47,80,169,7,36,93,93,17,51,51,130,2,69,132,8,35,17,23,57,47,130,0,34,43,16,237,131,1,39,18,57,57,16,237,17,57,57,69, + 144,5,76,157,7,130,7,131,2,33,1,50,106,48,7,32,6,80,176,6,38,21,20,14,2,7,30,3,131,6,32,35,90,157,5,34,3,51,50,93,141,5,32,39,69,193,6,131,6,38,54,1,52,46,2,39,14,130,39,130,64,8,142, + 62,3,2,120,79,138,108,73,15,161,8,46,67,85,46,125,129,50,83,109,59,68,139,112,70,28,55,82,54,49,79,55,30,58,112,164,107,93,154,117,79,19,161,12,55,78,99,57,58,102,77,44,57,95,123,65, + 65,131,104,65,32,59,84,53,42,75,57,33,211,1,225,53,88,113,61,58,89,60,31,49,82,108,60,54,93,68,39,5,204,30,63,98,69,20,45,61,37,17,89,71,45,64,46,32,14,16,46,73,106,76,45,87,73,55, + 13,21,51,64,81,51,73,117,83,44,27,64,104,77,31,55,71,41,16,21,45,68,47,54,73,51,35,16,15,130,36,8,90,73,45,83,71,53,15,14,46,64,80,48,139,155,252,203,48,69,51,35,14,3,33,52,69,39,46, + 66,48,34,15,1,24,47,70,0,0,2,1,79,4,195,3,124,5,123,0,3,0,7,0,49,64,31,3,133,0,0,7,133,16,4,1,4,5,1,144,4,15,0,63,0,95,0,175,0,191,0,5,0,64,22,30,72,0,0,47,43,93,50,237,50,68,107,5, + 39,237,49,48,1,53,51,21,33,130,3,40,2,217,163,253,211,165,4,195,184,130,0,32,0,130,0,8,142,3,0,31,0,38,4,173,5,204,0,41,0,61,0,81,1,28,64,119,123,80,139,80,2,116,74,132,74,2,118,79, + 134,79,2,118,75,134,75,2,116,70,132,70,2,123,64,139,64,2,121,69,137,69,2,121,65,137,65,2,3,80,19,80,2,3,74,19,74,2,12,18,28,18,44,18,3,3,24,19,24,35,24,3,102,0,118,0,134,0,3,0,196, + 21,10,32,198,31,11,31,112,31,128,31,2,15,31,31,31,2,47,21,63,21,2,21,64,16,20,72,21,31,21,31,52,62,16,13,17,72,62,195,16,42,1,42,42,83,72,184,255,240,64,87,130,16,46,72,195,52,48,10, + 64,10,176,10,192,10,208,10,5,48,132,8,8,37,3,10,10,5,201,16,37,201,26,32,32,26,127,16,143,16,239,16,255,16,4,80,26,176,26,192,26,3,0,26,16,26,112,26,128,26,4,16,130,8,58,77,105,67, + 121,67,137,67,3,67,200,57,0,102,77,118,77,134,77,3,77,200,47,208,0,63,237,93,130,2,44,18,57,57,47,47,93,113,93,17,51,47,16,237,72,21,5,35,1,47,237,43,130,14,32,93,130,5,131,28,34,43, + 93,93,130,29,36,16,237,50,16,237,99,45,6,69,129,5,32,0,94,225,28,32,23,121,5,6,33,5,20,24,75,30,33,8,167,1,142,28,56,84,56,41,63,47,33,12,115,19,52,74,99,67,90,133,87,43,43,86,130, + 87,67,99,72,49,16,114,10,32,46,60,39,58,83,53,25,3,31,91,158,213,121,120,212,159,92,92,158,212,121,121,213,158,91,92,77,134,178,102,101,178,132,77,77,132,178,101,102,178,134,77,2,251, + 72,120,86,48,27,45,56,28,35,43,81,63,38,64,114,158,94,98,156,111,59,36,59,75,39,33,26,50,38,23,46,83,118,74,187,254,239,178,85,85,178,1,17,187,187,1,17,177,86,86,177,254,239,187,167, + 238,152,71,71,152,238,167,168,239,152,70,70,152,239,0,2,0,206,2,139,3,253,5,152,0,42,0,59,0,122,185,0,3,255,232,179,76,219,5,45,216,64,55,9,13,72,34,40,9,17,72,41,24,13,130,4,32,40, + 130,23,53,30,30,23,226,36,6,0,53,1,53,53,0,61,13,227,14,14,43,227,0,0,16,130,70,8,36,26,48,228,40,54,228,5,5,19,36,33,40,184,1,22,180,13,13,10,228,19,184,1,21,0,63,237,51,47,63,51, + 51,18,57,47,237,16,66,148,8,32,17,130,14,41,93,51,51,237,50,47,49,48,0,43,131,0,33,19,52,100,130,5,24,73,81,9,36,22,21,17,20,22,74,222,5,38,1,35,34,38,39,35,14,131,6,70,235,7,8,105, + 61,1,7,14,3,206,58,99,130,71,191,94,86,84,92,11,149,8,48,84,121,79,153,166,31,43,12,28,9,23,56,23,82,75,3,4,43,146,102,121,137,147,18,38,57,39,59,92,65,34,154,50,90,68,40,3,102,69, + 92,56,24,1,4,43,93,89,68,77,10,47,82,62,35,134,133,254,221,58,60,5,3,94,6,7,85,75,77,89,114,125,28,50,38,22,45,68,81,37,51,4,1,13,30,53,0,0,130,220,8,69,127,0,141,4,96,3,172,0,8,0, + 17,0,80,64,43,0,235,8,3,235,4,236,6,235,1,1,19,10,9,235,17,12,235,13,236,15,235,0,10,1,10,11,2,10,1,12,9,0,1,2,15,3,6,6,3,239,0,238,0,63,228,57,61,47,23,51,17,51,50,17,130,4,44,1,24, + 47,93,237,253,237,212,237,17,18,57,47,132,8,43,49,48,37,1,53,1,51,21,9,1,21,33,135,8,38,3,172,254,174,1,82,178,130,4,55,84,253,111,254,176,1,80,177,254,177,1,81,141,1,109,63,1,115, + 31,254,140,254,145,29,138,10,59,0,0,1,0,116,0,180,4,87,2,242,0,5,0,33,64,18,0,170,5,7,0,2,16,2,2,2,63,130,26,46,2,173,3,179,0,63,253,198,93,1,47,93,16,222,237,130,110,48,17,33,53,33, + 17,3,198,252,174,3,227,180,1,172,146,253,194,130,226,33,0,4,68,11,8,8,114,19,0,39,0,53,0,62,0,236,64,41,123,38,139,38,2,116,32,132,32,2,118,37,134,37,2,118,33,134,33,2,116,28,132,28, + 2,123,22,139,22,2,121,27,137,27,2,121,23,137,23,2,47,184,255,216,64,21,11,17,72,51,40,9,12,72,3,38,19,38,2,3,32,19,32,2,52,41,41,184,255,240,64,38,41,49,59,43,196,44,40,53,16,53,53, + 49,196,54,112,54,128,54,2,44,54,44,54,10,20,16,13,17,72,20,195,16,130,166,34,0,64,30,131,42,32,50,130,16,8,45,30,195,10,40,44,52,42,200,59,58,200,45,0,45,16,45,2,44,59,45,45,59,44, + 3,35,105,25,121,25,137,25,3,25,200,15,0,102,35,118,35,134,35,3,35,200,5,67,223,8,35,23,57,47,47,130,226,37,237,16,237,50,17,51,67,218,14,32,93,130,20,34,47,56,51,130,5,35,17,57,56, + 17,85,137,5,37,43,43,93,93,1,93,93,171,5,32,93,116,134,16,24,78,220,18,46,5,3,35,17,35,17,33,50,22,21,20,6,7,19,3,115,229,6,34,54,4,173,67,172,32,52,254,222,199,161,127,1,51,142,151, + 104,85,221,159,95,81,170,182,80,84,2,249,67,157,34,8,60,250,1,80,254,176,3,63,126,111,102,123,19,254,162,2,80,69,72,254,211,85,0,255,255,255,251,5,242,4,209,6,82,18,7,0,66,0,0,6,206, + 0,0,0,2,1,71,3,32,3,133,5,90,0,19,0,39,0,74,185,0,17,67,199,5,42,13,184,255,232,64,38,9,12,72,7,24,130,4,32,3,131,4,63,20,172,0,0,30,172,0,10,16,10,128,10,3,10,35,175,0,5,1,5,5,25, + 175,15,4,0,63,237,51,47,93,237,70,55,8,33,0,43,130,0,91,248,8,99,209,6,91,21,15,8,58,3,133,45,78,105,59,59,104,78,46,46,78,104,59,59,105,78,45,109,27,48,65,38,37,65,48,28,28,48,65, + 37,38,65,48,27,4,61,59,104,77,45,45,77,104,59,60,104,77,44,44,77,104,60,38,65,49,28,28,49,131,37,36,49,29,29,49,65,131,191,50,0,116,0,0,4,87,4,195,0,11,0,15,0,106,64,55,15,12,4,24, + 82,136,9,32,6,130,223,8,63,10,3,2,2,16,17,13,173,64,12,2,14,4,7,5,14,47,7,63,7,143,7,3,143,7,159,7,223,7,239,7,4,7,0,4,173,9,5,184,255,192,179,19,22,72,5,0,47,43,51,237,50,198,93,113, + 43,0,24,16,77,230,47,26,237,24,82,164,8,130,22,33,43,1,131,20,37,17,51,50,49,48,1,73,253,5,41,17,51,17,33,21,1,53,33,21,2,24,82,170,8,8,85,252,29,3,227,2,168,254,117,1,139,145,1,138, + 254,118,145,253,88,145,145,0,1,1,56,2,51,3,148,5,141,0,32,0,83,181,90,2,106,2,2,21,184,255,216,64,40,9,14,72,31,31,23,225,111,8,143,8,2,8,8,0,34,14,224,15,15,0,30,30,0,0,16,0,2,0,14, + 14,11,228,20,222,1,30,228,0,221,97,191,5,39,51,47,1,47,93,51,47,17,78,134,6,32,93,69,8,6,38,1,93,1,39,62,5,53,24,78,86,10,34,22,21,20,130,237,8,62,33,23,1,58,2,27,88,100,103,82,52, + 71,74,68,88,8,133,6,46,76,108,67,131,145,54,85,103,99,83,22,1,203,2,2,51,103,61,95,80,71,73,82,50,62,75,73,68,8,51,87,64,37,127,112,61,98,82,73,73,78,47,107,0,130,0,38,1,1,50,2,39, + 3,153,130,183,8,46,53,0,123,185,0,42,255,232,64,32,9,15,72,2,40,9,12,72,48,45,22,34,225,35,35,8,45,225,28,79,22,95,22,2,22,28,22,28,8,0,225,15,184,255,192,64,34,130,31,39,15,15,55, + 9,225,0,8,16,130,216,51,48,21,228,22,22,12,40,34,34,31,228,40,222,12,228,3,9,9,3,223,78,93,5,24,79,93,12,36,237,18,57,47,43,130,4,37,57,47,47,93,16,237,134,229,39,49,48,0,43,43,1,20, + 6,73,173,6,33,1,51,24,79,75,28,8,96,6,7,21,30,3,3,153,155,142,87,116,73,36,6,136,9,88,85,77,83,37,56,67,29,61,57,29,61,50,32,74,71,68,84,6,135,7,50,79,103,58,69,104,70,35,86,90,52, + 75,48,22,3,27,116,128,39,64,81,43,13,67,69,72,76,45,54,29,9,109,13,31,53,40,60,69,70,65,12,58,86,57,28,33,59,79,46,75,110,20,2,5,37,54,68,0,1,1,107,243,12,44,123,2,139,2,2,128,3,1, + 3,0,2,140,5,107,243,11,8,82,53,55,51,21,5,1,146,227,197,254,212,4,177,20,239,29,230,0,0,0,1,0,144,254,119,4,60,4,58,0,39,0,104,64,24,11,16,15,18,72,57,7,1,34,70,5,16,31,32,31,2,96, + 31,192,31,208,31,3,31,184,255,192,64,42,36,83,72,31,31,41,21,13,17,70,18,64,39,83,72,18,64,28,35,130,4,57,18,21,72,0,18,1,18,32,19,15,18,27,26,80,12,5,9,22,0,21,0,63,63,51,51,237,130, + 4,46,1,47,93,43,43,43,237,50,50,18,57,47,43,93,113,104,142,5,36,43,33,46,3,53,71,1,5,34,39,35,30,122,240,5,90,72,15,8,65,3,148,1,3,3,3,4,53,167,114,82,128,32,4,3,1,182,182,27,63,101, + 73,67,112,82,46,181,1,2,2,1,6,49,62,63,19,109,110,64,58,32,63,27,254,139,5,195,253,124,69,116,85,47,52,91,126,75,2,105,252,175,34,76,67,49,7,131,219,8,52,127,254,188,4,56,5,69,0,19, + 0,65,64,24,5,154,0,6,1,6,6,12,2,19,19,1,154,2,2,21,0,12,16,12,2,12,4,19,184,1,16,183,18,7,7,1,18,3,6,1,0,47,51,63,130,170,71,220,5,130,8,35,237,50,47,17,130,6,33,93,237,67,92,5,8,63, + 35,17,35,17,34,46,2,53,52,62,2,51,33,21,3,183,132,197,133,80,133,96,53,50,95,138,87,2,71,4,203,249,241,6,15,249,241,3,190,45,88,134,88,84,133,93,50,122,0,255,255,1,240,1,164,2,219, + 2,207,18,6,0,211,0,131,147,36,119,254,68,1,227,130,9,8,36,27,0,82,185,0,26,255,232,64,47,15,19,72,68,26,84,26,2,3,24,13,19,72,24,25,25,23,22,64,9,12,72,22,22,8,16,135,130,39,41,8,1, + 8,19,146,25,64,13,16,72,130,25,50,11,141,5,149,0,63,237,47,57,47,43,237,1,47,93,51,47,237,17,130,10,41,51,51,17,51,49,48,0,43,93,43,68,225,5,35,38,39,53,22,76,63,5,8,88,38,35,42,1, + 7,55,51,7,30,1,1,227,30,65,104,75,20,45,25,50,36,41,57,34,15,61,72,14,29,14,65,107,39,94,94,254,254,42,69,49,26,1,3,98,6,13,22,32,19,40,42,2,182,100,3,83,0,1,1,29,2,51,3,155,5,130, + 0,18,0,63,64,34,63,17,1,17,17,16,224,2,8,8,1,1,14,0,2,16,2,130,0,42,19,20,8,228,9,9,3,14,220,16,1,68,11,7,32,51,131,144,34,18,1,57,130,153,34,51,47,51,24,84,141,7,32,1,24,84,141,12, + 8,34,51,21,1,35,1,9,13,55,74,86,43,48,87,73,56,17,121,236,2,51,107,2,98,26,42,30,17,109,18,35,50,33,253,28,107,130,245,8,73,2,0,226,2,139,3,234,5,152,0,19,0,33,0,82,64,48,122,25,138, + 25,2,122,21,138,21,2,117,33,133,33,2,117,28,133,28,2,0,227,144,20,160,20,192,20,3,20,20,35,26,227,111,10,1,0,10,16,10,80,10,3,10,23,228,15,184,1,21,178,31,228,5,184,1,98,8,8,33,113, + 237,65,201,6,80,152,5,24,93,2,10,8,81,51,50,30,2,7,52,38,35,34,6,21,20,30,2,51,50,54,3,234,49,98,146,98,93,144,97,51,48,97,146,98,103,147,93,44,149,120,114,116,125,34,61,85,51,117, + 127,4,18,87,143,104,57,57,104,143,87,84,143,104,59,58,104,143,85,151,134,136,149,76,109,69,32,133,0,0,2,0,107,0,141,4,76,73,95,8,8,39,42,3,235,8,236,1,6,235,5,0,235,1,1,19,10,12,235, + 16,236,10,15,235,14,9,235,10,17,8,16,7,15,9,0,7,8,12,3,3,3,6,73,94,20,36,237,212,237,16,253,73,94,5,132,9,43,49,48,37,35,53,9,1,53,51,1,21,1,135,8,44,2,250,178,1,82,254,176,176,1,82, + 252,211,180,131,9,46,178,1,79,141,29,1,111,1,116,31,254,141,63,254,147,136,10,8,62,0,255,255,0,27,0,0,4,205,5,69,16,39,0,212,254,246,0,0,16,38,0,209,0,0,17,7,0,215,1,152,253,182,0, + 40,64,23,3,2,24,24,3,2,80,25,1,64,25,1,25,1,96,20,1,20,0,32,0,1,0,17,93,53,132,2,36,93,53,53,0,63,130,3,131,75,32,17,130,75,32,176,134,75,32,236,133,75,32,240,131,75,43,213,1,172,253, + 180,0,24,64,13,2,23,24,143,65,130,60,32,0,131,59,32,11,130,59,34,205,5,81,130,135,34,214,255,33,133,59,32,25,137,135,37,32,64,18,3,2,53,131,135,39,58,1,64,58,1,58,0,64,135,130,136, + 127,8,64,0,2,0,153,254,223,4,108,4,57,0,39,0,43,0,119,64,54,102,6,118,6,134,6,3,102,4,118,4,134,4,3,69,27,85,27,2,105,0,121,0,137,0,3,10,9,40,152,43,43,0,30,152,0,29,112,29,128,29, + 3,29,29,45,19,152,130,116,130,67,52,9,184,255,192,64,21,15,19,72,9,9,35,42,157,43,105,35,121,35,137,35,130,32,37,24,158,35,184,1,14,72,67,5,67,98,5,79,231,8,37,18,57,47,237,51,50,77, + 84,6,39,55,52,62,6,55,51,14,7,66,54,5,24,70,88,8,37,2,1,21,35,53,153,24,75,188,29,41,175,113,108,177,126,68,2,113,195,79,24,75,187,33,40,4,68,201,201,0,0,255,255,0,130,0,8,38,4,204, + 6,167,18,38,0,36,0,0,17,7,0,67,255,163,0,243,0,21,180,2,21,5,38,2,184,255,163,180,22,25,4,7,37,1,43,53,0,130,2,136,47,32,168,136,47,40,116,0,93,0,244,0,19,64,11,132,48,34,93,21,24, + 137,45,138,95,137,47,32,195,130,112,133,47,38,22,5,38,2,0,27,21,148,47,32,157,136,95,32,197,136,47,131,144,34,0,32,48,148,47,32,93,136,47,32,105,130,47,37,226,0,23,64,13,3,131,144, + 36,3,2,0,25,23,133,49,131,192,32,53,130,28,136,195,32,96,134,51,48,6,0,196,3,85,0,22,64,12,3,2,56,3,3,2,3,31,134,146,66,151,6,8,73,0,2,0,1,0,0,4,171,5,69,0,15,0,24,0,133,64,81,11,4, + 43,4,155,4,3,23,1,2,4,1,82,2,17,2,94,3,4,20,3,4,2,3,9,5,9,5,13,8,12,92,24,15,15,3,13,13,26,3,1,95,23,11,95,8,15,8,63,8,127,8,143,8,4,111,130,4,8,43,159,8,191,8,223,8,5,23,8,23,8,12, + 17,7,95,4,3,12,95,3,15,18,0,63,51,237,63,237,50,18,57,57,47,47,113,114,16,237,16,237,1,47,18,57,131,2,32,51,134,21,91,151,8,45,16,192,192,49,48,1,93,1,33,3,35,1,33,21,24,70,235,7,8, + 95,33,17,35,14,3,7,3,33,2,134,254,177,142,168,1,230,2,155,254,175,1,71,254,185,1,122,253,219,73,4,20,21,21,6,139,1,28,1,156,254,100,5,69,152,254,82,150,254,47,152,4,178,16,66,72,68, + 18,254,111,0,255,255,0,113,254,68,4,118,5,90,18,38,0,38,0,0,17,7,0,120,1,98,0,0,0,11,182,1,27,50,42,21,11,37,1,43,65,55,6,38,162,0,0,4,98,6,168,130,39,32,40,132,39,52,67,255,185,0, + 244,0,21,180,1,12,5,38,1,184,255,157,180,13,16,0,10,66,43,10,143,47,34,116,0,91,66,43,5,132,48,34,63,12,15,137,45,32,0,131,135,143,47,66,43,5,130,95,32,13,132,95,35,228,180,18,12,146, + 95,32,93,136,143,40,105,0,20,0,226,0,25,182,2,131,95,32,2,130,145,35,248,180,16,14,133,49,65,253,5,35,255,255,0,202,130,195,32,1,132,195,32,44,134,195,32,175,139,195,32,176,131,195, + 32,1,138,195,143,47,34,116,0,103,138,195,32,103,130,195,136,45,132,195,143,47,66,239,8,32,1,131,196,32,0,130,193,147,47,131,195,133,143,66,191,8,134,196,32,0,130,193,132,49,133,193, + 37,0,0,0,2,0,27,130,195,8,64,101,5,69,0,16,0,31,0,142,64,98,114,22,130,22,2,2,48,22,96,22,2,20,22,36,22,2,121,26,137,26,2,127,25,143,25,2,27,25,43,25,59,25,107,25,4,17,17,6,0,90,0, + 23,1,23,23,33,30,19,90,6,10,6,8,8,89,33,5,8,34,18,8,95,30,31,9,79,9,2,63,9,111,9,159,9,207,9,223,9,239,9,6,9,64,31,40,72,9,9,19,29,95,11,3,19,24,76,60,10,38,43,93,113,51,237,50,1,75, + 155,5,39,16,237,50,18,57,47,93,237,102,53,8,34,93,93,95,78,41,6,55,33,17,35,53,51,17,33,50,30,2,1,33,17,51,50,18,17,52,46,2,43,1,17,33,24,74,52,7,8,50,135,135,1,55,158,243,166,85,254, + 29,254,223,162,212,206,56,114,172,117,121,1,33,2,176,168,254,255,174,89,2,85,149,2,91,77,162,250,254,249,254,71,1,10,1,10,135,192,121,57,254,65,66,31,7,62,42,6,158,18,38,0,49,0,0,17, + 7,0,197,0,0,0,245,0,19,64,11,1,20,5,38,1,0,31,47,8,18,65,185,7,32,0,131,47,38,102,255,236,4,102,6,168,130,47,32,50,132,47,52,67,255,170,0,244,0,21,180,2,30,5,38,2,184,255,170,180,31, + 34,10,0,65,235,10,143,47,34,116,0,98,65,235,5,132,48,34,98,30,33,137,45,148,95,65,235,8,33,2,31,130,96,34,0,36,30,148,47,32,157,136,143,68,219,9,131,144,34,0,41,57,148,47,32,93,136, + 47,35,105,0,3,0,68,219,6,130,48,36,3,2,2,34,32,133,49,66,27,8,8,53,1,0,157,0,225,4,46,4,115,0,11,0,142,64,29,185,2,201,2,217,2,3,185,0,201,0,217,0,3,182,8,198,8,214,8,3,182,6,198,6, + 214,6,3,7,184,255,240,64,14,18,22,72,1,16,130,4,32,10,131,4,32,4,130,18,32,179,130,12,32,5,130,7,32,182,130,7,35,25,5,1,3,130,10,33,64,38,131,11,34,3,1,9,131,35,35,22,9,1,11,132,7, + 38,11,1,4,128,10,1,79,73,33,5,51,2,10,7,1,178,0,25,63,51,1,47,93,93,93,51,49,48,0,93,43,133,1,36,43,43,1,43,43,130,19,8,54,93,19,9,1,55,9,1,23,9,1,7,9,1,157,1,98,254,160,104,1,94,1, + 94,105,254,162,1,96,102,254,159,254,156,1,74,1,98,1,96,103,254,159,1,95,105,254,164,254,160,105,1,97,254,157,0,130,0,8,42,3,0,56,255,218,4,146,5,112,0,26,0,34,0,43,0,197,64,140,139, + 29,1,101,25,117,25,133,25,3,106,12,122,12,138,12,3,119,11,1,103,37,135,37,90,216,5,35,70,17,86,17,24,95,249,10,8,57,52,34,116,34,132,34,3,118,31,134,31,2,123,30,139,30,2,54,30,1,116, + 38,1,57,38,1,59,43,123,43,139,43,3,29,38,30,37,4,35,27,24,21,8,11,4,14,23,0,91,95,27,1,27,64,25,28,72,0,130,7,8,46,27,45,35,91,10,80,14,1,16,14,1,14,30,37,29,38,4,32,41,95,8,11,24, + 21,4,5,22,19,4,32,95,9,5,19,0,63,51,237,63,51,18,23,57,237,17,23,57,130,254,42,113,51,237,18,57,47,93,43,113,237,50,130,16,32,17,130,24,24,88,101,16,34,93,93,0,67,40,6,90,162,7,38, + 53,52,18,62,1,51,50,90,193,5,8,119,7,52,39,1,22,51,50,18,1,20,23,1,46,1,35,34,2,4,102,70,132,191,120,111,172,63,117,94,141,48,47,68,130,191,124,217,128,116,94,140,48,48,201,30,254, + 0,79,152,163,148,253,146,30,2,0,39,116,75,156,156,2,169,173,254,250,177,89,70,68,156,65,189,88,232,145,173,1,2,172,86,134,156,65,187,85,228,146,164,114,253,82,138,1,24,1,10,166,116, + 2,172,66,65,254,249,0,0,255,255,0,142,255,236,4,61,6,168,18,38,0,56,67,35,6,47,159,0,244,0,21,180,1,26,5,38,1,184,255,160,180,27,66,197,10,146,47,34,116,0,86,67,35,5,132,48,34,86,26, + 29,66,195,14,143,95,67,35,8,33,1,27,130,96,34,0,32,26,148,47,32,93,136,143,69,15,10,130,145,36,2,1,0,30,28,66,243,13,130,147,36,36,0,0,4,168,132,195,32,60,132,195,34,116,0,101,134, + 147,32,9,130,99,36,101,9,12,4,8,68,23,9,24,70,247,10,8,46,16,0,27,0,99,64,66,118,15,134,15,2,101,26,117,26,133,26,3,106,18,122,18,138,18,3,0,90,127,17,143,17,2,17,64,25,28,72,0,17, + 1,17,17,29,22,11,7,24,70,252,10,38,22,96,6,21,96,11,6,130,1,36,7,9,3,7,18,90,192,7,38,16,237,16,237,1,47,93,78,156,5,35,93,43,93,237,65,250,5,44,1,20,14,2,35,33,21,35,17,51,21,33,50, + 24,71,1,20,35,191,1,13,125,24,71,2,11,8,84,2,180,88,159,120,71,254,5,69,235,60,111,156,99,134,149,253,192,44,79,107,0,0,0,1,0,142,255,227,4,143,5,204,0,67,0,189,64,41,124,58,140,58, + 2,117,56,133,56,2,101,66,1,102,65,1,68,63,84,63,116,63,132,63,4,101,47,1,101,46,117,46,133,46,3,3,40,9,12,72,51,184,255,232,64,37,130,8,8,39,57,31,1,54,72,31,38,61,54,61,2,61,72,24, + 64,24,80,24,144,24,160,24,4,31,9,24,24,9,31,3,42,0,72,17,184,255,192,179,18,23,132,7,8,42,64,41,9,14,72,17,17,69,42,70,43,64,18,21,72,0,43,1,43,17,61,31,3,5,36,80,49,0,43,21,96,9,112, + 9,128,9,3,9,9,14,80,5,22,74,215,5,46,63,63,237,18,23,57,1,47,93,43,237,18,57,47,43,130,5,84,117,7,38,93,16,237,93,49,48,0,68,9,5,47,93,1,93,0,93,1,20,14,2,35,34,38,47,1,30,3,80,233, + 5,35,4,53,52,62,130,3,32,46,130,21,38,14,2,21,17,35,17,52,121,56,5,8,161,21,20,14,4,21,20,30,4,4,143,41,87,135,95,80,148,55,2,26,66,74,78,37,92,98,54,81,95,81,54,33,49,58,49,33,33, + 63,90,57,68,107,73,39,180,63,120,174,110,102,155,105,53,33,51,58,51,33,55,82,95,82,55,1,39,66,118,88,52,25,24,164,15,26,18,10,95,79,59,82,63,57,71,94,70,51,73,58,48,52,63,43,37,62, + 45,26,35,77,122,88,252,3,4,3,119,172,112,54,46,80,109,62,59,87,67,52,49,50,32,40,61,58,64,84,111,255,255,0,128,255,236,4,136,5,180,18,38,0,68,0,0,17,6,0,67,142,0,0,21,180,2,66,17,38, + 2,184,255,112,180,67,70,21,3,70,115,12,143,47,38,116,77,0,0,19,64,11,132,48,34,47,66,69,137,45,146,91,39,195,238,0,0,25,183,2,67,130,91,40,32,72,1,184,255,208,180,72,66,132,48,34,93, + 53,0,73,131,7,133,95,32,169,136,143,38,197,248,0,0,34,64,14,132,95,39,48,77,1,32,77,1,16,77,130,58,35,218,180,77,93,133,58,32,93,134,60,136,111,32,123,136,59,39,105,240,0,0,30,64,10, + 3,131,60,35,3,2,32,70,130,55,35,210,180,70,68,133,55,32,53,70,84,6,135,55,33,6,11,136,55,32,196,130,115,49,23,64,13,3,2,71,17,38,3,2,18,76,66,39,47,37,1,43,135,48,8,118,3,0,20,255, + 236,4,184,4,78,0,65,0,80,0,89,0,198,64,129,124,88,140,88,2,124,84,140,84,2,106,19,122,19,138,19,3,108,17,124,17,140,17,3,5,61,21,61,2,5,55,21,55,2,3,33,19,33,2,27,40,9,14,72,20,56, + 86,0,73,75,36,75,9,73,10,10,64,72,87,207,75,223,75,2,16,87,1,75,87,75,87,30,91,47,72,48,48,66,72,79,30,95,30,159,30,175,30,4,30,81,42,80,53,0,80,86,75,81,36,86,130,1,8,36,59,56,53, + 47,47,143,47,2,47,47,53,16,6,69,80,25,20,15,25,128,9,1,9,9,25,22,0,63,51,47,93,17,51,51,16,237,50,134,9,37,57,57,47,47,16,237,131,1,41,50,1,47,93,237,51,47,237,17,18,131,19,33,93,93, + 130,35,130,12,131,42,38,57,57,49,48,0,43,93,133,0,33,1,21,100,77,5,39,23,14,3,35,34,46,2,39,133,6,126,26,20,41,23,62,1,51,50,30,2,29,1,5,109,96,5,8,124,61,1,7,14,3,1,34,14,2,7,33,46, + 1,2,167,1,21,44,70,49,75,89,16,138,12,43,73,109,77,57,92,72,53,18,21,54,73,92,58,68,97,63,30,57,93,118,62,167,15,33,52,36,32,51,38,23,4,168,8,40,74,113,80,91,121,35,45,125,76,96,129, + 78,32,252,5,57,57,56,81,52,25,122,37,74,58,37,2,158,28,62,51,35,2,1,105,9,88,1,247,17,82,136,98,55,94,72,45,45,91,73,47,24,55,89,64,49,85,62,36,44,84,119,75,107,137,126,77,17,43,70, + 69,74,65,88,155,210,122,24,204,82,100,126,82,9,54,2,82,29,74,127,98,171,157,255,255,0,130,254,68,4,56,4,78,18,38,0,70,0,76,15,5,43,81,0,0,0,11,182,1,33,46,38,0,28,76,15,9,38,133,255, + 236,4,70,5,180,130,39,32,72,130,39,52,6,0,67,177,0,0,21,180,2,44,17,38,2,184,255,178,180,45,48,21,31,67,27,12,143,47,33,116,111,67,27,5,131,48,34,111,44,47,137,45,130,131,143,43,33, + 195,7,133,43,32,45,130,92,34,7,50,44,146,43,32,123,136,135,34,105,8,0,78,1,5,130,88,36,3,2,8,48,46,133,45,76,3,8,36,143,0,0,4,101,132,183,32,192,133,183,56,189,0,0,30,64,11,1,10,17, + 38,1,96,11,1,80,11,1,184,255,169,180,11,14,3,1,130,190,67,63,10,143,55,33,116,104,132,147,132,55,34,84,10,13,132,46,131,100,32,0,130,191,143,43,40,195,10,0,0,34,64,14,1,11,131,99,38, + 16,1,80,16,1,48,16,130,102,35,246,180,16,10,132,55,32,93,144,103,131,207,133,159,39,105,41,0,0,47,64,31,2,131,104,48,2,1,144,14,1,128,14,1,112,14,1,96,14,1,80,14,1,130,83,36,0,14,12, + 6,9,132,170,130,69,32,93,67,135,7,8,93,2,0,125,255,236,4,78,5,216,0,37,0,55,0,187,64,129,69,54,85,54,101,54,3,69,49,85,49,101,49,3,74,46,90,46,106,46,3,74,40,90,40,106,40,3,43,4,59, + 4,107,4,3,5,36,21,36,2,19,16,6,9,4,18,8,8,12,18,12,18,33,23,71,3,112,38,1,160,38,1,0,38,16,38,96,38,3,38,38,57,48,71,255,33,1,130,2,8,73,0,33,1,33,101,16,117,16,133,16,3,106,6,122, + 6,138,6,3,16,9,19,6,4,7,17,43,80,0,3,0,0,0,16,0,2,7,0,7,0,12,51,80,28,22,18,17,17,12,0,0,63,51,47,51,63,237,17,57,57,47,47,93,17,51,16,237,17,18,23,57,93,93,1,47,24,73,156,7,34,113, + 114,51,133,29,33,51,47,73,55,11,41,1,50,22,23,46,1,39,5,39,55,130,5,41,51,30,1,23,37,23,7,22,18,29,89,123,11,33,1,52,70,56,6,67,113,5,8,104,2,100,71,117,43,39,105,76,254,252,55,218, + 56,120,66,209,45,83,42,1,8,55,211,164,167,57,120,188,132,125,181,118,56,52,116,186,1,178,33,71,113,80,81,115,73,33,144,145,81,118,75,36,3,211,32,27,89,159,69,115,110,94,45,78,35,20, + 51,31,114,108,92,149,254,125,244,6,121,196,138,75,73,132,185,111,107,182,134,75,254,14,94,137,90,44,45,92,136,92,190,178,44,90,139,255,255,119,11,5,44,5,169,18,38,0,81,0,0,17,6,0,197, + 10,66,19,5,40,38,17,38,1,9,49,65,19,36,76,95,10,38,130,255,236,4,73,5,180,130,43,32,82,132,43,48,67,186,0,0,21,180,2,36,17,38,2,184,255,187,180,37,40,73,11,14,143,47,33,116,101,132, + 91,132,48,34,101,36,39,76,187,12,143,43,33,195,0,133,43,32,37,130,92,34,0,42,36,146,43,131,179,133,135,32,197,134,43,131,136,34,0,47,63,146,43,32,123,136,179,32,105,130,43,35,23,64, + 13,3,131,132,36,3,2,0,40,38,73,141,12,8,91,3,0,116,0,223,4,87,4,117,0,3,0,7,0,11,0,99,64,60,6,14,11,5,8,14,112,5,1,5,3,11,171,0,63,8,79,8,2,0,8,1,10,3,8,8,13,12,9,174,64,8,14,4,1,5, + 14,0,174,47,1,63,1,79,1,3,79,1,143,1,159,1,239,1,4,1,4,173,5,179,0,63,237,214,93,113,237,43,0,24,16,77,246,90,163,9,35,93,51,237,50,24,109,231,7,40,49,48,1,53,51,21,1,53,33,130,3,56, + 51,21,2,17,168,253,187,3,227,253,186,168,3,190,183,183,254,162,146,146,254,127,183,183,0,130,141,8,57,110,255,236,4,93,4,78,0,25,0,37,0,49,0,219,64,36,69,36,85,36,101,36,3,107,48,1, + 74,48,90,48,2,12,32,11,17,72,121,7,137,7,2,7,64,11,15,72,116,25,132,25,2,25,184,255,192,64,9,131,13,36,20,132,20,2,20,131,13,32,103,130,13,38,29,42,30,41,4,38,26,76,47,6,63,23,0,71, + 32,26,48,26,64,26,112,26,128,26,144,26,6,0,26,80,26,160,26,176,26,192,26,5,0,26,16,26,96,132,23,8,46,224,26,6,26,26,51,38,71,14,10,10,14,64,40,46,72,14,64,24,27,72,14,42,29,41,30,4, + 33,45,8,11,24,21,4,19,5,22,22,45,80,19,16,33,80,5,9,9,116,197,8,38,237,51,47,17,18,23,57,131,3,35,1,47,43,43,104,161,8,35,114,237,50,47,76,93,9,37,43,93,1,43,93,0,131,5,33,0,93,116, + 213,7,102,251,6,35,53,52,62,2,76,87,5,46,22,7,52,38,39,1,30,1,51,50,62,2,37,20,22,76,90,5,8,112,14,2,4,93,70,131,188,119,101,166,64,77,76,84,48,51,67,129,188,121,213,122,72,78,80,97, + 189,17,16,254,3,43,114,64,69,118,86,48,253,139,17,17,1,253,42,113,69,69,117,85,48,2,30,142,211,140,69,53,54,87,68,95,69,188,122,140,209,141,70,100,82,68,90,137,247,76,120,46,253,192, + 53,42,39,98,166,126,75,117,46,2,64,50,39,41,99,164,0,0,0,255,255,0,185,255,236,4,31,5,180,18,38,0,88,66,247,5,49,162,0,0,21,180,1,38,17,38,1,184,255,156,180,39,42,36,19,70,39,12,143, + 47,33,116,90,67,83,9,34,84,38,41,137,45,146,91,32,195,130,114,130,91,32,39,132,91,35,250,180,44,38,148,91,32,123,136,139,32,105,130,47,34,25,182,2,131,140,32,2,130,141,35,250,180,42, + 40,133,49,70,45,5,132,191,36,66,254,87,4,137,132,191,32,92,132,191,33,116,107,133,143,32,32,130,100,35,107,32,35,17,70,229,9,34,2,0,185,130,43,8,82,66,5,204,0,19,0,56,0,101,64,68,121, + 33,137,33,2,100,18,1,70,18,86,18,2,107,2,1,73,2,89,2,2,53,22,1,58,38,1,6,27,22,27,118,27,134,27,4,30,71,96,0,1,0,0,58,50,10,47,70,0,48,128,48,2,48,49,0,47,27,40,15,80,35,22,20,5,80, + 25,16,0,63,237,51,131,2,32,63,76,90,9,76,88,6,32,93,131,0,120,119,15,36,1,62,3,51,50,24,117,144,9,50,39,35,30,3,21,17,35,17,51,17,20,6,7,6,7,3,136,24,59,121,131,13,35,253,232,25,63, + 121,159,6,121,193,14,33,180,1,130,0,33,2,34,121,127,14,33,1,206,121,152,7,121,187,14,44,7,117,254,89,29,57,22,26,23,0,255,255,0,65,51,5,35,123,18,38,0,65,51,5,48,105,5,0,0,23,64,13, + 2,1,32,17,38,2,1,5,36,34,65,53,5,65,101,6,40,1,0,143,0,0,4,101,4,58,125,223,5,127,237,5,125,223,8,33,11,10,127,223,14,125,223,27,32,233,127,212,9,38,142,142,142,3,30,142,0,130,0,8, + 60,2,0,20,255,251,4,197,5,75,0,23,0,40,0,116,64,73,118,38,134,38,2,70,9,86,9,2,73,4,89,4,2,18,14,18,14,22,17,21,92,27,27,6,22,22,42,36,92,6,20,95,15,17,63,17,127,17,143,17,4,111,130, + 4,57,159,17,191,17,223,17,5,17,17,14,24,22,95,23,3,3,23,18,31,15,95,14,11,11,14,3,108,60,11,44,50,17,57,47,113,114,237,1,47,237,18,57,47,130,2,32,237,85,210,5,78,4,5,38,33,14,1,35, + 34,2,17,79,232,6,85,187,9,37,37,50,54,55,17,46,24,70,27,8,8,206,2,97,23,49,26,255,236,61,123,185,124,58,51,2,46,254,114,1,111,254,145,1,183,253,59,22,59,19,19,59,21,78,117,78,39,40, + 79,116,2,3,1,82,1,92,180,1,0,162,76,6,152,254,72,150,254,57,152,140,2,4,4,33,3,3,58,127,200,143,142,204,132,63,0,0,3,0,28,255,236,4,180,4,78,0,38,0,58,0,67,0,170,64,115,123,66,139, + 66,2,122,62,138,62,2,118,56,134,56,2,121,42,137,42,2,102,3,118,3,134,3,3,100,51,116,51,132,51,3,107,47,123,47,139,47,3,5,34,21,34,2,5,26,21,26,2,10,21,26,21,2,16,29,64,0,72,39,39,24, + 37,8,73,9,9,37,72,16,65,1,65,65,69,49,72,79,24,95,24,159,24,175,24,4,24,0,80,64,64,54,59,44,80,32,29,27,16,5,54,80,19,16,14,19,128,8,1,8,8,19,75,121,13,38,51,237,50,18,57,47,237,88, + 249,8,33,50,47,90,138,5,33,50,57,104,214,9,32,93,130,0,32,1,91,51,5,49,55,23,14,3,35,34,39,14,1,35,34,46,2,53,16,18,51,50,75,89,8,32,37,24,105,137,14,75,93,8,8,68,200,19,41,64,44,65, + 78,14,138,11,40,68,102,72,182,81,42,133,91,75,120,85,45,170,162,187,79,45,133,81,91,123,74,31,253,107,24,44,63,39,39,65,47,25,27,46,62,35,40,65,46,25,1,76,25,55,47,32,2,1,68,8,80,1, + 247,85,143,103,57,75,72,6,53,183,93,90,68,140,211,143,1,23,1,25,177,94,83,88,155,210,122,24,39,126,164,125,154,12,33,2,41,75,53,5,8,78,0,0,0,1,1,27,4,177,3,177,5,180,0,9,0,37,64,23, + 32,1,80,1,96,1,128,1,4,1,6,3,32,11,15,72,3,142,9,140,5,2,147,0,63,51,253,237,43,1,47,196,93,49,48,1,21,35,39,35,7,35,53,55,51,3,177,105,219,2,232,104,254,164,4,197,20,138,138,20,239, + 130,78,47,0,2,1,116,4,124,3,88,6,11,0,19,0,39,0,139,98,179,6,44,15,72,13,184,255,232,64,97,9,15,72,7,24,130,4,32,3,131,4,8,65,20,132,0,0,16,0,32,0,3,48,0,112,0,128,0,176,0,192,0,208, + 0,6,96,0,1,15,0,31,0,2,0,30,132,0,10,1,10,25,142,127,15,143,15,2,15,64,9,12,72,15,35,142,47,5,79,5,111,5,223,5,4,15,5,47,5,63,132,12,52,127,5,159,5,191,5,8,5,64,22,25,72,5,0,47,43, + 93,113,237,212,43,98,241,5,38,212,93,93,113,114,237,49,102,141,5,98,244,32,40,88,30,60,91,61,61,91,60,30,135,7,42,121,13,29,46,33,33,46,30,13,13,30,131,7,61,29,13,5,68,39,72,56,33, + 33,56,72,39,39,72,55,33,33,55,72,39,15,29,24,15,15,24,29,15,15,30,131,7,43,30,0,0,1,0,255,4,177,3,204,5,169,130,195,40,81,64,16,10,31,26,31,2,30,131,237,8,53,5,15,21,15,2,14,184,255, + 232,64,36,9,15,72,26,131,15,27,47,27,63,27,127,27,143,27,5,27,27,10,131,0,11,48,11,2,11,26,5,143,16,140,21,143,10,0,147,0,63,50,237,253,237,51,77,229,5,32,93,132,195,36,93,43,93,1, + 34,74,48,5,33,7,35,69,46,5,8,53,51,50,62,2,55,51,14,3,3,2,42,84,80,77,35,23,29,19,10,5,111,5,20,45,74,59,44,84,80,74,33,23,30,20,10,4,112,5,20,43,74,4,177,37,45,37,16,30,44,29,45,88, + 71,44,131,10,38,31,44,28,44,88,71,45,130,175,8,40,170,1,195,4,34,2,76,0,3,0,15,181,2,0,0,185,1,188,0,63,237,1,47,47,49,48,19,53,33,21,170,3,120,1,195,137,137,0,1,255,246,130,39,32, + 214,148,39,32,3,130,39,34,10,4,224,133,39,8,34,1,141,3,94,3,63,5,204,0,3,0,42,181,6,1,22,1,2,1,184,255,240,64,17,14,17,72,3,154,0,151,1,153,0,2,130,18,34,156,0,0,132,101,24,117,238, + 7,48,1,3,33,1,3,63,169,254,247,1,55,5,204,253,146,2,110,130,31,35,0,1,1,143,130,75,32,65,132,75,36,51,64,34,0,24,130,67,39,9,0,25,0,2,9,2,25,24,118,71,12,32,0,130,198,34,3,156,1,141, + 82,36,93,43,1,19,33,130,66,42,169,1,9,254,201,3,94,2,110,253,146,130,84,40,1,1,93,254,149,3,15,1,3,130,159,34,59,64,40,153,83,32,47,130,83,36,0,16,0,2,0,24,118,156,11,135,174,134,91, + 32,93,132,91,33,254,149,134,91,38,2,0,177,3,94,4,26,132,175,41,7,0,87,181,6,5,22,5,2,5,131,253,32,9,130,185,65,11,9,32,38,130,13,48,7,154,4,151,5,153,47,6,159,6,2,0,6,16,6,2,6,65,28, + 7,32,16,130,217,36,5,7,1,156,3,131,205,35,51,50,1,47,131,115,33,47,93,135,121,38,43,93,1,3,33,1,33,130,3,40,2,99,169,254,247,1,55,2,50,65,53,10,65,57,5,34,2,0,178,130,135,32,27,134, + 135,36,104,64,75,4,24,130,113,39,9,4,25,4,2,9,6,25,130,107,32,16,130,14,147,249,51,5,153,6,151,7,154,0,4,16,4,144,4,3,4,1,153,2,151,3,154,65,7,8,33,7,5,65,98,6,132,148,133,149,40,237, + 253,237,49,48,43,93,93,43,131,3,33,1,19,130,152,130,3,40,2,105,169,1,9,254,201,253,206,65,121,10,36,2,110,253,146,0,130,151,45,254,148,4,27,1,2,0,3,0,7,0,106,64,76,196,151,41,4,0,156, + 5,1,155,8,0,16,244,109,31,5,168,153,33,254,148,131,149,66,23,8,8,51,80,1,145,3,123,3,188,0,19,0,55,64,37,21,17,37,17,53,17,3,21,13,37,13,53,13,3,26,7,42,7,58,7,3,26,3,42,3,58,3,3,0, + 0,16,10,1,10,5,5,15,0,47,101,242,6,33,49,48,97,74,20,57,3,123,44,77,102,58,57,99,75,43,43,75,99,57,58,102,77,44,2,170,58,102,76,45,45,76,130,21,53,100,74,43,43,74,100,0,1,1,90,0,141, + 3,93,3,172,0,8,0,46,64,24,106,133,8,34,79,1,1,130,27,34,1,2,1,106,114,11,35,51,51,1,24,66,10,5,33,212,237,106,99,10,33,2,170,106,79,20,130,63,32,110,130,91,32,113,132,91,34,48,64,26, + 97,129,9,41,0,1,96,1,208,1,3,1,8,7,97,113,11,133,93,97,107,5,97,97,10,33,2,32,97,88,6,32,81,97,78,9,63,0,0,1,0,204,0,0,3,254,5,69,0,3,0,39,183,2,3,16,3,3,5,0,1,184,255,240,64,10,0, + 1,16,130,183,60,2,6,0,24,0,63,63,1,47,93,56,51,18,57,47,56,51,49,48,33,35,1,51,1,66,118,2,191,115,130,54,32,0,131,67,8,104,91,255,236,4,99,5,90,0,60,0,150,64,85,105,49,1,55,3,71,3, + 87,3,3,43,42,42,12,53,0,0,57,11,12,12,28,62,51,1,1,57,110,28,32,0,23,1,23,23,28,34,22,22,28,64,11,14,72,28,1,23,115,24,59,24,54,32,115,33,51,33,111,24,1,63,33,1,24,33,24,33,6,46,115, + 37,43,43,37,7,6,115,17,48,11,1,11,11,17,25,0,63,51,47,93,16,237,130,5,32,16,102,187,6,33,93,17,114,90,8,38,1,47,43,51,47,51,17,130,33,35,50,16,237,50,114,78,6,130,172,130,18,32,17, + 130,175,40,0,93,93,1,33,30,3,51,50,96,208,9,41,39,35,53,51,46,1,53,52,54,55,130,8,83,48,5,33,23,7,73,7,5,8,37,7,33,21,33,14,1,21,20,23,33,3,105,254,53,8,42,77,116,80,52,87,75,66,30, + 76,42,88,96,108,62,120,178,123,71,13,131,119,2,130,0,8,77,119,129,29,251,229,56,100,93,87,43,82,58,138,86,73,114,83,51,11,1,205,254,37,1,1,3,1,218,1,207,79,122,84,43,18,30,37,19,135, + 28,46,32,18,68,125,178,109,144,23,34,18,26,35,18,142,234,236,14,28,44,30,140,42,58,28,71,122,95,141,14,43,21,42,32,0,0,0,1,101,87,7,39,0,3,0,40,64,24,3,150,130,18,42,0,0,4,5,1,155, + 15,0,31,0,63,130,21,40,64,14,17,72,0,0,47,43,93,105,45,5,101,194,5,42,51,17,1,240,235,1,164,1,43,254,213,130,48,45,1,37,2,77,2,199,5,69,0,18,0,68,64,28,100,224,20,57,0,9,1,9,9,3,14, + 184,1,17,179,16,1,228,0,184,1,18,0,63,237,50,63,51,51,47,135,87,130,10,100,233,5,8,48,49,48,1,53,51,17,14,3,35,53,50,62,2,55,51,17,51,21,1,73,135,13,47,51,47,13,18,48,51,47,17,121, + 116,2,77,107,2,1,26,36,23,10,119,12,27,44,33,253,115,88,35,5,56,250,2,77,3,4,5,81,0,32,0,79,185,0,21,255,216,64,32,9,17,72,31,31,23,225,105,121,16,45,95,14,1,14,14,11,228,20,184,1, + 19,179,1,30,137,140,32,237,130,140,93,230,6,73,162,7,38,47,49,48,0,43,19,53,105,127,20,8,56,21,250,24,74,84,84,68,42,51,60,61,59,8,130,6,39,65,92,61,120,121,46,71,86,82,68,16,1,124, + 2,77,97,55,85,72,63,64,71,44,57,64,62,61,5,47,80,58,33,112,103,55,89,75,65,63,65,38,133,175,36,234,2,59,2,237,130,175,37,51,0,138,185,0,42,130,175,32,64,130,175,33,3,40,130,4,50,0, + 28,16,28,32,28,3,123,48,1,34,225,35,35,10,45,225,28,24,130,1,41,10,0,225,17,11,225,10,17,17,53,92,126,8,46,48,24,23,22,22,14,40,95,34,1,34,34,31,228,40,130,207,45,64,9,14,228,5,80, + 11,1,11,11,5,184,1,20,66,188,7,32,237,130,216,38,17,18,57,47,196,17,57,130,223,32,93,130,9,33,16,237,66,206,6,105,137,5,34,0,93,1,88,169,5,24,119,15,10,105,139,12,39,54,53,52,38,35, + 34,6,7,24,122,61,8,8,116,6,7,21,30,1,2,237,39,68,94,56,64,92,62,34,6,136,9,65,48,54,66,25,41,54,29,41,37,68,64,54,41,45,66,6,135,7,44,68,85,48,58,87,58,29,67,69,82,75,3,26,54,83,57, + 29,37,60,76,39,13,60,61,60,65,37,47,26,10,109,57,66,53,50,63,57,12,54,80,54,26,31,55,74,42,66,85,26,2,18,100,0,0,2,0,197,2,75,3,53,5,69,0,10,0,23,0,93,64,18,9,9,8,1,224,18,6,96,24, + 123,194,7,8,44,25,17,184,1,23,64,32,4,64,13,17,72,0,4,16,4,2,4,0,4,228,8,5,15,17,31,17,47,17,159,17,191,17,5,17,17,1,11,6,186,1,17,0,1,1,118,20,6,24,120,204,8,33,43,237,130,255,8,74, + 93,51,51,237,50,50,47,49,48,1,21,35,53,33,53,1,51,17,51,21,3,14,3,7,3,33,17,60,2,54,2,187,127,254,137,1,105,141,122,248,6,21,24,23,7,185,1,9,1,2,236,161,161,99,1,246,254,12,101,1,217, + 9,31,34,32,10,255,0,1,4,13,32,31,27,0,131,0,36,30,1,110,0,1,131,8,130,3,34,53,0,108,134,11,36,1,0,7,0,178,134,11,32,2,130,11,32,202,134,11,36,3,0,18,0,248,134,11,36,4,0,7,1,27,134, + 11,36,5,0,12,1,61,134,11,32,6,130,23,32,90,134,11,34,7,0,80,130,225,133,95,36,8,0,20,2,127,134,23,35,9,0,14,2,135,95,36,10,1,99,5,137,134,23,36,11,0,28,7,39,134,11,36,12,0,46,7,162, + 134,11,35,13,0,53,8,135,95,38,14,0,26,8,169,0,3,130,196,32,9,130,179,32,106,130,3,133,11,36,1,0,14,0,162,134,23,32,2,130,11,32,186,134,11,36,3,0,36,0,210,134,11,36,4,0,14,1,11,134, + 11,36,5,0,24,1,35,134,11,32,6,130,23,32,74,134,11,36,7,0,160,1,98,134,11,36,8,0,40,2,85,134,11,36,9,0,28,2,148,134,11,36,10,2,198,2,193,134,11,36,11,0,56,6,237,134,11,36,12,0,92,7, + 68,134,11,36,13,0,106,7,209,134,11,42,14,0,52,8,115,0,68,0,105,0,103,130,3,32,116,130,3,38,122,0,101,0,100,0,32,130,3,36,97,0,116,0,97,130,9,40,99,0,111,0,112,0,121,0,114,132,37,32, + 104,130,21,38,32,0,40,0,99,0,41,130,27,36,50,0,48,0,49,130,3,34,32,0,71,130,37,32,111,130,69,32,108,130,63,34,32,0,67,130,13,32,114,130,53,32,111,130,53,131,71,32,105,130,15,8,56,110, + 0,46,0,0,68,105,103,105,116,105,122,101,100,32,100,97,116,97,32,99,111,112,121,114,105,103,104,116,32,40,99,41,32,50,48,49,48,32,71,111,111,103,108,101,32,67,111,114,112,111,114,97, + 116,105,111,110,130,54,131,79,34,117,0,115,130,129,32,110,130,95,42,0,67,111,117,115,105,110,101,0,0,82,130,13,36,103,0,117,0,108,130,169,32,114,130,14,36,101,103,117,108,97,130,8, + 32,65,130,43,32,99,130,27,32,110,130,199,32,101,130,131,34,32,0,45,130,173,142,69,41,65,115,99,101,110,100,101,114,32,45,130,109,134,80,142,34,136,104,33,86,0,131,70,131,128,34,111, + 0,110,130,74,32,49,130,204,32,48,130,248,36,0,86,101,114,115,130,163,37,32,49,46,48,48,0,132,247,147,167,141,86,32,32,130,199,32,115,130,68,32,97,130,3,32,116,130,157,32,97,132,165, + 32,109,132,195,32,107,130,19,34,111,0,102,130,5,65,81,13,32,97,130,118,32,100,130,21,131,37,32,121,130,7,32,98,130,144,32,32,130,59,34,101,0,103,132,77,32,116,132,160,65,187,5,32,105, + 130,43,34,32,0,99,132,17,32,116,130,85,133,15,34,106,0,117,130,47,36,105,0,115,0,100,130,49,34,99,0,116,130,5,131,204,32,115,130,202,65,81,7,49,32,105,115,32,97,32,116,114,97,100,101, + 109,97,114,107,32,111,102,65,143,7,61,97,110,100,32,109,97,121,32,98,101,32,114,101,103,105,115,116,101,114,101,100,32,105,110,32,99,101,114,116,97,130,7,47,106,117,114,105,115,100, + 105,99,116,105,111,110,115,46,0,0,65,130,17,66,20,21,32,0,65,134,8,65,242,10,34,0,0,83,130,159,34,101,0,118,132,229,32,77,130,195,32,116,132,15,34,115,0,111,130,219,46,0,83,116,101, + 118,101,32,77,97,116,116,101,115,111,110,65,118,15,34,32,0,119,130,48,34,115,0,32,130,232,32,101,130,238,34,105,0,103,130,52,65,26,5,34,98,0,121,130,23,155,92,32,32,134,59,32,97,130, + 51,34,32,0,105,130,5,32,110,130,116,32,118,132,130,32,105,132,144,32,44,130,65,32,114,130,152,33,102,0,131,5,34,115,0,104,132,37,32,103,130,21,32,115,130,37,32,110,130,113,32,32,130, + 3,32,101,130,29,34,105,0,102,142,133,32,32,130,198,32,104,132,75,131,91,131,163,32,109,130,73,32,116,132,43,32,99,130,23,34,108,0,108,132,161,32,99,130,115,34,109,0,112,132,41,34,105, + 0,98,130,21,32,101,130,73,32,119,130,105,32,116,130,111,67,96,5,32,117,132,53,66,222,5,32,78,130,69,36,119,33,34,0,46,66,230,16,32,32,130,73,32,102,130,135,66,191,5,131,117,32,109, + 130,87,32,114,132,211,65,19,5,32,111,130,227,32,45,130,177,32,99,132,207,32,101,132,247,131,217,34,97,0,100,130,127,32,98,130,115,32,108,132,119,32,121,130,91,32,99,130,125,32,97,130, + 39,34,97,0,99,130,201,133,227,32,115,130,9,32,105,130,15,66,210,5,32,110,130,53,133,229,131,177,131,193,37,110,0,45,0,69,0,131,173,32,111,130,121,32,101,130,83,32,110,130,73,36,87, + 0,71,0,76,148,81,32,32,130,139,32,101,130,83,32,32,132,41,32,100,130,35,32,115,130,173,34,108,0,118,130,227,131,99,32,116,130,131,131,91,32,110,130,15,32,101,130,109,131,19,67,50,5, + 32,100,130,17,131,37,32,108,130,47,32,112,130,11,32,114,130,73,33,32,0,131,13,34,111,0,107,65,165,8,32,102,130,29,32,114,130,85,32,119,130,17,32,100,130,103,32,104,130,159,65,111,21, + 131,41,32,110,130,33,133,117,32,111,130,51,32,97,130,111,32,100,130,255,32,101,130,89,66,97,5,32,111,130,249,34,117,0,109,130,109,131,41,32,32,68,244,6,32,116,65,57,16,32,97,130,39, + 32,114,130,121,32,115,132,147,32,112,130,149,32,97,130,89,133,139,32,109,130,19,33,46,0,67,132,8,47,119,97,115,32,100,101,115,105,103,110,101,100,32,98,121,32,66,238,13,32,32,130,29, + 8,35,97,110,32,105,110,110,111,118,97,116,105,118,101,44,32,114,101,102,114,101,115,104,105,110,103,32,115,97,110,115,32,115,101,114,105,102,134,66,55,32,116,104,97,116,32,105,115, + 32,109,101,116,114,105,99,97,108,108,121,32,99,111,109,112,130,58,53,98,108,101,32,119,105,116,104,32,67,111,117,114,105,101,114,32,78,101,119,170,46,69,12,7,45,32,111,102,102,101, + 114,115,32,105,109,112,114,111,118,130,137,40,111,110,45,115,99,114,101,101,110,130,111,39,97,100,97,98,105,108,105,116,130,75,37,104,97,114,97,99,116,130,113,40,115,116,105,99,115, + 32,97,110,100,130,114,43,101,32,112,97,110,45,69,117,114,111,112,101,130,172,36,87,71,76,32,99,135,40,130,158,32,116,132,38,37,115,111,108,118,101,115,132,45,36,110,101,101,100,115, + 130,113,130,180,34,118,101,108,130,52,130,120,35,108,111,111,107,131,210,34,102,111,114,130,166,35,100,116,104,45,138,183,35,102,111,110,116,130,58,54,111,32,97,100,100,114,101,115, + 115,32,100,111,99,117,109,101,110,116,32,112,111,114,116,135,156,35,97,99,114,111,130,27,35,112,108,97,116,130,69,39,109,115,46,0,0,104,0,116,130,1,36,112,0,58,0,47,130,1,33,119,0, + 131,1,34,46,0,97,70,48,14,41,99,0,111,0,114,0,112,0,46,0,66,25,5,32,47,130,56,42,116,116,112,58,47,47,119,119,119,46,97,70,57,6,36,99,111,114,112,46,130,148,131,29,182,86,34,116,0, + 121,130,102,32,101,68,170,14,32,114,130,142,32,46,132,170,34,109,0,108,157,122,35,116,121,112,101,66,44,6,37,114,115,46,104,116,109,130,47,34,76,0,105,130,183,34,101,0,110,130,70,67, + 227,5,32,117,130,11,33,100,0,68,43,5,67,63,7,40,83,0,73,0,76,0,32,0,79,132,128,32,110,130,9,32,70,130,245,66,197,5,141,65,32,44,130,27,71,1,19,8,35,49,0,0,76,105,99,101,110,115,101, + 100,32,117,110,100,101,114,32,116,104,101,32,83,73,76,32,79,112,101,110,32,70,111,110,116,32,134,32,33,44,32,71,41,9,130,54,65,133,13,32,115,130,173,32,114,130,179,34,112,0,116,132, + 248,32,115,130,11,34,108,0,46,130,141,36,114,0,103,0,47,130,161,32,70,130,169,33,0,104,65,129,5,51,115,99,114,105,112,116,115,46,115,105,108,46,111,114,103,47,79,70,76,0,130,0,33,2, + 0,132,0,35,254,35,0,84,130,8,32,1,130,3,141,2,32,216,132,21,8,88,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0,22,0,23,0,24,0,25,0,26, + 0,27,0,28,0,29,0,30,0,31,0,32,0,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41,0,42,0,43,0,44,0,45,0,46,130,166,8,60,48,0,49,0,50,0,51,0,52,0,53,0,54,0,55,0,56,0,57,0,58,0,59,0,60,0,61, + 0,62,0,63,0,64,0,65,0,66,0,67,0,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75,0,76,0,77,0,78,130,228,38,80,0,81,0,82,0,83,130,191,9,12,85,0,86,0,87,0,88,0,89,0,90,0,91,0,92,0,93,0,94,0,95, + 0,96,0,97,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0,164,0,138,0,218,0,131,0,147,1,2,1,3,0,141,0,151,0,136,0,195,0,222,1,4,0,158,0,170,0,245,0,244,0,246,0, + 162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207,0,204,0,205,0,206,0,233,0,102,0,211,0,208,0,209,0,175,0,103,0,240,0,145,0,214,0,212,0,213,0,104,0,235, + 0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115,0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0,161,0,127,0,126,0,128, + 0,129,0,236,0,238,0,186,0,215,0,176,0,177,0,216,0,221,0,217,0,178,0,179,0,182,0,183,0,196,0,180,0,181,0,197,0,135,0,190,0,191,0,188,1,5,1,6,1,7,1,8,1,9,1,10,7,117,110,105,48,48,66, + 50,134,7,32,51,134,7,37,57,4,69,117,114,111,131,12,51,50,50,49,57,11,111,110,101,102,114,97,99,116,105,111,110,11,116,119,111,135,11,36,13,116,104,114,101,132,25,40,105,116,111,110, + 12,102,111,117,114,135,26,32,0,131,0,44,3,0,8,0,2,0,17,0,1,255,255,0,3,130,16,32,1,130,3,34,0,0,1,130,5,32,14,130,3,32,24,130,3,32,0,130,31,32,1,130,17,32,215,130,3,32,4,130,15,33, + 2,0,131,0,131,33,36,0,199,254,176,223,131,12,43,189,118,137,36,0,0,0,0,201,111,45,196,5,250,86,44,41,149, +}; + diff --git a/src/EquipotentialView.cpp b/src/EquipotentialView.cpp index 1f4767a..7b369d8 100644 --- a/src/EquipotentialView.cpp +++ b/src/EquipotentialView.cpp @@ -72,7 +72,9 @@ static int g_ctxInstanceId = -1; // box the pin sits on. -1 = none. static int g_ctxPortId = -1; -struct OccurrenceInfo { InstancePath path; DesignRef designRef; std::optional sourceLoc; }; +// What a box's context menu needs. `hasSources`: offer "Show Sources" -- +// a module frame always does (the provider sent no flag for it). +struct OccurrenceInfo { InstancePath path; DesignRef designRef; bool hasSources = false; }; struct PortEquiRequest { std::vector pathIds; unsigned termId = 0; @@ -293,7 +295,7 @@ static HierEmitResult emitInstanceInternals(InstanceShape& parent, int& nextInst cs.hierExpanded = c.hasInstances && g_hierExpanded.count(cs.path) > 0; cs.diagOutline = DiagnosisStore::instanceColor(cs.path); childIdToShapeId[c.childId] = cs.id; - g_occInfoByShapeId[cs.id] = { cs.path, c.designRef }; + g_occInfoByShapeId[cs.id] = { cs.path, c.designRef, c.hasSources }; children.push_back(std::move(cs)); } @@ -473,12 +475,7 @@ EquipotentialView::startInstanceFromResolved(const json& reply) { if (inst.contains("design_ref")) start.designRef = inst["design_ref"].get(); start.primitiveType = primitiveTypeFromString(inst.value("primitive_type", std::string("unknown"))); start.hasInstances = inst.value("has_instances", false); - if (inst.contains("source_loc") && inst["source_loc"].is_object()) { - const auto& loc = inst["source_loc"]; - start.sourceLoc = SourceLoc{loc.value("file", std::string("")), loc.value("line", 0), - loc.value("end_line", 0), loc.value("column", 0), - loc.value("end_column", 0)}; - } + start.hasSources = inst.value("has_sources", false); for (const auto& t : inst.value("terms", json::array())) { ExpandedPort ep; ep.name = t.value("name", std::string("")); @@ -630,15 +627,12 @@ void EquipotentialView::renderSchematic(const std::vector& equip if (busIt != g_expandedBusGroupByPortId.end() && ImGui::MenuItem("Collapse Bus")) g_expandedBuses.erase(busIt->second); } else if (occIt != g_occInfoByShapeId.end()) { - if (occIt->second.sourceLoc.has_value()) { - if (ImGui::MenuItem("Show RTL Source") && g_provider) { - const auto& loc = *occIt->second.sourceLoc; - json req; - req["request"] = "load_source"; - req["file"] = loc.file; - req["line"] = loc.line; - g_provider->send(req.dump()); - } + if (occIt->second.hasSources && ImGui::MenuItem("Show Sources") && g_provider) { + json req; + req["request"] = "get_sources"; + req["kind"] = "instance"; + writePath(req, occIt->second.path); + g_provider->send(req.dump()); } if (ImGui::MenuItem("Show Properties") && g_provider) { json req; @@ -778,7 +772,7 @@ void EquipotentialView::renderSchematic(const std::vector& equip PrimitiveType primitiveType = PrimitiveType::Unknown; bool hasInstances = false; std::optional bitTermCount; - std::optional sourceLoc; + bool hasSources = false; InstancePath path; std::vector pathModels; std::vector pathIds; // pathIds(path), for pin requests @@ -831,7 +825,7 @@ void EquipotentialView::renderSchematic(const std::vector& equip mi.primitiveType = item.primitiveType; mi.hasInstances = item.hasInstances; mi.bitTermCount = item.bitTermCount; - mi.sourceLoc = item.sourceLoc; + mi.hasSources = item.hasSources; mi.path = item.path; mi.pathModels = item.pathModels; mi.pathIds = item.pathIds; @@ -881,7 +875,7 @@ void EquipotentialView::renderSchematic(const std::vector& equip mi.primitiveType = start.primitiveType; mi.hasInstances = start.hasInstances; mi.bitTermCount = start.ports.size(); - mi.sourceLoc = start.sourceLoc; + mi.hasSources = start.hasSources; mi.path = start.path; mi.pathModels = start.pathModels; mi.pathIds = start.pathIds; @@ -909,7 +903,7 @@ void EquipotentialView::renderSchematic(const std::vector& equip if (inst.diagOutline == 0 && traceIdsByInstance[key].size() >= 2) inst.diagOutline = TraceStore::convergenceColor(); inst.selected = SelectionStore::isSelected(key); - g_occInfoByShapeId[inst.id] = { key, mi.designRef, mi.sourceLoc }; + g_occInfoByShapeId[inst.id] = { key, mi.designRef, mi.hasSources }; pathToInstId[key] = inst.id; // Hierarchy embedding: this box's model has sub-instances, so it can @@ -1150,7 +1144,7 @@ void EquipotentialView::renderSchematic(const std::vector& equip for (auto& f : frames) { f.shape.diagOutline = DiagnosisStore::instanceColor(f.shape.path); f.shape.selected = SelectionStore::isSelected(f.shape.path); - g_occInfoByShapeId[f.shape.id] = { f.shape.path, DesignRef{}, std::nullopt }; + g_occInfoByShapeId[f.shape.id] = { f.shape.path, DesignRef{}, true }; frameShapes.push_back(std::move(f.shape)); } // Parent-first order at the front: drawn under everything, and reverse diff --git a/src/EquipotentialView.h b/src/EquipotentialView.h index 43f6d83..0e66d03 100644 --- a/src/EquipotentialView.h +++ b/src/EquipotentialView.h @@ -27,6 +27,7 @@ class EquipotentialView { DesignRef designRef{}; PrimitiveType primitiveType = PrimitiveType::Unknown; bool hasInstances = false; // can itself be expanded further + bool hasSources = false; // has source references (get_sources) }; // One endpoint of an internal net: a bit term on a specific sub-instance // (instChildId set), or the parent instance's own boundary port @@ -56,7 +57,7 @@ class EquipotentialView { DesignRef designRef{}; // the instance's model PrimitiveType primitiveType = PrimitiveType::Unknown; bool hasInstances = false; - std::optional sourceLoc; + bool hasSources = false; std::vector ports; // every pin, bus bits expanded }; diff --git a/src/LocalSNLProvider.cpp b/src/LocalSNLProvider.cpp index b610adb..4f77993 100644 --- a/src/LocalSNLProvider.cpp +++ b/src/LocalSNLProvider.cpp @@ -7,9 +7,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -235,9 +237,6 @@ static json bitTermJson(const SNLBitTerm* bt) { return t; } -// RTL source location for an elaborated object, if naja has one recorded -// (SNLRTLInfos -- populated today only by the SystemVerilog/slang frontend). -// Returns JSON null when unavailable, which the client treats as "no link". // Every pin of a design, bus terms expanded bit by bit ("data[3]", with // "bit" set) so each gets its own schematic pin. static json allBitTermsJson(const SNLDesign* design) { @@ -267,20 +266,90 @@ static json allBitTermsJson(const SNLDesign* design) { return terms; } -static json sourceLocJson(const SNLDesignObject* obj) { - if (!obj || !obj->hasRTLInfos()) return nullptr; - auto* rtl = obj->getRTLInfos(); - if (!rtl->hasSourceLoc()) return nullptr; - const auto& loc = *rtl->getSourceLoc(); +// --------------------------------------------------------------------------- +// Source references (see docs/source_information.md) +// --------------------------------------------------------------------------- +// naja keeps, per object, an optional *declaration* (where the object is +// written in the loaded input: a gate's line in the netlist, an instance's +// instantiation, a design's definition) and ordered *origins* (where it came +// from upstream, e.g. Yosys `src` RTL lines), each qualified by provider, +// language and representation ("" = unknown, never guessed). Older frontends +// only fill the unclassified legacy slot (getSourceLoc()), sent as role +// "legacy" when the object has no typed reference. Mirrors protocol.py's +// object_sources(). + +static json sourceRangeJson(const SNLSourceLoc& loc) { return json{ {"file", loc.file.getString()}, {"line", loc.line}, - {"end_line", loc.endLine}, {"column", loc.column}, + {"end_line", loc.endLine}, {"end_column", loc.endColumn} }; } +static json sourceReferenceJson(const std::string& role, const SNLSourceReference& ref, + std::optional index = std::nullopt) { + json entry = sourceRangeJson(ref.range); + entry["role"] = role; + entry["provider"] = ref.provider.getString(); + entry["language"] = ref.language.getString(); + entry["representation"] = ref.representation.getString(); + if (index) entry["index"] = *index; + return entry; +} + +// The source entries of one design object or design: its declaration, its +// origins in order, or else its legacy location. +template +static json objectSources(const T* obj) { + json entries = json::array(); + auto* infos = obj ? obj->getRTLInfos() : nullptr; + if (!infos) return entries; + if (const auto& declaration = infos->getSourceDeclaration()) + entries.push_back(sourceReferenceJson("declaration", *declaration)); + const auto& origins = infos->getSourceOrigins(); + for (size_t i = 0; i < origins.size(); ++i) + entries.push_back(sourceReferenceJson("origin", origins[i], i)); + if (entries.empty() && infos->hasSourceLoc()) { + json entry = sourceRangeJson(*infos->getSourceLoc()); + entry["role"] = "legacy"; + entry["provider"] = entry["language"] = entry["representation"] = ""; + entries.push_back(std::move(entry)); + } + return entries; +} + +template +static bool hasSources(const T* obj) { + auto* infos = obj ? obj->getRTLInfos() : nullptr; + return infos && (infos->hasSourceReferences() || infos->hasSourceLoc()); +} + +// An instance's has_sources flag: its own references, or its model's +// definition (listed after them as "model_declaration"). +static bool instanceHasSources(const SNLInstance* inst) { + auto* model = inst->getModel(); + auto* modelInfos = model ? model->getRTLInfos() : nullptr; + return hasSources(inst) || (modelInfos && modelInfos->getSourceDeclaration()); +} + +// A bus row offers its sources if the bus or any of its bits has some (a bit +// row inherits the flag; a bit with none of its own shows the bus's). +template +static bool busHasSources(const Bus* bus) { + if (hasSources(bus)) return true; + for (auto* bit : bus->getBits()) + if (hasSources(bit)) return true; + return false; +} + +// Only sent when set, like "clock". +static json& withSources(json& entry, bool flag) { + if (flag) entry["has_sources"] = true; + return entry; +} + // Find a design matching {dbId, libId, designId}. static SNLDesign* findDesign(unsigned dbId, unsigned libId, unsigned designId) { return NLUniverse::get()->getSNLDesign(NLID::DesignReference( @@ -336,6 +405,7 @@ void LocalSNLProvider::send(const std::string& msg) { handleRequest(msg); } NLDB* LocalSNLProvider::freshDB() { db_ = nullptr; // old DB remains in universe; SNL has no public single-DB removal API + loadedAt_ = std::filesystem::file_time_type::clock::now(); // a design is being loaded return NLDB::create(NLUniverse::get()); } @@ -401,7 +471,8 @@ void LocalSNLProvider::loadSNL(const std::string& path) { void LocalSNLProvider::loadVerilog( const std::vector& verilogFiles, - const std::vector& libertyFiles) + const std::vector& libertyFiles, + bool yosysSourceAttributes) { Console::Log("LocalSNLProvider: loading Verilog (" + std::to_string(verilogFiles.size()) + " files, " + @@ -419,6 +490,7 @@ void LocalSNLProvider::loadVerilog( auto* userLib = NLLibrary::create(db_, NLName("work")); SNLVRLConstructor::Config cfg; cfg.blackboxUnknownModules_ = !libertyFiles.empty(); + cfg.yosysSourceAttributes_ = yosysSourceAttributes; SNLVRLConstructor vrlCtor(userLib); vrlCtor.config_ = cfg; std::vector paths(verilogFiles.begin(), verilogFiles.end()); @@ -548,6 +620,8 @@ void LocalSNLProvider::handleRequest(const std::string& jsonRequest) { msgCb_(buildExpandInstanceTermsResponse(req)); } else if (request == "load_instance_internals") { msgCb_(buildInstanceInternalsResponse(req)); + } else if (request == "get_sources") { + msgCb_(buildSourcesResponse(req)); } else if (request == "load_source") { msgCb_(buildSourceResponse(req)); } else if (request == "get_properties") { @@ -589,6 +663,7 @@ std::string LocalSNLProvider::buildRootResponse() const { {"has_instances", !top->getNonPrimitiveInstances().empty()}, {"has_nets", hasVisibleNets(top)} }; + withSources(root, hasSources(top)); } return json{{"response", "root_response"}, {"root", root}}.dump(); } @@ -628,9 +703,9 @@ std::string LocalSNLProvider::buildInstancesResponse( {"has_terms", model && !model->getTerms().empty()}, {"has_primitives", model && hasVisiblePrimitiveInstances(model)}, {"has_instances", model && !model->getNonPrimitiveInstances().empty()}, - {"has_nets", model && hasVisibleNets(model)}, - {"source_loc", sourceLocJson(inst)} + {"has_nets", model && hasVisibleNets(model)} }); + withSources(children.back(), instanceHasSources(inst)); } } @@ -666,9 +741,10 @@ std::string LocalSNLProvider::buildTermsResponse( {"msb", static_cast(bt->getMSB())}, {"lsb", static_cast(bt->getLSB())} }; - children.push_back(std::move(t)); + children.push_back(std::move(withSources(t, busHasSources(bt)))); } else if (auto* st = dynamic_cast(term)) { - children.push_back(bitTermJson(st)); + json t = bitTermJson(st); + children.push_back(std::move(withSources(t, hasSources(st)))); } } } @@ -704,9 +780,10 @@ std::string LocalSNLProvider::buildNetsResponse( {"msb", static_cast(bn->getMSB())}, {"lsb", static_cast(bn->getLSB())} }; - children.push_back(std::move(n)); + children.push_back(std::move(withSources(n, busHasSources(bn)))); } else if (auto* bit = dynamic_cast(net)) { - children.push_back({{"name", bit->getName().getString()}}); + json n = {{"name", bit->getName().getString()}}; + children.push_back(std::move(withSources(n, hasSources(bit)))); } } } @@ -767,7 +844,7 @@ static json equipotentialJson(const SNLEquipotential& equi, // screen (solid box) or only a subset (dashed, double-click to expand). if (auto* model = theInst->getModel()) entry["bit_term_count"] = model->getBitTerms().size(); - entry["source_loc"] = sourceLocJson(theInst); + withSources(entry, instanceHasSources(theInst)); occs.push_back(std::move(entry)); } return json{{"terms", std::move(terms)}, {"occurrences", std::move(occs)}}; @@ -1020,9 +1097,9 @@ std::string LocalSNLProvider::buildResolveInstanceResponse(const json& req) cons }}, {"primitive_type", toString(getPrimitiveType(design))}, {"has_instances", hasAnySubInstances(design)}, - {"source_loc", sourceLocJson(instance)}, {"terms", allBitTermsJson(design)} }; + withSources(reply["instance"], instanceHasSources(instance)); return reply.dump(); } @@ -1064,9 +1141,9 @@ std::string LocalSNLProvider::buildInstanceInternalsResponse(const json& req) co }}, {"has_terms", sub && !sub->getTerms().empty()}, {"has_primitives", sub && hasVisiblePrimitiveInstances(sub)}, - {"has_instances", sub && !sub->getNonPrimitiveInstances().empty()}, - {"source_loc", sourceLocJson(inst)} + {"has_instances", sub && !sub->getNonPrimitiveInstances().empty()} }); + withSources(children.back(), instanceHasSources(inst)); } for (auto* inst : model->getPrimitiveInstances()) { auto* sub = inst->getModel(); @@ -1083,9 +1160,9 @@ std::string LocalSNLProvider::buildInstanceInternalsResponse(const json& req) co }}, {"has_terms", sub && !sub->getTerms().empty()}, {"has_primitives", sub && hasVisiblePrimitiveInstances(sub)}, - {"has_instances", sub && !sub->getNonPrimitiveInstances().empty()}, - {"source_loc", sourceLocJson(inst)} + {"has_instances", sub && !sub->getNonPrimitiveInstances().empty()} }); + withSources(children.back(), instanceHasSources(inst)); } // Internal nets: split each net's components into sub-instance pins @@ -1132,31 +1209,210 @@ std::string LocalSNLProvider::buildInstanceInternalsResponse(const json& req) co }.dump(); } -// Fetches the raw text of an RTL source file named by a source_loc (see -// sourceLocJson() above), so the client can display it without needing -// filesystem access of its own -- the WASM/browser build has none, so this -// goes through the same provider abstraction as everything else rather than -// having native mode read the file directly. +// The source entries of the object a get_sources/load_source request names +// -- same addressing as get_properties: "kind" (instance, term or net), the +// (containing) instance by "id_path"/"path", "terminal"/"net" and an optional +// "bit". `entries` is empty when the object doesn't resolve or has no source +// information. +// +// An instance lists its own references (its instantiation, its origins) and +// then its model's definition ("model_declaration"); the top design ([]) its +// definition. A bus bit with no reference of its own lists its bus's. +// Mirrors protocol.py's request_sources(). +static void requestSources(SNLDesign* top, const json& req, + std::string& subject, json& entries) { + subject.clear(); + entries = json::array(); + if (!top) return; + auto resolved = resolveRequestPath(top, req); + if (!resolved.ok) return; + SNLDesign* design = resolved.design; + const std::string at = displayChain(resolved.chain); + const std::string kind = req.value("kind", std::string("instance")); + if (kind == "term" || kind == "net") { + const json name = req.value(kind == "term" ? "terminal" : "net", json("")); + if (!name.is_string()) return; + const std::string n = name.get(); + subject = at.empty() ? n : at + "/" + n; + if (n.empty() || !design) return; + const bool hasBit = req.contains("bit") && !req["bit"].is_null(); + if (hasBit && (!req["bit"].is_number_integer())) return; + if (kind == "term") { + auto* term = design->getTerm(NLName(n)); + if (!term) return; + if (!hasBit) { entries = objectSources(term); return; } + auto* bus = dynamic_cast(term); + auto* bit = bus ? bus->getBit(static_cast(req["bit"].get())) : nullptr; + if (!bit) return; + subject += "[" + std::to_string(bit->getBit()) + "]"; + entries = objectSources(bit); + if (entries.empty()) entries = objectSources(bus); + } else { + auto* net = design->getNet(NLName(n)); + if (!net) return; + if (!hasBit) { entries = objectSources(net); return; } + auto* bus = dynamic_cast(net); + auto* bit = bus ? bus->getBit(static_cast(req["bit"].get())) : nullptr; + if (!bit) return; + subject += "[" + std::to_string(bit->getBit()) + "]"; + entries = objectSources(bit); + if (entries.empty()) entries = objectSources(bus); + } + return; + } + auto* instance = resolved.instance(); + if (!instance) { + subject = designName(top); + entries = objectSources(top); + return; + } + subject = at; + entries = objectSources(instance); + auto* modelInfos = design ? design->getRTLInfos() : nullptr; + if (modelInfos && modelInfos->getSourceDeclaration()) + entries.push_back(sourceReferenceJson("model_declaration", *modelInfos->getSourceDeclaration())); +} + +// Addressing keys of a get_sources/load_source request, echoed in replies so +// the viewer can name the same object again (load_source from the list). +static const char* const kSourceAddressKeys[] = {"kind", "id_path", "path", "terminal", "net", "bit"}; + +std::string LocalSNLProvider::buildSourcesResponse(const json& req) const { + std::string subject; + json entries; + requestSources(db_ ? db_->getTopDesign() : nullptr, req, subject, entries); + json reply = { + {"response", "sources_response"}, + {"subject", subject}, + {"sources", entries} + }; + for (const char* key : kSourceAddressKeys) + if (req.contains(key)) reply[key] = req[key]; + return reply.dump(); +} + +// A source file is sent whole up to this size; past it (a large gate-level +// netlist, typically) only the lines around the reference are, so a click +// doesn't copy hundreds of megabytes into the viewer. Same values as +// protocol.py's MAX_SOURCE_BYTES/SOURCE_CONTEXT_LINES. +static constexpr std::uintmax_t kMaxSourceBytes = 4 * 1024 * 1024; +static constexpr std::uint64_t kSourceContextLines = 500; + +void LocalSNLProvider::addSourceRoot(const std::string& dir) { + sourceRoots_.push_back(dir); +} + +void LocalSNLProvider::addSourceMap(const std::string& from, const std::string& to) { + sourceMaps_.emplace_back(from, to); +} + +// `file` with a leading `prefix` replaced, at a path boundary only (/a/syn +// maps /a/syn/x.v, not /a/synth/x.v); nullopt if it doesn't start so. +static std::optional mapPrefix(const std::string& file, const std::string& prefix, + const std::string& replacement) { + if (prefix.empty() || file.compare(0, prefix.size(), prefix) != 0) return std::nullopt; + const std::string rest = file.substr(prefix.size()); + const bool boundary = rest.empty() || prefix.back() == '/' || prefix.back() == '\\' || + rest.front() == '/' || rest.front() == '\\'; + if (!boundary) return std::nullopt; + return replacement + rest; +} + +// Where to read a referenced source file. First the path through each source +// map whose prefix it starts with, in order, if a file is there; then an +// absolute path as is; a relative one under the first source root holding +// it, else relative to the current directory. "" if no file is there. Paths +// are opaque producer strings (naja's contract): nothing is canonicalized, +// prefixes compare as written. Mirrors protocol.py's resolve_source_file(). +std::string LocalSNLProvider::resolveSourceFile(const std::string& file) const { + namespace fs = std::filesystem; + std::error_code ec; + if (file.empty()) return ""; + for (const auto& [prefix, replacement] : sourceMaps_) { + auto mapped = mapPrefix(file, prefix, replacement); + if (mapped && fs::is_regular_file(*mapped, ec)) return *mapped; + } + fs::path p(file); + if (p.is_absolute()) return fs::is_regular_file(p, ec) ? file : ""; + for (const auto& root : sourceRoots_) { + fs::path candidate = fs::path(root) / p; + if (fs::is_regular_file(candidate, ec)) return candidate.string(); + } + return fs::is_regular_file(p, ec) ? file : ""; +} + +// The file of one source entry of a design object: the request names the +// object as get_sources does, plus the entry's "role" (and "index" for an +// origin). Only files the design references can be read this way -- the +// viewer never names a path itself. The file goes through the provider (not +// read by the viewer directly) because the WASM build has no filesystem. std::string LocalSNLProvider::buildSourceResponse(const json& req) const { - std::string file = req.value("file", std::string("")); - int line = req.value("line", 0); + std::string subject; + json entries; + requestSources(db_ ? db_->getTopDesign() : nullptr, req, subject, entries); + const json role = req.value("role", json()); + const json index = req.value("index", json()); + json reply = { + {"response", "source_response"}, + {"found", false}, + {"text", ""}, + {"first_line", 1}, + {"truncated", false} + }; + const json* entry = nullptr; + for (const auto& e : entries) { + if (e["role"] == role && (role != "origin" || e.value("index", json()) == index)) { + entry = &e; + break; + } + } + if (!entry) { + Console::Error("load_source: no such source reference"); + return reply.dump(); + } + for (auto it = entry->begin(); it != entry->end(); ++it) reply[it.key()] = it.value(); - std::ifstream ifs(file); - bool found = static_cast(ifs); + const std::string file = (*entry)["file"].get(); + const std::string path = resolveSourceFile(file); + std::ifstream ifs(path, std::ios::binary); + if (path.empty() || !ifs) { + Console::Error("Source file not found: " + file); + return reply.dump(); + } + std::error_code ec; + const auto size = std::filesystem::file_size(path, ec); std::string text; - if (found) { + std::uint64_t firstLine = 1; + bool truncated = false; + if (!ec && size <= kMaxSourceBytes) { std::ostringstream ss; ss << ifs.rdbuf(); text = ss.str(); + } else { + const std::uint64_t line = (*entry)["line"].get(); + const std::uint64_t endLine = (*entry)["end_line"].get(); + firstLine = line > kSourceContextLines ? line - kSourceContextLines : 1; + const std::uint64_t lastLine = std::max(line, endLine) + kSourceContextLines; + std::string lineText; + for (std::uint64_t number = 1; number <= lastLine && std::getline(ifs, lineText); ++number) { + if (number < firstLine) continue; + text += lineText; + if (!ifs.eof()) text += '\n'; + } + truncated = true; } - - return json{ - {"response", "source_response"}, - {"file", file}, - {"line", line}, - {"found", found}, - {"text", text} - }.dump(); + reply["found"] = true; + // Where the host read it, for a viewer on the same machine to open it in + // an editor. "stale": the file changed after the design was loaded, so the + // reference's lines may not match it any more. + reply["path"] = std::filesystem::absolute(path).string(); + const auto modified = std::filesystem::last_write_time(path, ec); + reply["stale"] = !ec && modified > loadedAt_; + reply["text"] = std::move(text); + reply["first_line"] = firstLine; + reply["truncated"] = truncated; + // Undecodable bytes are replaced (as protocol.py does), not thrown on. + return reply.dump(-1, ' ', false, json::error_handler_t::replace); } // General name/value property inspector for whatever object the UI asks diff --git a/src/LocalSNLProvider.h b/src/LocalSNLProvider.h index f5f5e42..1bb4094 100644 --- a/src/LocalSNLProvider.h +++ b/src/LocalSNLProvider.h @@ -30,8 +30,12 @@ class LocalSNLProvider : public INetlistProvider { void loadSNL(const std::string& path); // Load structural Verilog. Liberty files define the primitive cell library. + // `yosysSourceAttributes` decodes Yosys (* src *) attributes into source + // origins (naja only does it when asked: nothing establishes Yosys + // provenance otherwise). void loadVerilog(const std::vector& verilogFiles, - const std::vector& libertyFiles); + const std::vector& libertyFiles, + bool yosysSourceAttributes = false); // Load SystemVerilog via slang. // Each entry in sources is either a .sv/.v file or a .f/.flist file; @@ -51,9 +55,22 @@ class LocalSNLProvider : public INetlistProvider { std::optional termRequest(const nlohmann::json& spec, std::string& error) const; + // A directory relative source file references are looked up in, after + // those added before and before the current directory (see + // resolveSourceFile()). + void addSourceRoot(const std::string& dir); + // A path prefix rewrite for source files recorded on another machine + // (a synthesis server's paths for a local checkout): a referenced path + // starting with `from`, at a path boundary, is tried with `to` instead, + // first (see resolveSourceFile()). + void addSourceMap(const std::string& from, const std::string& to); + // Serve an already-built DB (e.g. one a test builds with the naja API); // its top design is the design shown. - void setDB(naja::NL::NLDB* db) { db_ = db; } + void setDB(naja::NL::NLDB* db) { + db_ = db; + loadedAt_ = std::filesystem::file_time_type::clock::now(); + } private: std::function openCb_; @@ -62,6 +79,11 @@ class LocalSNLProvider : public INetlistProvider { std::function errCb_; naja::NL::NLDB* db_ {nullptr}; + std::vector sourceRoots_; + // When the design was loaded (freshDB(), setDB()): naja recorded its + // source ranges then, so a source file modified later is "stale". + std::filesystem::file_time_type loadedAt_ {}; + std::vector> sourceMaps_; // Reset the SNL universe to a clean state and return a fresh NLDB. naja::NL::NLDB* freshDB(); @@ -86,7 +108,9 @@ class LocalSNLProvider : public INetlistProvider { std::string buildTraceDriverResponse(const nlohmann::json& req) const; std::string buildExpandInstanceTermsResponse(const nlohmann::json& req) const; std::string buildInstanceInternalsResponse(const nlohmann::json& req) const; + std::string buildSourcesResponse(const nlohmann::json& req) const; std::string buildSourceResponse(const nlohmann::json& req) const; + std::string resolveSourceFile(const std::string& file) const; std::string buildPropertiesResponse(const nlohmann::json& req) const; std::string buildResolveInstanceResponse(const nlohmann::json& req) const; }; diff --git a/src/NetlistTree.cpp b/src/NetlistTree.cpp index ddfe210..b5ae6ae 100644 --- a/src/NetlistTree.cpp +++ b/src/NetlistTree.cpp @@ -17,6 +17,20 @@ void viewModeTooltip(bool clearView) { : "Keeps what the schematic already shows"); } +// "Show Sources" (when the provider flagged the object) and "Show +// Properties" for the object `address` names (kind, path/id_path, +// terminal/net, bit): get_sources and get_properties take the same address. +void inspectMenuItems(INetlistProvider* provider, json address, bool hasSources) { + if (hasSources && ImGui::MenuItem("Show Sources")) { + address["request"] = "get_sources"; + provider->send(address.dump()); + } + if (ImGui::MenuItem("Show Properties")) { + address["request"] = "get_properties"; + provider->send(address.dump()); + } +} + } // namespace void NetlistTree::createRootNode( @@ -26,8 +40,8 @@ void NetlistTree::createRootNode( bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc) { - root_ = new NetlistTreeInstanceNode(this, name, design_ref, hasTerms, hasPrimitives, hasInstances, hasNets, sourceLoc); + bool hasSources) { + root_ = new NetlistTreeInstanceNode(this, name, design_ref, hasTerms, hasPrimitives, hasInstances, hasNets, hasSources); } void NetlistTree::render() { @@ -41,7 +55,20 @@ void NetlistTree::render() { } advanceReveal(); if (root_) { + // The selected row must stand out more than the row under the mouse. + // The default theme does the opposite (hovered at 80% opacity, selected + // at 31%), so after a context-menu click the row under the closed menu + // looked selected. Here the selection takes the strong accent and hover + // a faint one, both from the current theme. + const ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 accent = style.Colors[ImGuiCol_HeaderHovered]; + ImVec4 faint = accent; + accent.w = 0.85f; + faint.w = 0.22f; + ImGui::PushStyleColor(ImGuiCol_Header, accent); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, faint); root_->render(); + ImGui::PopStyleColor(2); } else { ImGui::Text("Loading Root Node..."); } @@ -216,62 +243,41 @@ void NetlistTreeNode::render() { isBusBit() ? std::vector{getBusBit()} : std::vector{}, traceLabel(getInstancePath(), label), clearView); } - if (ImGui::MenuItem("Show Properties")) { - json req; - req["request"] = "get_properties"; - req["kind"] = "term"; - writePath(req, getInstancePath()); - req["terminal"] = getTermBaseName(); - if (isBusBit()) req["bit"] = getBusBit(); - getTree()->getProvider()->send(req.dump()); - } + json req; + req["kind"] = "term"; + writePath(req, getInstancePath()); + req["terminal"] = getTermBaseName(); + if (isBusBit()) req["bit"] = getBusBit(); + inspectMenuItems(getTree()->getProvider(), req, hasSources()); ImGui::EndPopup(); } } else if (isBus()) { if (ImGui::BeginPopupContextItem()) { // No bus-level Show Equipotential or Trace to Driver for now: both // work one bit at a time, from the bit rows. - if (ImGui::MenuItem("Show Properties")) { - json req; - req["request"] = "get_properties"; - req["kind"] = "term"; - writePath(req, getInstancePath()); - req["terminal"] = getTermBaseName(); - getTree()->getProvider()->send(req.dump()); - } + json req; + req["kind"] = "term"; + writePath(req, getInstancePath()); + req["terminal"] = getTermBaseName(); + inspectMenuItems(getTree()->getProvider(), req, hasSources()); ImGui::EndPopup(); } } else if (isBitNet() || isBusNet()) { if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("Show Properties")) { - json req; - req["request"] = "get_properties"; - req["kind"] = "net"; - writePath(req, getInstancePath()); - req["net"] = getNetBaseName(); - if (isBusBit()) req["bit"] = getBusBit(); - getTree()->getProvider()->send(req.dump()); - } + json req; + req["kind"] = "net"; + writePath(req, getInstancePath()); + req["net"] = getNetBaseName(); + if (isBusBit()) req["bit"] = getBusBit(); + inspectMenuItems(getTree()->getProvider(), req, hasSources()); ImGui::EndPopup(); } } else if (isInstanceNode()) { if (ImGui::BeginPopupContextItem()) { - if (auto loc = getSourceLoc()) { - if (ImGui::MenuItem("Show RTL Source")) { - json req; - req["request"] = "load_source"; - req["file"] = loc->file; - req["line"] = loc->line; - getTree()->getProvider()->send(req.dump()); - } - } - if (ImGui::MenuItem("Show Properties")) { - json req; - req["request"] = "get_properties"; - req["kind"] = "instance"; - writePath(req, getInstancePath()); - getTree()->getProvider()->send(req.dump()); - } + json req; + req["kind"] = "instance"; + writePath(req, getInstancePath()); + inspectMenuItems(getTree()->getProvider(), req, hasSources()); ImGui::EndPopup(); } } @@ -303,13 +309,13 @@ NetlistTreeInstanceNode::NetlistTreeInstanceNode( bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc): + bool hasSources): NetlistTreeNode(tree), isRoot_(true), name_(name), childID_(0),designRef_(designRef), hasTerms_(hasTerms), hasPrimitives_(hasPrimitives), hasInstances_(hasInstances), hasNets_(hasNets), - sourceLoc_(sourceLoc) + hasSources_(hasSources) {} NetlistTreeInstanceNode::NetlistTreeInstanceNode( @@ -322,13 +328,13 @@ NetlistTreeInstanceNode::NetlistTreeInstanceNode( bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc): + bool hasSources): NetlistTreeNode(parent), isRoot_(false), name_(name), modelName_(modelName), childID_(childID), designRef_(designRef), hasTerms_(hasTerms), hasPrimitives_(hasPrimitives), hasInstances_(hasInstances), hasNets_(hasNets), - sourceLoc_(sourceLoc) + hasSources_(hasSources) {} void NetlistTreeInstanceNode::getPath(NetlistTree::Path& path) const { @@ -425,8 +431,10 @@ NetlistTreeTermNode::NetlistTreeTermNode( unsigned childID, Direction direction, std::optional msb, - std::optional lsb): - NetlistTreeNode(parent), name_(name), childID_(childID), direction_(direction), msb_(msb), lsb_(lsb) { + std::optional lsb, + bool hasSources): + NetlistTreeNode(parent), name_(name), childID_(childID), direction_(direction), msb_(msb), lsb_(lsb), + hasSources_(hasSources) { if (not msb_.has_value() or not lsb_.has_value()) { //scalar children_ = new Children(); } @@ -514,8 +522,9 @@ NetlistTreeNetNode::NetlistTreeNetNode( NetlistTreeNode* parent, const std::string& name, std::optional msb, - std::optional lsb): - NetlistTreeNode(parent), name_(name), msb_(msb), lsb_(lsb) { + std::optional lsb, + bool hasSources): + NetlistTreeNode(parent), name_(name), msb_(msb), lsb_(lsb), hasSources_(hasSources) { if (not msb_.has_value() or not lsb_.has_value()) { //scalar children_ = new Children(); } @@ -602,17 +611,19 @@ void NetlistTreeNode::createTermNode( unsigned childID, Direction direction, std::optional msb, - std::optional lsb) { + std::optional lsb, + bool hasSources) { //this should be a group node of type Terms - children_->push_back(new NetlistTreeTermNode(this, name, childID, direction, msb, lsb)); + children_->push_back(new NetlistTreeTermNode(this, name, childID, direction, msb, lsb, hasSources)); } void NetlistTreeNode::createNetNode( const std::string& name, std::optional msb, - std::optional lsb) { + std::optional lsb, + bool hasSources) { //this should be a group node of type Nets - children_->push_back(new NetlistTreeNetNode(this, name, msb, lsb)); + children_->push_back(new NetlistTreeNetNode(this, name, msb, lsb, hasSources)); } void NetlistTreeNode::createInstanceNode( @@ -624,12 +635,12 @@ void NetlistTreeNode::createInstanceNode( bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc) { + bool hasSources) { auto node = new NetlistTreeInstanceNode( this, name, modelName, childID, design_ref, - hasTerms, hasPrimitives, hasInstances, hasNets, sourceLoc); + hasTerms, hasPrimitives, hasInstances, hasNets, hasSources); children_->push_back(node); } diff --git a/src/NetlistTree.h b/src/NetlistTree.h index c9f1ed8..807772f 100644 --- a/src/NetlistTree.h +++ b/src/NetlistTree.h @@ -32,7 +32,7 @@ class NetlistTree { bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc = std::nullopt); + bool hasSources = false); NetlistTreeNode* getNode(unsigned id) const; NetlistTreeNode* getRoot() const { return root_; } @@ -103,11 +103,13 @@ class NetlistTreeNode { unsigned childID, Direction direction, std::optional msb, - std::optional lsb); + std::optional lsb, + bool hasSources = false); void createNetNode( const std::string& name, std::optional msb, - std::optional lsb); + std::optional lsb, + bool hasSources = false); void createInstanceNode( const std::string& name, const std::string& modelName, @@ -117,7 +119,7 @@ class NetlistTreeNode { bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc = std::nullopt); + bool hasSources = false); void createChildren(); bool hasChildren() const { return children_ != nullptr; } virtual NetlistTreeInstanceNode* getInstanceNode() const; @@ -184,11 +186,10 @@ class NetlistTreeNode { // Diagnosis items attached to this node's path, if any (only // NetlistTreeInstanceNode currently reports these). virtual std::vector getDiagnostics() const { return {}; } - // RTL source location, if naja recorded one for this node (only - // NetlistTreeInstanceNode currently reports these; populated today only - // for SystemVerilog-loaded designs). Drives the "Show RTL Source" - // context-menu entry. - virtual std::optional getSourceLoc() const { return std::nullopt; } + // Whether the provider has source references for this node's object + // (has_sources; a bus bit row inherits its bus's flag). Drives the "Show + // Sources" context-menu entry, which sends get_sources. + virtual bool hasSources() const { return false; } protected: NetlistTreeNode(NetlistTree* tree); NetlistTreeNode(NetlistTreeNode* parent); @@ -211,7 +212,7 @@ class NetlistTreeInstanceNode : public NetlistTreeNode { bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc = std::nullopt + bool hasSources = false ); NetlistTreeInstanceNode( NetlistTreeNode* parent, @@ -223,7 +224,7 @@ class NetlistTreeInstanceNode : public NetlistTreeNode { bool hasPrimitives, bool hasInstances, bool hasNets, - const std::optional& sourceLoc = std::nullopt + bool hasSources = false ); virtual void expand() override; @@ -241,7 +242,7 @@ class NetlistTreeInstanceNode : public NetlistTreeNode { } virtual ImU32 getColor() const override; virtual std::vector getDiagnostics() const override; - virtual std::optional getSourceLoc() const override { return sourceLoc_; } + virtual bool hasSources() const override { return hasSources_; } virtual bool isInstanceNode() const override { return true; } const std::string& getName() const { return name_; } private: @@ -254,7 +255,7 @@ class NetlistTreeInstanceNode : public NetlistTreeNode { bool hasPrimitives_ {false}; bool hasInstances_ {false}; bool hasNets_ {false}; - std::optional sourceLoc_ {}; + bool hasSources_ {false}; }; class NetlistTreeGroupNode : public NetlistTreeNode { @@ -284,10 +285,12 @@ class NetlistTreeTermNode : public NetlistTreeNode { unsigned childID, Direction direction, std::optional msb, - std::optional lsb); + std::optional lsb, + bool hasSources = false); virtual std::string getLabel() const override; virtual ImU32 getColor() const override; + virtual bool hasSources() const override { return hasSources_; } virtual void expand() override; virtual bool isLeaf() const override { return isBitTerm(); @@ -309,6 +312,7 @@ class NetlistTreeTermNode : public NetlistTreeNode { Direction direction_; std::optional msb_; std::optional lsb_; + bool hasSources_; }; class NetlistTreeBusTermBitNode : public NetlistTreeNode { @@ -334,6 +338,7 @@ class NetlistTreeBusTermBitNode : public NetlistTreeNode { virtual std::string getTermBaseName() const override { return getParent()->getTermBaseName(); } + virtual bool hasSources() const override { return getParent()->hasSources(); } bool isTopTerm() const { return getInstanceNode()->isRoot(); } @@ -346,10 +351,12 @@ class NetlistTreeNetNode : public NetlistTreeNode { NetlistTreeNetNode(NetlistTreeNode* parent, const std::string& name, std::optional msb, - std::optional lsb); + std::optional lsb, + bool hasSources = false); virtual std::string getLabel() const override; virtual void expand() override; + virtual bool hasSources() const override { return hasSources_; } virtual bool isLeaf() const override { return isBitNet(); } @@ -366,6 +373,7 @@ class NetlistTreeNetNode : public NetlistTreeNode { std::string name_; std::optional msb_; std::optional lsb_; + bool hasSources_; }; class NetlistTreeBusNetBitNode : public NetlistTreeNode { @@ -388,6 +396,7 @@ class NetlistTreeBusNetBitNode : public NetlistTreeNode { virtual std::string getNetBaseName() const override { return getParent()->getNetBaseName(); } + virtual bool hasSources() const override { return getParent()->hasSources(); } private: int bit_; }; \ No newline at end of file diff --git a/src/SchematicLayout.cpp b/src/SchematicLayout.cpp index 3d59136..7cbd1af 100644 --- a/src/SchematicLayout.cpp +++ b/src/SchematicLayout.cpp @@ -32,7 +32,7 @@ void buildItems(const Equipotential* eq, item.pathIds = occ.pathIds; item.hasInstances = occ.has_instances; item.bitTermCount = occ.bit_term_count; - item.sourceLoc = occ.source_loc; + item.hasSources = occ.has_sources; item.path = occ.path; item.pathModels = occ.pathModels; item.direction = occ.term.direction; diff --git a/src/SchematicLayout.h b/src/SchematicLayout.h index 7693a42..a204073 100644 --- a/src/SchematicLayout.h +++ b/src/SchematicLayout.h @@ -193,8 +193,8 @@ struct Item { bool hasInstances = false; // Total bit-term count of the instance's model, if known. std::optional bitTermCount; - // RTL source location of the instance itself, if available. - std::optional sourceLoc; + // Whether the instance itself has source references (get_sources). + bool hasSources = false; // Instance occurrences only: per-segment instance names / model names of // the full hierarchical path (the last entry is the instance itself). // `path` is the instance's identity in the layout. diff --git a/src/SourceStore.cpp b/src/SourceStore.cpp index 3d22d0e..4cbb45d 100644 --- a/src/SourceStore.cpp +++ b/src/SourceStore.cpp @@ -1,16 +1,68 @@ #include "SourceStore.h" +#include + namespace { -std::string g_file; +std::string g_subject; +json g_address = json::object(); +std::vector g_references; +bool g_hasReferences = false; + +SourceRef g_shown; +std::string g_hostPath; std::vector g_lines; -int g_targetLine = 0; -bool g_hasSource = false; -bool g_pendingScroll = false; +std::uint64_t g_firstLine = 1; +bool g_truncated = false; +bool g_stale = false; +bool g_hasSource = false; +bool g_missing = false; +bool g_pendingScroll = false; + +bool sameReference(const SourceRef& a, const SourceRef& b) { + return a.role == b.role && a.index == b.index; +} + +// The reference's last line: its end line, or its first when that's unknown. +std::uint64_t lastLine(const SourceRef& ref) { + return std::max(ref.line, ref.endLine); +} } // namespace -void SourceStore::setSource(const std::string& file, int line, const std::string& text) { - g_file = file; - g_targetLine = line; +void SourceStore::setReferences(const std::string& subject, const json& address, + std::vector references) { + g_subject = subject; + g_address = address; + g_references = std::move(references); + g_hasReferences = true; +} + +bool SourceStore::hasReferences() { return g_hasReferences; } +const std::string& SourceStore::subject() { return g_subject; } +const std::vector& SourceStore::references() { return g_references; } + +json SourceStore::loadRequest(size_t i) { + json req = g_address; + req["request"] = "load_source"; + req["role"] = g_references.at(i).role; + if (g_references[i].index) req["index"] = *g_references[i].index; + return req; +} + +std::optional SourceStore::shownReference() { + if (!g_hasSource && !g_missing) return std::nullopt; + for (size_t i = 0; i < g_references.size(); ++i) + if (sameReference(g_references[i], g_shown) && g_references[i].file == g_shown.file) return i; + return std::nullopt; +} + +void SourceStore::setSource(const SourceRef& ref, const std::string& text, + std::uint64_t firstLine, bool truncated, + const std::string& hostPath, bool stale) { + g_shown = ref; + g_hostPath = hostPath; + g_stale = stale; + g_firstLine = firstLine ? firstLine : 1; + g_truncated = truncated; g_lines.clear(); size_t start = 0; @@ -27,24 +79,114 @@ void SourceStore::setSource(const std::string& file, int line, const std::string } g_hasSource = true; + g_missing = false; g_pendingScroll = true; } -void SourceStore::clear() { - g_file.clear(); +void SourceStore::setMissing(const SourceRef& ref) { + g_shown = ref; + g_hostPath.clear(); + g_stale = false; g_lines.clear(); - g_targetLine = 0; - g_hasSource = false; + g_firstLine = 1; + g_truncated = false; + g_hasSource = false; + g_missing = true; g_pendingScroll = false; } bool SourceStore::hasSource() { return g_hasSource; } -const std::string& SourceStore::file() { return g_file; } +const SourceRef& SourceStore::shown() { return g_shown; } +bool SourceStore::missing() { return g_missing; } +const std::string& SourceStore::file() { return g_shown.file; } +const std::string& SourceStore::hostPath() { return g_hostPath; } const std::vector& SourceStore::lines() { return g_lines; } -int SourceStore::targetLine() { return g_targetLine; } +std::uint64_t SourceStore::firstLine() { return g_firstLine; } +bool SourceStore::truncated() { return g_truncated; } +bool SourceStore::stale() { return g_stale; } +std::uint64_t SourceStore::targetLine() { return g_shown.line; } + +bool SourceStore::inRange(std::uint64_t lineNo) { + return g_shown.line && lineNo >= g_shown.line && lineNo <= lastLine(g_shown); +} + +std::optional> SourceStore::highlightBytes(size_t index) { + if (index >= g_lines.size()) return std::nullopt; + const std::uint64_t lineNo = g_firstLine + index; + if (!inRange(lineNo)) return std::nullopt; + const size_t length = g_lines[index].size(); + size_t begin = 0, end = length; + if (lineNo == g_shown.line && g_shown.column) + begin = std::min(g_shown.column - 1, length); + // The end column belongs to the end line, so only when that's known. + if (g_shown.endLine && lineNo == g_shown.endLine && g_shown.endColumn) + end = std::min(g_shown.endColumn, length); + if (end < begin) end = begin; + return std::make_pair(begin, end); +} + +size_t SourceStore::glyphIndex(const std::string& line, size_t byte) { + size_t glyphs = 0; + const size_t n = std::min(byte, line.size()); + for (size_t i = 0; i < n; ++i) + if ((static_cast(line[i]) & 0xC0) != 0x80) ++glyphs; // not a continuation byte + return glyphs; +} + +std::string SourceStore::editorCommand(const std::string& pattern, const std::string& path, + std::uint64_t line, std::uint64_t column) { + std::string quoted = "'"; + for (char c : path) quoted += c == '\'' ? std::string("'\\''") : std::string(1, c); + quoted += "'"; + const std::pair fields[] = { + {"{file}", quoted}, + {"{line}", std::to_string(line ? line : 1)}, + {"{col}", std::to_string(column ? column : 1)}, + }; + std::string out; + for (size_t i = 0; i < pattern.size();) { + bool replaced = false; + for (const auto& [key, value] : fields) { + if (pattern.compare(i, key.size(), key) == 0) { + out += value; + i += key.size(); + replaced = true; + break; + } + } + if (!replaced) out += pattern[i++]; + } + return out; +} + +std::string SourceStore::editorPattern(const std::string& configured, bool codeOnPath, + const std::string& bundledCode, + const std::string& systemOpener) { + if (!configured.empty()) return configured; + if (codeOnPath) return "code -g {file}:{line}:{col}"; + if (!bundledCode.empty()) + return editorCommand("{file}", bundledCode, 0, 0) + " -g {file}:{line}:{col}"; + return systemOpener + " {file}"; +} bool SourceStore::takePendingScroll() { bool v = g_pendingScroll; g_pendingScroll = false; return v; } + +void SourceStore::clear() { + g_subject.clear(); + g_address = json::object(); + g_references.clear(); + g_hasReferences = false; + g_shown = SourceRef{}; + g_hostPath.clear(); + g_stale = false; + g_lines.clear(); + g_firstLine = 1; + g_truncated = false; + g_hasSource = false; + g_missing = false; + g_pendingScroll = false; +} diff --git a/src/SourceStore.h b/src/SourceStore.h index 51ab1f0..fffe6d7 100644 --- a/src/SourceStore.h +++ b/src/SourceStore.h @@ -1,21 +1,87 @@ #pragma once +#include +#include #include +#include #include -// Holds the most recently fetched RTL source text (from a source_response), -// split into lines, plus which line to scroll to/highlight. Same -// static/global state pattern as DiagnosisStore/EquipotentialView. +#include "Types.h" + +// What the "Source" tab shows (see docs/source_information.md): the source +// references of the object last asked about (a sources_response, answering +// get_sources), and the text of one referenced file (a source_response, +// answering load_source) with the reference's range to highlight. Same +// static/global state pattern as DiagnosisStore/EquipotentialView. Cleared +// on every fresh root load: references name objects of the old design. class SourceStore { public: - static void setSource(const std::string& file, int line, const std::string& text); - static void clear(); + // -- the object's references ------------------------------------------ + // `address` is the get_sources request's object address (kind, + // path/id_path, terminal/net, bit), as echoed by the reply. + static void setReferences(const std::string& subject, const json& address, + std::vector references); + static bool hasReferences(); + static const std::string& subject(); + static const std::vector& references(); + // The load_source request reading references()[i]'s file. + static json loadRequest(size_t i); + // The entry of references() whose file is shown, if any. + static std::optional shownReference(); + // -- the shown file --------------------------------------------------- + // `firstLine` is the file line number of text's first line (1 unless + // the provider sent only the lines around a reference in a big file). + // `hostPath` is where the netlist host read the file (source_response + // "path"): for "Open in Editor" when the viewer runs on the same host. + // `stale`: the file changed after the design was loaded, so the + // reference's lines may not match it any more. + static void setSource(const SourceRef& ref, const std::string& text, + std::uint64_t firstLine = 1, bool truncated = false, + const std::string& hostPath = {}, bool stale = false); + // The file of `ref` couldn't be read on the netlist host. + static void setMissing(const SourceRef& ref); static bool hasSource(); + static const SourceRef& shown(); // the reference whose file is shown (or missing) + static bool missing(); static const std::string& file(); + static const std::string& hostPath(); static const std::vector& lines(); - static int targetLine(); + static std::uint64_t firstLine(); + static bool truncated(); + static bool stale(); + // File line number to scroll to (the reference's first line), 0 if unknown. + static std::uint64_t targetLine(); + // Whether file line `lineNo` is within the reference's lines. + static bool inRange(std::uint64_t lineNo); + // The bytes [begin, end) of lines()[index] the reference covers, by its + // 1-based inclusive byte columns: from the column on its first line, to + // the end column on its last, the whole line in between or where a + // column is unknown (0). nullopt outside the reference's lines. + static std::optional> highlightBytes(size_t index); // True once right after a fresh setSource() call, then false again -- - // lets the viewer scroll-to-line exactly once instead of every frame. + // lets the viewer load the text into its editor and scroll to the + // reference exactly once instead of every frame. static bool takePendingScroll(); + + // The code point index of byte `byte` of `line` (UTF-8): naja's columns + // count bytes, the editor's positions count glyphs. + static size_t glyphIndex(const std::string& line, size_t byte); + + // The shell command opening `path` at `line`/`column` in an external + // editor: `pattern` with {file} (shell-quoted), {line} and {col} + // replaced (an unknown line or column, 0, becomes 1). + static std::string editorCommand(const std::string& pattern, const std::string& path, + std::uint64_t line, std::uint64_t column); + + // The editor command pattern to use: `configured` ($NAJA_SCHEMATIC_EDITOR) + // when set; else VS Code's `code -g` when `codeOnPath`; else the CLI + // inside an installed VS Code app (`bundledCode`, "" if none), which + // `code` on PATH usually only links to; else the system's opener + // (`systemOpener`: open / xdg-open), which can't go to a line. + static std::string editorPattern(const std::string& configured, bool codeOnPath, + const std::string& bundledCode, + const std::string& systemOpener); + + static void clear(); }; diff --git a/src/SourceView.cpp b/src/SourceView.cpp index cb55422..ff19bf6 100644 --- a/src/SourceView.cpp +++ b/src/SourceView.cpp @@ -1,52 +1,319 @@ #include "SourceView.h" +#include +#include +#include +#include +#include +#ifndef __EMSCRIPTEN__ +#include +#include +#include +#endif + #include +#include "ImGuiColorTextEdit/TextEditor.h" + +#include "Console.h" +#include "INetlistProvider.h" #include "SourceStore.h" +#include "VerilogLanguage.h" -void SourceView::render() { - if (!SourceStore::hasSource()) { - ImGui::TextDisabled("No RTL source loaded."); - ImGui::TextWrapped( - "Right-click an instance in the tree or on a schematic box and choose " - "\"Show RTL Source\" (only available for SystemVerilog-loaded " - "instances with a recorded source location)."); +namespace { + +ImFont* g_font = nullptr; + +// Squiggle category of the shown reference's range (see TextEditor::AddSquiggle). +constexpr size_t kReferenceSquiggle = 1; +constexpr ImU32 kReferenceColor = IM_COL32(230, 170, 40, 110); // the shown range +constexpr ImU32 kReferenceLine = IM_COL32(230, 170, 40, 200); // its line numbers +constexpr ImU32 kOtherLine = IM_COL32(120, 160, 230, 170); // the object's other references here + +std::string roleLabel(const SourceRef& ref) { + if (ref.role == "declaration") return "Declaration"; + if (ref.role == "model_declaration") return "Model definition"; + if (ref.role == "origin") return "Origin " + std::to_string(ref.index.value_or(0) + 1); + if (ref.role == "legacy") return "Location"; + return ref.role; +} + +// "" (unknown) shows as a dash: naja never guesses these. +const char* orDash(const std::string& s) { return s.empty() ? "-" : s.c_str(); } + +// The lines to highlight in the line-number gutter (document line -> color, +// tooltip): the shown reference's and the object's other references'. +struct LineMark { ImU32 color; std::string tooltip; }; +std::unordered_map g_lineMarks; + +TextEditor& editor() { + static TextEditor e = [] { + TextEditor t; + t.SetReadOnlyEnabled(true); + t.SetLanguage(verilogLanguage()); + t.SetPalette(TextEditor::GetDarkPalette()); + t.SetShowCurrentLineHighlightEnabled(false); + t.SetShowWhitespacesEnabled(false); + t.SetShowMatchingBrackets(true); + // A big file comes as a window of its lines: number them as in the file. + // Marked lines are highlighted here too, behind their number: the + // editor's own line-number markers are offset from these numbers by the + // window padding (it places them from the text origin, the numbers from + // the window edge), so the box is drawn from the same position and + // width as the digits instead. + t.SetCustomLineNumberRenderer([](const TextEditor::CustomLineNumber& n) { + std::string text = std::to_string(SourceStore::firstLine() + n.lineNumber); + const float w = ImGui::CalcTextSize(text.c_str()).x; + const float right = n.pos.x + n.size.x; + if (auto mark = g_lineMarks.find(n.lineNumber); mark != g_lineMarks.end()) { + const float pad = ImGui::CalcTextSize("0").x * 0.25f; + const ImVec2 min(right - std::max(n.size.x, w) - pad, n.pos.y); + const ImVec2 max(right + pad, n.pos.y + n.size.y); + n.drawList->AddRectFilled(min, max, mark->second.color); + if (ImGui::IsMouseHoveringRect(min, max)) ImGui::SetTooltip("%s", mark->second.tooltip.c_str()); + } + n.drawList->AddText(ImVec2(right - w, n.pos.y), n.color, text.c_str()); + }); + return t; + }(); + return e; +} + +size_t digits(std::uint64_t n) { return std::to_string(n).size(); } + +// The shown file just arrived: load it into the editor, mark the reference, +// and scroll to it. +void loadIntoEditor() { + auto& e = editor(); + const auto& lines = SourceStore::lines(); + const auto first = SourceStore::firstLine(); + e.SetText(std::vector(lines.begin(), lines.end())); + g_lineMarks.clear(); + e.ClearSquiggles(); + // Room for the file's line numbers, not just the window's. + const auto last = first + lines.size() - 1; + e.SetLineNumberLeftMargin(1 + digits(last) - std::min(digits(last), digits(lines.size()))); + + const auto& shown = SourceStore::shown(); + const std::string where = roleLabel(shown) + ": " + sourceRefLocation(shown); + auto inWindow = [&](std::uint64_t lineNo) { return lineNo >= first && lineNo <= last; }; + + // The object's other references into this file: their first line, marked. + const auto shownIndex = SourceStore::shownReference(); + const auto& refs = SourceStore::references(); + for (size_t i = 0; i < refs.size(); ++i) { + if (shownIndex == i || refs[i].file != shown.file || !inWindow(refs[i].line)) continue; + const std::string tip = roleLabel(refs[i]) + ": " + sourceRefLocation(refs[i]); + g_lineMarks[refs[i].line - first] = {kOtherLine, tip}; + } + // The shown reference: its byte columns as a highlighted range, its lines marked. + for (size_t i = 0; i < lines.size(); ++i) { + auto span = SourceStore::highlightBytes(i); + if (!span) continue; + g_lineMarks[i] = {kReferenceLine, where}; + const size_t g0 = SourceStore::glyphIndex(lines[i], span->first); + const size_t g1 = SourceStore::glyphIndex(lines[i], span->second); + if (g1 > g0) + e.AddSquiggle(TextEditor::DocPos(i, g0), TextEditor::DocPos(i, g1), kReferenceSquiggle, + kReferenceColor, where, TextEditor::SquiggleStyle::background); + } + const auto target = SourceStore::targetLine(); + if (inWindow(target)) { + const size_t row = target - first; + const size_t col = shown.column ? SourceStore::glyphIndex(lines[row], shown.column - 1) : 0; + e.SetCursor(TextEditor::DocPos(row, col)); + e.ScrollToLine(row, TextEditor::Scroll::alignMiddle); + } +} + +void renderReferences(INetlistProvider* provider) { + ImGui::TextUnformatted(SourceStore::subject().empty() ? "" : SourceStore::subject().c_str()); + const auto& refs = SourceStore::references(); + if (refs.empty()) { + ImGui::TextDisabled("No source information for this object."); return; } + const float rowH = ImGui::GetTextLineHeightWithSpacing(); + const float height = rowH * (std::min(refs.size(), 6) + 1) + 4.0f; + const ImGuiTableFlags flags = ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable; + if (!ImGui::BeginTable("##SourceRefs", 5, flags, ImVec2(0, height))) return; + ImGui::TableSetupScrollFreeze(0, 1); + ImGui::TableSetupColumn("Role", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Location", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Representation", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Language", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Provider", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableHeadersRow(); + const auto shown = SourceStore::shownReference(); + for (size_t i = 0; i < refs.size(); ++i) { + const auto& ref = refs[i]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushID(static_cast(i)); + if (ImGui::Selectable(roleLabel(ref).c_str(), shown == i, + ImGuiSelectableFlags_SpanAllColumns) && provider) + provider->send(SourceStore::loadRequest(i).dump()); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Show this reference's file"); + ImGui::PopID(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(sourceRefLocation(ref).c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(orDash(ref.representation)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(orDash(ref.language)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(orDash(ref.provider)); + } + ImGui::EndTable(); +} - bool scrollToTarget = SourceStore::takePendingScroll(); - const auto& lines = SourceStore::lines(); - int target = SourceStore::targetLine(); +#ifndef __EMSCRIPTEN__ +// What the last "Open in Editor" did, written by its worker thread: shown +// next to the button, so a missing editor doesn't fail silently. +std::mutex g_editorMutex; +std::string g_editorStatus; +bool g_editorFailed = false; - ImGui::TextUnformatted(SourceStore::file().c_str()); - ImGui::Separator(); +bool isExecutable(const std::string& path) { return ::access(path.c_str(), X_OK) == 0; } - ImGui::BeginChild("##SourceLines", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); +bool onPath(const std::string& name) { + const char* path = std::getenv("PATH"); + std::string dirs = path ? path : ""; + for (size_t start = 0; start <= dirs.size();) { + size_t end = dirs.find(':', start); + if (end == std::string::npos) end = dirs.size(); + if (end > start && isExecutable(dirs.substr(start, end - start) + "/" + name)) return true; + start = end + 1; + } + return false; +} + +// The `code` CLI inside an installed VS Code app, "" if none (macOS: VS Code +// ships it there, and only links it onto PATH on request). +std::string bundledVSCode() { +#ifdef __APPLE__ + const std::string inApp = "/Visual Studio Code.app/Contents/Resources/app/bin/code"; + const char* home = std::getenv("HOME"); + for (const std::string& dir : {std::string("/Applications"), + std::string(home ? home : "") + "/Applications"}) + if (isExecutable(dir + inApp)) return dir + inApp; +#endif + return ""; +} + +// Opens the shown file in an external editor ($NAJA_SCHEMATIC_EDITOR, else +// VS Code, else the system's opener; see SourceStore::editorPattern()), on a +// worker thread whose exit status and output end up in g_editorStatus. +// Native only: the file is on this machine (the host read it), the +// browser's isn't. +void openInEditor() { + const char* env = std::getenv("NAJA_SCHEMATIC_EDITOR"); +#ifdef __APPLE__ + const char* opener = "open"; +#else + const char* opener = "xdg-open"; +#endif + const std::string pattern = SourceStore::editorPattern(env ? env : "", onPath("code"), + bundledVSCode(), opener); + const auto& ref = SourceStore::shown(); + const std::string cmd = SourceStore::editorCommand(pattern, SourceStore::hostPath(), + ref.line, ref.column); + Console::Log("Opening in editor: " + cmd); + { + std::lock_guard lock(g_editorMutex); + g_editorStatus = "Opening..."; + g_editorFailed = false; + } + std::thread([cmd] { + std::string output; + int status = -1; + if (FILE* pipe = ::popen((cmd + " 2>&1").c_str(), "r")) { + char buf[256]; + while (std::fgets(buf, sizeof buf, pipe)) output += buf; + status = ::pclose(pipe); + } + while (!output.empty() && (output.back() == '\n' || output.back() == '\r')) output.pop_back(); + std::lock_guard lock(g_editorMutex); + g_editorFailed = status != 0; + g_editorStatus = g_editorFailed + ? "Editor command failed: " + cmd + (output.empty() ? "" : "\n" + output) + + "\nSet NAJA_SCHEMATIC_EDITOR, e.g. \"subl {file}:{line}:{col}\"." + : "Opened in the editor."; + }).detach(); +} +#endif - float lineH = ImGui::GetTextLineHeightWithSpacing(); - if (scrollToTarget && target > 0) { - float childH = ImGui::GetWindowHeight(); - float y = float(target - 1) * lineH - childH * 0.35f; - ImGui::SetScrollY(y > 0.0f ? y : 0.0f); +void renderFile() { + const auto& ref = SourceStore::shown(); + if (SourceStore::missing()) { + ImGui::TextWrapped( + "%s is not readable on the netlist host. Relative paths are looked up " + "in the --source-root directories, then the current directory.", + ref.file.empty() ? "" : ref.file.c_str()); + return; } + if (!SourceStore::hasSource()) return; - ImGuiListClipper clipper; - clipper.Begin(static_cast(lines.size())); - while (clipper.Step()) { - for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) { - int lineNo = i + 1; - bool isTarget = (lineNo == target); - if (isTarget) { - ImVec2 rowMin = ImGui::GetCursorScreenPos(); - ImVec2 rowMax(rowMin.x + ImGui::GetContentRegionAvail().x, rowMin.y + lineH); - ImGui::GetWindowDrawList()->AddRectFilled(rowMin, rowMax, IM_COL32(90, 70, 20, 140)); - } - ImGui::TextDisabled("%4d ", lineNo); + if (SourceStore::takePendingScroll()) loadIntoEditor(); + + ImGui::TextUnformatted(sourceRefLocation(ref).c_str()); + if (SourceStore::truncated()) { + ImGui::SameLine(); + ImGui::TextDisabled("(large file: only the lines around the reference)"); + } +#ifndef __EMSCRIPTEN__ + if (!SourceStore::hostPath().empty()) { + ImGui::SameLine(); + if (ImGui::SmallButton("Open in Editor")) openInEditor(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Runs $NAJA_SCHEMATIC_EDITOR ({file}, {line}, {col}),\n" + "else VS Code (code -g), else the system's default app.\n" + "Edits there reach this view only once the design is\n" + "loaded again: until then its source lines may be off."); + std::lock_guard lock(g_editorMutex); + if (!g_editorStatus.empty()) { ImGui::SameLine(); - ImGui::TextUnformatted(lines[i].c_str()); + const auto firstLine = g_editorStatus.substr(0, g_editorStatus.find('\n')); + if (g_editorFailed) { + ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), "%s", firstLine.c_str()); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", g_editorStatus.c_str()); + } else { + ImGui::TextDisabled("%s", firstLine.c_str()); + } } } - clipper.End(); +#endif + + if (SourceStore::stale()) { + // naja recorded the ranges when the design was loaded: the file changed + // since, so they may point at the wrong lines. + ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.3f, 1.0f), + "This file changed after the design was loaded: the highlighted " + "lines may be off. Load the design again to update them."); + } + if (g_font) ImGui::PushFont(g_font, 0.0f); + editor().Render("##SourceText"); + if (g_font) ImGui::PopFont(); +} + +} // namespace - ImGui::EndChild(); +void SourceView::setFont(ImFont* font) { g_font = font; } + +void SourceView::render(INetlistProvider* provider) { + if (!SourceStore::hasReferences() && !SourceStore::hasSource() && !SourceStore::missing()) { + ImGui::TextDisabled("No source shown."); + ImGui::TextWrapped( + "Right-click an instance, term or net in the tree, or an instance box " + "in the schematic, and choose \"Show Sources\" (offered when the " + "netlist records where the object comes from: its declaration, and " + "for synthesized logic the RTL it came from)."); + return; + } + if (SourceStore::hasReferences()) { + renderReferences(provider); + ImGui::Separator(); + } + renderFile(); } diff --git a/src/SourceView.h b/src/SourceView.h index 8ffc07c..7ee8c65 100644 --- a/src/SourceView.h +++ b/src/SourceView.h @@ -1,9 +1,21 @@ #pragma once -// Renders the currently-loaded RTL source text (see SourceStore) into a -// read-only, line-numbered, scrollable panel; scrolls to and highlights the -// target line once each time a new file is loaded. +struct ImFont; +class INetlistProvider; + +// The "Source" tab (see SourceStore): the source references of the object +// last asked about (Show Sources) -- each a row naming its role, location, +// representation, language and provider; clicking one asks `provider` for +// its file -- and below them the shown file in a read-only code editor +// (ImGuiColorTextEdit): monospace, Verilog/SystemVerilog coloring +// (VerilogLanguage), find (Ctrl/Cmd+F), selection and copy. The +// reference's range is highlighted and its lines marked, the object's other +// references into the same file are marked too, and the editor scrolls to +// the reference once per file loaded. Native builds also offer "Open in +// Editor" ($NAJA_SCHEMATIC_EDITOR). class SourceView { public: - static void render(); + // The monospace font the code is drawn with (setupFonts()). + static void setFont(ImFont* font); + static void render(INetlistProvider* provider); }; diff --git a/src/Types.cpp b/src/Types.cpp index cf50368..998c653 100644 --- a/src/Types.cpp +++ b/src/Types.cpp @@ -5,16 +5,30 @@ #include "Console.h" -static std::optional parseSourceLoc(const json& j) { - if (!j.contains("source_loc") || j["source_loc"].is_null()) return std::nullopt; - const auto& s = j["source_loc"]; - SourceLoc loc; - loc.file = s.value("file", std::string("")); - loc.line = s.value("line", 0); - loc.endLine = s.value("end_line", loc.line); - loc.column = s.value("column", 0); - loc.endColumn = s.value("end_column", loc.column); - return loc; +SourceRef parseSourceRef(const json& j) { + SourceRef ref; + ref.role = j.value("role", std::string("")); + if (j.contains("index") && j["index"].is_number_unsigned()) + ref.index = j["index"].get(); + ref.file = j.value("file", std::string("")); + // Missing coordinates stay 0 (unknown): an end is never made up. + ref.line = j.value("line", 0u); + ref.column = j.value("column", 0u); + ref.endLine = j.value("end_line", 0u); + ref.endColumn = j.value("end_column", 0u); + ref.provider = j.value("provider", std::string("")); + ref.language = j.value("language", std::string("")); + ref.representation = j.value("representation", std::string("")); + return ref; +} + +std::string sourceRefLocation(const SourceRef& ref) { + std::string out = ref.file.empty() ? "" : ref.file; + if (ref.line) { + out += ":" + std::to_string(ref.line); + if (ref.column) out += ":" + std::to_string(ref.column); + } + return out; } Direction intToDirection(int dirInt) { @@ -50,7 +64,7 @@ void from_json(const json& j, InstanceResponseJson& r) { j.at("has_instances").get_to(r.has_instances); j.at("has_terms").get_to(r.has_terms); j.at("has_nets").get_to(r.has_nets); - r.source_loc = parseSourceLoc(j); + r.has_sources = j.value("has_sources", false); } void from_json(const json& j, InstancesResponseJson& instances) { @@ -90,6 +104,7 @@ void from_json(const json& j, TermsResponseJson& t) { } else { term.lsb = std::nullopt; } + term.has_sources = child.value("has_sources", false); t.children.push_back(std::move(term)); } @@ -118,6 +133,7 @@ void from_json(const json& j, NetsResponseJson& n) { } else { net.lsb = std::nullopt; } + net.has_sources = child.value("has_sources", false); n.children.push_back(std::move(net)); } @@ -191,7 +207,7 @@ void from_json(const json& j, Equipotential& e) { occurrence.has_instances = occJson.value("has_instances", false); if (occJson.contains("bit_term_count") && occJson["bit_term_count"].is_number_unsigned()) occurrence.bit_term_count = occJson["bit_term_count"].get(); - occurrence.source_loc = parseSourceLoc(occJson); + occurrence.has_sources = occJson.value("has_sources", false); e.occurrences.push_back(std::move(occurrence)); } diff --git a/src/Types.h b/src/Types.h index bfe47bd..deb4e7f 100644 --- a/src/Types.h +++ b/src/Types.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -20,17 +21,31 @@ struct DesignRef { int design_id; }; -// RTL source location for an elaborated object (naja's SNLRTLInfos), only -// populated for SystemVerilog-loaded designs today (see CLAUDE.md). Absent -// means "no source location available" -- not an error. -struct SourceLoc { +// One source reference of a netlist object, an entry of a sources_response +// (see docs/source_information.md). `role` is "declaration" (where the object +// is written in the loaded input), "origin" (where it came from upstream, +// e.g. a Yosys src RTL line; `index` orders them), "model_declaration" (an +// instance's model definition) or "legacy" (naja's unclassified location). +// Lines and byte columns are 1-based and inclusive; 0 means unknown, and an +// unknown end is not made up. "" qualifiers mean unknown, never guessed. +struct SourceRef { + std::string role; + std::optional index; std::string file; - int line = 0; - int endLine = 0; - int column = 0; - int endColumn = 0; + std::uint32_t line = 0; + std::uint32_t column = 0; + std::uint32_t endLine = 0; + std::uint32_t endColumn = 0; + std::string provider; + std::string language; + std::string representation; }; +SourceRef parseSourceRef(const json& j); + +// "rtl/alu.v:2:14": the reference's start, for a human to read. +std::string sourceRefLocation(const SourceRef& ref); + // Coarse gate/cell function classification for an instance, used to pick a // standard schematic symbol instead of a generic box (see // SchematicView::drawInstance()). It comes from naja's modeling of the cell @@ -103,7 +118,7 @@ struct InstanceResponseJson { bool has_instances; bool has_terms; bool has_nets; - std::optional source_loc; + bool has_sources = false; // see get_sources (CLAUDE.md, "Source references") }; struct InstancesResponseJson { @@ -229,8 +244,8 @@ struct InstTermOccurrence { // bit), if the provider sent it -- used to tell a fully-shown interface // from a partial one. std::optional bit_term_count; - // RTL source location of the tail instance itself, if available. - std::optional source_loc; + // Whether the tail instance has source references (get_sources). + bool has_sources = false; }; struct TermResponseJson { @@ -239,6 +254,7 @@ struct TermResponseJson { Direction direction; std::optional msb; std::optional lsb; + bool has_sources = false; // the term, or any bit of a bus term }; struct TermsResponseJson { @@ -255,6 +271,7 @@ struct NetResponseJson { std::string name; std::optional msb; std::optional lsb; + bool has_sources = false; // the net, or any bit of a bus net }; struct NetsResponseJson { diff --git a/src/VerilogLanguage.cpp b/src/VerilogLanguage.cpp new file mode 100644 index 0000000..1e45c9f --- /dev/null +++ b/src/VerilogLanguage.cpp @@ -0,0 +1,166 @@ +#include "VerilogLanguage.h" + +namespace { + +using Iterator = TextEditor::Iterator; + +bool isIdentifierStart(ImWchar c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; +} + +bool isIdentifierChar(ImWchar c) { + return isIdentifierStart(c) || (c >= '0' && c <= '9') || c == '$'; +} + +bool isDecimalDigit(ImWchar c) { return (c >= '0' && c <= '9') || c == '_'; } + +bool isBaseChar(ImWchar c) { + switch (c) { + case 'b': case 'B': case 'o': case 'O': case 'd': case 'D': case 'h': case 'H': return true; + default: return false; + } +} + +// Digits of a based number: hex digits, unknown/high-impedance values, `_`. +bool isBasedDigit(ImWchar c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || + c == 'x' || c == 'X' || c == 'z' || c == 'Z' || c == '?' || c == '_'; +} + +bool isUnbasedUnsizedValue(ImWchar c) { + return c == '0' || c == '1' || c == 'x' || c == 'X' || c == 'z' || c == 'Z'; +} + +// foo, foo$1, $display (system task), \escaped/name (up to white space). +Iterator getIdentifier(Iterator start, Iterator end) { + Iterator i = start; + if (i == end) return start; + if (*i == '\\') { + ++i; + while (i < end && *i != ' ' && *i != '\t') ++i; + return i; + } + if (*i == '$') { + ++i; + if (i == end || !isIdentifierStart(*i)) return start; + } else if (!isIdentifierStart(*i)) { + return start; + } + while (i < end && isIdentifierChar(*i)) ++i; + return i; +} + +// 42, 1_000, 3.14, 1.5e-3, 8'hFF, 4'sb10xz, 'h1F, '0, 'x. +Iterator getNumber(Iterator start, Iterator end) { + Iterator i = start; + while (i < end && isDecimalDigit(*i)) ++i; + const bool sized = i != start; + if (i < end && *i == '\'') { + Iterator j = i; + ++j; + if (j < end && (*j == 's' || *j == 'S')) ++j; + if (j < end && isBaseChar(*j)) { + ++j; + while (j < end && (*j == ' ' || *j == '\t')) ++j; // 8'h FF is legal + Iterator digits = j; + while (j < end && isBasedDigit(*j)) ++j; + if (j != digits) return j; + } else if (!sized && j < end && isUnbasedUnsizedValue(*j)) { + ++j; + return j; + } + return sized ? i : start; // a size alone; the quote is punctuation + } + if (!sized) return start; + // Real: digits "." digits, optional exponent. + if (i < end && *i == '.') { + Iterator j = i; + ++j; + if (j < end && isDecimalDigit(*j)) { + while (j < end && isDecimalDigit(*j)) ++j; + i = j; + } + } + if (i < end && (*i == 'e' || *i == 'E')) { + Iterator j = i; + ++j; + if (j < end && (*j == '+' || *j == '-')) ++j; + if (j < end && isDecimalDigit(*j)) { + while (j < end && isDecimalDigit(*j)) ++j; + i = j; + } + } + return i; +} + +bool isPunctuation(ImWchar c) { + switch (c) { + case '!': case '%': case '&': case '(': case ')': case '*': case '+': case ',': + case '-': case '.': case '/': case ':': case ';': case '<': case '=': case '>': + case '?': case '@': case '[': case ']': case '^': case '{': case '|': case '}': + case '~': case '#': case '\'': + return true; + default: + return false; + } +} + +} // namespace + +const TextEditor::Language* verilogLanguage() { + static bool initialized = false; + static TextEditor::Language language; + if (initialized) return &language; + + language.name = "Verilog"; + language.preprocess = '`'; + language.singleLineComment = "//"; + language.commentStart = "/*"; + language.commentEnd = "*/"; + language.hasDoubleQuotedStrings = true; + language.stringEscape = '\\'; + + // Control and structure (IEEE 1364 + the 1800 additions in common use). + static const char* const keywords[] = { + "always", "always_comb", "always_ff", "always_latch", "assign", "assert", "assume", "automatic", + "begin", "break", "case", "casex", "casez", "cell", "config", "continue", "cover", "deassign", + "default", "defparam", "design", "disable", "do", "else", "end", "endcase", "endclass", + "endconfig", "endfunction", "endgenerate", "endinterface", "endmodule", "endpackage", + "endprimitive", "endprogram", "endproperty", "endspecify", "endtable", "endtask", "export", + "extends", "final", "for", "force", "foreach", "forever", "fork", "function", "generate", + "genvar", "if", "iff", "import", "incdir", "include", "initial", "inside", "instance", "join", + "join_any", "join_none", "liblist", "library", "localparam", "macromodule", "modport", "module", + "negedge", "new", "null", "or", "package", "parameter", "posedge", "primitive", "priority", + "program", "property", "release", "repeat", "return", "specify", "specparam", "super", "table", + "task", "this", "unique", "unique0", "use", "virtual", "wait", "while", "with", + // built-in gates + "and", "buf", "bufif0", "bufif1", "cmos", "nand", "nmos", "nor", "not", "notif0", "notif1", + "pmos", "pulldown", "pullup", "rcmos", "rnmos", "rpmos", "rtran", "rtranif0", "rtranif1", + "tran", "tranif0", "tranif1", "xnor", "xor", + }; + + // Ports, nets, variables and types. + static const char* const declarations[] = { + "bit", "byte", "chandle", "class", "const", "enum", "event", "inout", "input", "int", + "integer", "interface", "logic", "longint", "output", "packed", "real", "realtime", "ref", + "reg", "shortint", "shortreal", "signed", "static", "string", "struct", "supply0", "supply1", + "time", "tri", "tri0", "tri1", "triand", "trior", "trireg", "type", "typedef", "union", + "unsigned", "uwire", "var", "void", "wand", "wire", "wor", + }; + + // System tasks and functions. + static const char* const identifiers[] = { + "$bits", "$clog2", "$display", "$error", "$fatal", "$finish", "$fwrite", "$info", "$monitor", + "$random", "$readmemb", "$readmemh", "$signed", "$stop", "$strobe", "$time", "$unsigned", + "$warning", "$write", + }; + + for (auto* k : keywords) language.keywords.insert(k); + for (auto* d : declarations) language.declarations.insert(d); + for (auto* i : identifiers) language.identifiers.insert(i); + language.isPunctuation = isPunctuation; + language.getIdentifier = getIdentifier; + language.getNumber = getNumber; + initialized = true; + return &language; +} diff --git a/src/VerilogLanguage.h b/src/VerilogLanguage.h new file mode 100644 index 0000000..ea31992 --- /dev/null +++ b/src/VerilogLanguage.h @@ -0,0 +1,11 @@ +#pragma once + +#include "ImGuiColorTextEdit/TextEditor.h" + +// Syntax coloring for the Source tab (SourceView): Verilog and +// SystemVerilog (IEEE 1364/1800) keywords, data/net types, `directive +// lines, // and /* */ comments, "strings", system tasks ($display) and +// escaped identifiers (\a/b ), and numbers with sizes, bases and +// unknowns (8'hFF, 4'b10xz, 'x, 1_000, 1.5e3). Coloring only: it is no +// parser, and it colors gate-level netlists and RTL alike. +const TextEditor::Language* verilogLanguage(); diff --git a/src/main_native.cpp b/src/main_native.cpp index 002816f..ab44de2 100644 --- a/src/main_native.cpp +++ b/src/main_native.cpp @@ -18,6 +18,8 @@ int main(int argc, char* argv[]) { // CLI usage: naja-schematic-standalone [] [--liberty ]... [--diagnosis ] + // [--source-root ]... [--source-map FROM=TO]... + // [--yosys-src] // [--export [--focus ] // [--trace ]... [--equipotential ]...] // is loaded by extension (.sv/.v/otherwise-assumed-SNL-directory); @@ -28,6 +30,13 @@ int main(int argc, char* argv[]) { // caller (a script, or naja-agent's skill, after an edit-check cycle) can // open a fully annotated view in one command instead of requiring a human // to click through File > Open .../Load Diagnosis JSON... by hand. + // --yosys-src (.v only) decodes Yosys (* src *) attributes into source + // origins, so Show Sources leads from a gate back to its RTL; + // --source-map FROM=TO (repeatable) rewrites source paths recorded on + // another machine (e.g. a synthesis server's) to start with TO instead; + // --source-root adds a directory relative source file references (Show + // Sources) are looked up in (repeatable, tried in order, then the current + // directory); // --export draws the schematic to an SVG file and exits, with no window // (see HeadlessExport.h for what --focus/--trace/--equipotential take). auto* provider = new LocalSNLProvider(); @@ -35,6 +44,7 @@ int main(int argc, char* argv[]) { std::string designPath; std::string diagnosisPath; std::vector libertyPaths; + bool yosysSrc = false; HeadlessExportOptions exportOptions; bool exporting = false; for (int i = 1; i < argc; ++i) { @@ -54,6 +64,18 @@ int main(int argc, char* argv[]) { diagnosisPath = argv[++i]; } else if (arg == "--liberty" && i + 1 < argc) { libertyPaths.push_back(argv[++i]); + } else if (arg == "--yosys-src") { + yosysSrc = true; + } else if (arg == "--source-map" && i + 1 < argc) { + const std::string map = argv[++i]; + const auto eq = map.find('='); + if (eq == std::string::npos || eq == 0) { + std::cerr << "--source-map expects FROM=TO, got " << map << "\n"; + return 1; + } + provider->addSourceMap(map.substr(0, eq), map.substr(eq + 1)); + } else if (arg == "--source-root" && i + 1 < argc) { + provider->addSourceRoot(argv[++i]); } else if (designPath.empty()) { designPath = arg; } @@ -66,9 +88,13 @@ int main(int argc, char* argv[]) { std::cerr << "--liberty is only supported for .v designs, not .sv\n"; return 1; } + if (yosysSrc) { + std::cerr << "--yosys-src is only supported for .v designs\n"; + return 1; + } provider->loadSystemVerilog({designPath}); } else if (ext == "v") { - provider->loadVerilog({designPath}, libertyPaths); + provider->loadVerilog({designPath}, libertyPaths, yosysSrc); } else { provider->loadSNL(designPath); // assume SNL directory } diff --git a/tests/SelectionTest.cpp b/tests/SelectionTest.cpp index 45db28c..3400fdb 100644 --- a/tests/SelectionTest.cpp +++ b/tests/SelectionTest.cpp @@ -278,3 +278,68 @@ TEST_F(TreeReveal, RevealsASelectionMadeElsewhereOnRender) { SelectionStore::clear(); ImGui::DestroyContext(); } + +// Choosing a context-menu entry must not select the tree row that happens +// to lie under the menu item: the click belongs to the menu. +TEST_F(TreeReveal, ContextMenuClickDoesNotSelectTheRowUnderIt) { + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(800.f, 600.f); + io.DeltaTime = 1.f / 60.f; + io.IniFilename = nullptr; + unsigned char* px; int w, h; + io.Fonts->GetTexDataAsRGBA32(&px, &w, &h); + ImVec2 origin; + float pitch = 0.f; + auto frame = [&] { + ImGui::NewFrame(); + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(400, 500)); + ImGui::Begin("Tree", nullptr, ImGuiWindowFlags_NoMove); + origin = ImGui::GetCursorScreenPos(); + pitch = ImGui::GetTextLineHeightWithSpacing(); + tree.render(); + ImGui::End(); + ImGui::Render(); + }; + // Center of tree row `row` (0 = top), `x` from the window's content start. + auto at = [&](int row, float x) { + return ImVec2(origin.x + x, origin.y + row * pitch + ImGui::GetFontSize() * 0.5f); + }; + auto click = [&](ImVec2 pos, ImGuiMouseButton button) { + io.AddMousePosEvent(pos.x, pos.y); + frame(); + io.AddMouseButtonEvent(button, true); + frame(); + io.AddMouseButtonEvent(button, false); + frame(); + frame(); + }; + + SelectionStore::clear(); + frame(); + click(at(0, 4.f), ImGuiMouseButton_Left); // open "top" + // Its groups: Terms, Primitives, Instances. Open Instances (on its arrow, + // one indent in): that loads it. + click(at(3, ImGui::GetStyle().IndentSpacing + 5.f), ImGuiMouseButton_Left); + auto load = take("load_instances"); + ASSERT_EQ(load.size(), 1u); + answer(load[0]["gui_id"].get(), + {{"u0", 0, false}, {"u1", 1, false}, {"u2", 2, false}}); + frame(); + SelectionStore::clear(); + provider.sent.clear(); + + click(at(4, 60.f), ImGuiMouseButton_Right); // u0's context menu + // Its first entry ("Show Properties") lies over the next row, u1's. + click(ImVec2(at(4, 60.f).x + 30.f, at(5, 0.f).y), ImGuiMouseButton_Left); + + auto props = take("get_properties"); + ASSERT_EQ(props.size(), 1u); + EXPECT_EQ(props[0]["id_path"], nlohmann::json::array({0})); // u0's + EXPECT_FALSE(SelectionStore::isSelected(InstancePath{{1, "u1"}})); + EXPECT_FALSE(SelectionStore::isSelected(InstancePath{{2, "u2"}})); + + SelectionStore::clear(); + ImGui::DestroyContext(); +} diff --git a/tests/SourceStoreTest.cpp b/tests/SourceStoreTest.cpp index 66bcf89..cd101e6 100644 --- a/tests/SourceStoreTest.cpp +++ b/tests/SourceStoreTest.cpp @@ -2,6 +2,23 @@ #include +namespace { + +SourceRef ref(std::uint32_t line, std::uint32_t column, std::uint32_t endLine, std::uint32_t endColumn, + const std::string& role = "declaration", std::optional index = std::nullopt) { + SourceRef r; + r.role = role; + r.index = index; + r.file = "top.sv"; + r.line = line; + r.column = column; + r.endLine = endLine; + r.endColumn = endColumn; + return r; +} + +} // namespace + class SourceStoreTest : public ::testing::Test { protected: void TearDown() override { SourceStore::clear(); } @@ -9,14 +26,16 @@ class SourceStoreTest : public ::testing::Test { TEST_F(SourceStoreTest, StartsEmpty) { EXPECT_FALSE(SourceStore::hasSource()); + EXPECT_FALSE(SourceStore::hasReferences()); + EXPECT_FALSE(SourceStore::missing()); EXPECT_TRUE(SourceStore::lines().empty()); } TEST_F(SourceStoreTest, SplitsLfSeparatedLines) { - SourceStore::setSource("top.sv", 2, "line0\nline1\nline2"); + SourceStore::setSource(ref(2, 0, 0, 0), "line0\nline1\nline2"); ASSERT_TRUE(SourceStore::hasSource()); EXPECT_EQ(SourceStore::file(), "top.sv"); - EXPECT_EQ(SourceStore::targetLine(), 2); + EXPECT_EQ(SourceStore::targetLine(), 2u); ASSERT_EQ(SourceStore::lines().size(), 3u); EXPECT_EQ(SourceStore::lines()[0], "line0"); EXPECT_EQ(SourceStore::lines()[1], "line1"); @@ -24,7 +43,7 @@ TEST_F(SourceStoreTest, SplitsLfSeparatedLines) { } TEST_F(SourceStoreTest, StripsTrailingCarriageReturn) { - SourceStore::setSource("top.sv", 0, "line0\r\nline1\r\n"); + SourceStore::setSource(ref(0, 0, 0, 0), "line0\r\nline1\r\n"); ASSERT_EQ(SourceStore::lines().size(), 3u); EXPECT_EQ(SourceStore::lines()[0], "line0"); EXPECT_EQ(SourceStore::lines()[1], "line1"); @@ -32,29 +51,149 @@ TEST_F(SourceStoreTest, StripsTrailingCarriageReturn) { } TEST_F(SourceStoreTest, EmptyTextYieldsOneEmptyLine) { - SourceStore::setSource("top.sv", 0, ""); + SourceStore::setSource(ref(0, 0, 0, 0), ""); ASSERT_EQ(SourceStore::lines().size(), 1u); EXPECT_EQ(SourceStore::lines()[0], ""); } TEST_F(SourceStoreTest, ClearResetsState) { - SourceStore::setSource("top.sv", 5, "a\nb"); + SourceStore::setReferences("u1", json{{"kind", "instance"}}, {ref(5, 0, 0, 0)}); + SourceStore::setSource(ref(5, 0, 0, 0), "a\nb"); SourceStore::clear(); EXPECT_FALSE(SourceStore::hasSource()); + EXPECT_FALSE(SourceStore::hasReferences()); + EXPECT_TRUE(SourceStore::references().empty()); EXPECT_EQ(SourceStore::file(), ""); EXPECT_TRUE(SourceStore::lines().empty()); - EXPECT_EQ(SourceStore::targetLine(), 0); + EXPECT_EQ(SourceStore::targetLine(), 0u); } TEST_F(SourceStoreTest, PendingScrollIsConsumedExactlyOnce) { - SourceStore::setSource("top.sv", 3, "a\nb"); + SourceStore::setSource(ref(3, 0, 0, 0), "a\nb"); EXPECT_TRUE(SourceStore::takePendingScroll()); EXPECT_FALSE(SourceStore::takePendingScroll()); } TEST_F(SourceStoreTest, NewSourceRearmsPendingScroll) { - SourceStore::setSource("a.sv", 1, "x"); + SourceStore::setSource(ref(1, 0, 0, 0), "x"); EXPECT_TRUE(SourceStore::takePendingScroll()); - SourceStore::setSource("b.sv", 2, "y"); + SourceStore::setSource(ref(2, 0, 0, 0), "y"); EXPECT_TRUE(SourceStore::takePendingScroll()); } + +TEST_F(SourceStoreTest, LoadRequestNamesTheObjectAndTheReference) { + json address = {{"kind", "term"}, {"id_path", {0}}, {"terminal", "a"}, {"bit", 1}}; + SourceStore::setReferences("u_alu/a[1]", address, + {ref(4, 3, 4, 39), ref(2, 14, 2, 24, "origin", 0)}); + json declaration = SourceStore::loadRequest(0); + EXPECT_EQ(declaration["request"], "load_source"); + EXPECT_EQ(declaration["role"], "declaration"); + EXPECT_FALSE(declaration.contains("index")); + EXPECT_EQ(declaration["terminal"], "a"); + EXPECT_EQ(declaration["bit"], 1); + json origin = SourceStore::loadRequest(1); + EXPECT_EQ(origin["role"], "origin"); + EXPECT_EQ(origin["index"], 0); + EXPECT_EQ(origin["id_path"], json::array({0})); +} + +TEST_F(SourceStoreTest, ShownReferenceFollowsTheLoadedFile) { + SourceStore::setReferences("g0", json::object(), + {ref(4, 3, 4, 39), ref(2, 14, 2, 24, "origin", 0), + ref(1, 12, 1, 24, "origin", 1)}); + EXPECT_FALSE(SourceStore::shownReference().has_value()); + SourceStore::setSource(ref(1, 12, 1, 24, "origin", 1), "x"); + EXPECT_EQ(SourceStore::shownReference(), 2u); + SourceStore::setMissing(ref(4, 3, 4, 39)); + EXPECT_TRUE(SourceStore::missing()); + EXPECT_FALSE(SourceStore::hasSource()); + EXPECT_EQ(SourceStore::shownReference(), 0u); +} + +TEST_F(SourceStoreTest, HighlightsTheColumnsOfASingleLineRange) { + // Columns are 1-based, inclusive, in bytes: 14..24 is "a[0] & a[1]". + SourceStore::setSource(ref(2, 14, 2, 24), + "module alu(input [1:0] a, output y);\n assign y = a[0] & a[1];\n"); + EXPECT_FALSE(SourceStore::highlightBytes(0).has_value()); + auto span = SourceStore::highlightBytes(1); + ASSERT_TRUE(span.has_value()); + EXPECT_EQ(SourceStore::lines()[1].substr(span->first, span->second - span->first), "a[0] & a[1]"); + EXPECT_FALSE(SourceStore::highlightBytes(2).has_value()); + EXPECT_TRUE(SourceStore::inRange(2)); + EXPECT_FALSE(SourceStore::inRange(1)); +} + +TEST_F(SourceStoreTest, HighlightsAMultiLineRangeFromStartColumnToEndColumn) { + SourceStore::setSource(ref(1, 3, 3, 2), "abcdef\nghijkl\nmnopqr"); + EXPECT_EQ(SourceStore::highlightBytes(0), std::make_pair(size_t(2), size_t(6))); + EXPECT_EQ(SourceStore::highlightBytes(1), std::make_pair(size_t(0), size_t(6))); + EXPECT_EQ(SourceStore::highlightBytes(2), std::make_pair(size_t(0), size_t(2))); +} + +TEST_F(SourceStoreTest, UnknownEndHighlightsOnlyTheStartLine) { + // A Yosys src with no end: line 2 from column 3, nothing invented after. + SourceStore::setSource(ref(2, 3, 0, 0), "abc\ndefgh\nijk"); + EXPECT_FALSE(SourceStore::highlightBytes(0).has_value()); + EXPECT_EQ(SourceStore::highlightBytes(1), std::make_pair(size_t(2), size_t(5))); + EXPECT_FALSE(SourceStore::highlightBytes(2).has_value()); +} + +TEST_F(SourceStoreTest, WindowOfALargeFileKeepsFileLineNumbers) { + // Only lines 99..101 of the file were sent; the reference is line 100. + SourceStore::setSource(ref(100, 2, 100, 3), "a\nbcd\ne", /*firstLine=*/99, /*truncated=*/true); + EXPECT_TRUE(SourceStore::truncated()); + EXPECT_EQ(SourceStore::firstLine(), 99u); + EXPECT_FALSE(SourceStore::highlightBytes(0).has_value()); + EXPECT_EQ(SourceStore::highlightBytes(1), std::make_pair(size_t(1), size_t(3))); +} + +TEST_F(SourceStoreTest, ColumnsPastTheLineAreClamped) { + SourceStore::setSource(ref(1, 10, 1, 40), "short"); + EXPECT_EQ(SourceStore::highlightBytes(0), std::make_pair(size_t(5), size_t(5))); +} + +TEST_F(SourceStoreTest, GlyphIndexCountsCodePointsNotBytes) { + const std::string line = " y = a \xC2\xB5 b;"; // a two-byte character (micro sign) + EXPECT_EQ(SourceStore::glyphIndex(line, 0), 0u); + EXPECT_EQ(SourceStore::glyphIndex(line, 8), 8u); // up to the multi-byte character + EXPECT_EQ(SourceStore::glyphIndex(line, 10), 9u); // past it: one glyph for two bytes + EXPECT_EQ(SourceStore::glyphIndex(line, 99), 12u); // clamped to the line +} + +TEST_F(SourceStoreTest, EditorCommandQuotesThePath) { + EXPECT_EQ(SourceStore::editorCommand("code -g {file}:{line}:{col}", "/w/rtl/alu.v", 12, 5), + "code -g '/w/rtl/alu.v':12:5"); + // Unknown coordinates open at 1; quotes in the path can't break out. + EXPECT_EQ(SourceStore::editorCommand("vim +{line} {file}", "/w/it's here.v", 0, 0), + "vim +1 '/w/it'\\''s here.v'"); + EXPECT_EQ(SourceStore::editorCommand("subl {file}:{line}:{col} {", "a.v", 3, 0), + "subl 'a.v':3:1 {"); +} + +TEST_F(SourceStoreTest, KeepsTheHostPathOfTheShownFile) { + SourceStore::setSource(ref(1, 0, 0, 0), "x", 1, false, "/abs/top.sv"); + EXPECT_EQ(SourceStore::hostPath(), "/abs/top.sv"); + SourceStore::setMissing(ref(1, 0, 0, 0)); + EXPECT_EQ(SourceStore::hostPath(), ""); +} + +TEST_F(SourceStoreTest, EditorPatternFallsBackToWhatIsInstalled) { + const std::string app = "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"; + // A configured command always wins. + EXPECT_EQ(SourceStore::editorPattern("subl {file}:{line}", true, app, "open"), "subl {file}:{line}"); + EXPECT_EQ(SourceStore::editorPattern("", true, app, "open"), "code -g {file}:{line}:{col}"); + // VS Code installed without its `code` shell command: its bundled CLI, quoted. + EXPECT_EQ(SourceStore::editorPattern("", false, app, "open"), + "'" + app + "' -g {file}:{line}:{col}"); + EXPECT_EQ(SourceStore::editorPattern("", false, "", "xdg-open"), "xdg-open {file}"); +} + +TEST_F(SourceStoreTest, KeepsWhetherTheFileIsStale) { + SourceStore::setSource(ref(1, 0, 0, 0), "x", 1, false, "/abs/top.sv", /*stale=*/true); + EXPECT_TRUE(SourceStore::stale()); + SourceStore::setSource(ref(1, 0, 0, 0), "x"); + EXPECT_FALSE(SourceStore::stale()); + SourceStore::setSource(ref(1, 0, 0, 0), "x", 1, false, "", true); + SourceStore::clear(); + EXPECT_FALSE(SourceStore::stale()); +} diff --git a/tests/TypesJsonTest.cpp b/tests/TypesJsonTest.cpp index 6d6d8db..0e79826 100644 --- a/tests/TypesJsonTest.cpp +++ b/tests/TypesJsonTest.cpp @@ -15,7 +15,7 @@ TEST(DesignRefJson, ThrowsOnMissingField) { EXPECT_THROW(j.get(), json::exception); } -TEST(InstanceResponseJson, ParsesWithoutSourceLoc) { +TEST(InstanceResponseJson, ParsesWithoutSources) { json j = { {"name", "u1"}, {"model_name", "AND2"}, @@ -34,26 +34,62 @@ TEST(InstanceResponseJson, ParsesWithoutSourceLoc) { EXPECT_FALSE(r.has_instances); EXPECT_TRUE(r.has_terms); EXPECT_TRUE(r.has_nets); - EXPECT_FALSE(r.source_loc.has_value()); + EXPECT_FALSE(r.has_sources); } -TEST(InstanceResponseJson, ParsesSourceLocWithDefaults) { +TEST(InstanceResponseJson, ParsesHasSources) { json j = { {"design_ref", {{"db_id", 1}, {"library_id", 2}, {"design_id", 3}}}, {"has_primitives", false}, {"has_instances", false}, {"has_terms", false}, {"has_nets", false}, - {"source_loc", {{"file", "top.sv"}, {"line", 42}}}, + {"has_sources", true}, }; - InstanceResponseJson r = j.get(); - ASSERT_TRUE(r.source_loc.has_value()); - EXPECT_EQ(r.source_loc->file, "top.sv"); - EXPECT_EQ(r.source_loc->line, 42); - // end_line/column/end_column default from line/column when absent. - EXPECT_EQ(r.source_loc->endLine, 42); - EXPECT_EQ(r.source_loc->column, 0); - EXPECT_EQ(r.source_loc->endColumn, 0); + EXPECT_TRUE(j.get().has_sources); +} + +TEST(SourceRefJson, ParsesAReference) { + SourceRef r = parseSourceRef(json::parse(R"({"role": "origin", "index": 1, + "file": "rtl/alu.v", "line": 2, "column": 14, "end_line": 2, "end_column": 24, + "provider": "yosys", "language": "", "representation": ""})")); + EXPECT_EQ(r.role, "origin"); + EXPECT_EQ(r.index, 1u); + EXPECT_EQ(r.file, "rtl/alu.v"); + EXPECT_EQ(r.line, 2u); + EXPECT_EQ(r.column, 14u); + EXPECT_EQ(r.endLine, 2u); + EXPECT_EQ(r.endColumn, 24u); + EXPECT_EQ(r.provider, "yosys"); + EXPECT_EQ(r.language, ""); + EXPECT_EQ(sourceRefLocation(r), "rtl/alu.v:2:14"); +} + +TEST(SourceRefJson, MissingCoordinatesStayUnknown) { + // Unknown is 0 and an end is never made up from the start. + SourceRef r = parseSourceRef(json{{"role", "declaration"}, {"file", "top.sv"}, {"line", 42}}); + EXPECT_FALSE(r.index.has_value()); + EXPECT_EQ(r.line, 42u); + EXPECT_EQ(r.endLine, 0u); + EXPECT_EQ(r.column, 0u); + EXPECT_EQ(r.endColumn, 0u); + EXPECT_EQ(sourceRefLocation(r), "top.sv:42"); + EXPECT_EQ(sourceRefLocation(parseSourceRef(json::object())), ""); +} + +TEST(TermsResponseJson, ParsesHasSources) { + TermsResponseJson t = json::parse(R"({"gui_id": 1, "children": [ + {"name": "a", "child_id": 0, "direction": 0, "msb": 1, "lsb": 0, "has_sources": true}, + {"name": "y", "child_id": 1, "direction": 1}]})").get(); + EXPECT_TRUE(t.children[0].has_sources); + EXPECT_FALSE(t.children[1].has_sources); +} + +TEST(NetsResponseJson, ParsesHasSources) { + NetsResponseJson n = json::parse(R"({"gui_id": 1, "children": [ + {"name": "s", "msb": 1, "lsb": 0, "has_sources": true}, {"name": "n1"}]})").get(); + EXPECT_TRUE(n.children[0].has_sources); + EXPECT_FALSE(n.children[1].has_sources); } TEST(InstanceResponseJson, MissingNameDefaultsEmpty) { diff --git a/tests/VerilogLanguageTest.cpp b/tests/VerilogLanguageTest.cpp new file mode 100644 index 0000000..dfd933a --- /dev/null +++ b/tests/VerilogLanguageTest.cpp @@ -0,0 +1,63 @@ +#include "VerilogLanguage.h" + +#include +#include + +#include + +namespace { + +// How many glyphs of `text` a tokenizer takes from its start (0: none). +size_t taken(const std::function& f, + const std::string& text) { + std::vector glyphs; + for (unsigned char c : text) glyphs.emplace_back(static_cast(c)); + glyphs.emplace_back(ImWchar(0)); // something past the end, as in a line + TextEditor::Iterator begin(glyphs.data()); + TextEditor::Iterator end(glyphs.data() + text.size()); + return f(begin, end) - begin; +} + +size_t number(const std::string& s) { return taken(verilogLanguage()->getNumber, s); } +size_t identifier(const std::string& s) { return taken(verilogLanguage()->getIdentifier, s); } + +} // namespace + +TEST(VerilogLanguage, Numbers) { + EXPECT_EQ(number("42;"), 2u); + EXPECT_EQ(number("1_000 "), 5u); + EXPECT_EQ(number("8'hFF)"), 5u); + EXPECT_EQ(number("4'b10xz;"), 7u); + EXPECT_EQ(number("16'sd5,"), 6u); + EXPECT_EQ(number("8'h FF"), 6u); // white space after the base is legal + EXPECT_EQ(number("'h1F "), 4u); // unsized + EXPECT_EQ(number("'0;"), 2u); // unbased unsized + EXPECT_EQ(number("'x"), 2u); + EXPECT_EQ(number("1.5e-3 "), 6u); + EXPECT_EQ(number("3.14"), 4u); + EXPECT_EQ(number("2'"), 1u); // a size alone: the quote isn't the number's + EXPECT_EQ(number("'{a, b}"), 0u); // an assignment pattern + EXPECT_EQ(number("a1"), 0u); +} + +TEST(VerilogLanguage, Identifiers) { + EXPECT_EQ(identifier("data_in[3]"), 7u); + EXPECT_EQ(identifier("u1$2 "), 4u); + EXPECT_EQ(identifier("$display("), 8u); + EXPECT_EQ(identifier("\\a/b[0] .Y"), 7u); // escaped: up to white space + EXPECT_EQ(identifier("$ "), 0u); + EXPECT_EQ(identifier("3x"), 0u); +} + +TEST(VerilogLanguage, KeywordsAndTypes) { + const auto* l = verilogLanguage(); + EXPECT_EQ(l->name, "Verilog"); + EXPECT_TRUE(l->keywords.count("module")); + EXPECT_TRUE(l->keywords.count("always_ff")); + EXPECT_TRUE(l->keywords.count("nand")); // a built-in gate + EXPECT_TRUE(l->declarations.count("wire")); + EXPECT_TRUE(l->declarations.count("logic")); + EXPECT_TRUE(l->identifiers.count("$clog2")); + EXPECT_EQ(l->preprocess, ImWchar('`')); + EXPECT_EQ(l->singleLineComment, "//"); +} diff --git a/tests/data/source_protocol_cases.json b/tests/data/source_protocol_cases.json new file mode 100644 index 0000000..aec7f67 --- /dev/null +++ b/tests/data/source_protocol_cases.json @@ -0,0 +1,150 @@ +{ + "comment": [ + "Source-reference cases both backends must answer the same way: run by", + "python/tests/test_source_references.py against protocol.py and by", + "tests/provider/LocalSNLProviderProtocolTest.cpp against LocalSNLProvider,", + "same format as anonymous_protocol_cases.json except for the design: the", + "runners load `design.verilog` with naja's Verilog loader, Yosys src", + "decoding on, from `source_root` (tests/data/sources) as the current", + "directory and as their source root, so recorded paths are relative.", + "gate/alu_syn.v is a Yosys-style gate netlist: every object gets its", + "netlist declaration (provider naja-verilog) and its (* src *) attributes", + "become ordered origins (provider yosys) into rtl/alu.v, which is there,", + "and rtl/top.v, which isn't, and /synth/machine/rtl/alu.v, a path from", + "another machine that `source_map` rewrites (FROM -> TO, TO relative to", + "tests/data). \"$design:\" stands for the design_ref of the design", + "of that name, the top or any model below it." + ], + "source_root": "sources", + "source_map": [["/synth/machine", "sources"]], + "design": {"verilog": "gate/alu_syn.v", "top": "top"}, + "cases": [ + {"name": "the root design flags its definition", + "request": {"request": "load_root"}, + "expect": [{"response": "root_response", "root": {"name": "top", "has_sources": true}}]}, + + {"name": "an instance flags its instantiation", + "request": {"request": "load_instances", "design_ref": "$design:top", "gui_id": 1}, + "expect": [{"response": "instances_response", "gui_id": 1, "children": [ + {"name": "u_alu", "child_id": 0, "has_sources": true}]}]}, + + {"name": "gates flag their netlist declaration, the anonymous one included", + "request": {"request": "load_primitives", "design_ref": "$design:alu", "gui_id": 2}, + "expect": [{"response": "primitives_response", "gui_id": 2, "children": [ + {"name": "g0", "child_id": 0, "has_sources": true}, + {"name": "", "child_id": 1, "has_sources": true}, + {"name": "g2", "child_id": 2, "has_sources": true}]}]}, + + {"name": "terms flag their declaration", + "request": {"request": "load_terms", "design_ref": "$design:alu", "gui_id": 3}, + "expect": [{"response": "terms_response", "gui_id": 3, "children": [ + {"name": "a", "has_sources": true}, + {"name": "y", "has_sources": true}]}]}, + + {"name": "a gate lists its netlist declaration, then its Yosys origins in order", + "request": {"request": "get_sources", "kind": "instance", "path": ["u_alu", "g0"]}, + "expect": [{"response": "sources_response", "subject": "u_alu/g0", + "kind": "instance", "path": ["u_alu", "g0"], "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 10, "column": 7, + "end_line": 10, "end_column": 8, + "provider": "naja-verilog", "language": "verilog", "representation": ""}, + {"role": "origin", "index": 0, "file": "rtl/alu.v", "line": 2, "column": 14, + "end_line": 2, "end_column": 24, "provider": "yosys", "language": "", "representation": ""}, + {"role": "origin", "index": 1, "file": "rtl/alu.v", "line": 1, "column": 12, + "end_line": 1, "end_column": 24, "provider": "yosys", "language": "", "representation": ""}]}]}, + + {"name": "an anonymous gate by id_path: its type span, and an origin with no end", + "request": {"request": "get_sources", "kind": "instance", "id_path": [0, 1]}, + "expect": [{"response": "sources_response", "subject": "u_alu/<#1>", "id_path": [0, 1], "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 12, "column": 3, + "end_line": 12, "end_column": 5}, + {"role": "origin", "index": 0, "file": "rtl/alu.v", "line": 2, "column": 14, + "end_line": 0, "end_column": 0}]}]}, + + {"name": "a gate whose origin was recorded on another machine", + "request": {"request": "get_sources", "kind": "instance", "id_path": [0, 2]}, + "expect": [{"response": "sources_response", "subject": "u_alu/g2", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 14, "column": 7}, + {"role": "origin", "index": 0, "file": "/synth/machine/rtl/alu.v", "line": 2, "column": 14}]}]}, + + {"name": "a hierarchical instance lists its instantiation, origins, then its model's definition", + "request": {"request": "get_sources", "kind": "instance", "id_path": [0]}, + "expect": [{"response": "sources_response", "subject": "u_alu", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 21, "column": 7}, + {"role": "origin", "index": 0, "file": "rtl/top.v", "line": 2, "column": 3}, + {"role": "model_declaration", "file": "gate/alu_syn.v", "line": 2, "column": 1, + "end_line": 2, "end_column": 10}]}]}, + + {"name": "the top design lists its definition", + "request": {"request": "get_sources", "kind": "instance", "id_path": []}, + "expect": [{"response": "sources_response", "subject": "top", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 17}]}]}, + + {"name": "a term lists its port declaration and its origin", + "request": {"request": "get_sources", "kind": "term", "id_path": [0], "terminal": "y"}, + "expect": [{"response": "sources_response", "subject": "u_alu/y", "terminal": "y", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 6, "column": 3, "end_column": 10}, + {"role": "origin", "index": 0, "file": "rtl/alu.v", "line": 1, "column": 27}]}]}, + + {"name": "a bus term bit with no reference of its own lists its bus's", + "request": {"request": "get_sources", "kind": "term", "id_path": [0], "terminal": "a", "bit": 0}, + "expect": [{"response": "sources_response", "subject": "u_alu/a[0]", "bit": 0, "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 4, "column": 3, "end_column": 15}, + {"role": "origin", "index": 0, "file": "rtl/alu.v", "line": 1, "column": 12}]}]}, + + {"name": "a net lists its wire declaration", + "request": {"request": "get_sources", "kind": "net", "id_path": [0], "net": "n1"}, + "expect": [{"response": "sources_response", "subject": "u_alu/n1", "net": "n1", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 7, "column": 3, "end_column": 10}]}]}, + + {"name": "a bus net bit lists its bus's", + "request": {"request": "get_sources", "kind": "net", "id_path": [0], "net": "s", "bit": 1}, + "expect": [{"response": "sources_response", "subject": "u_alu/s[1]", "sources": [ + {"role": "declaration", "file": "gate/alu_syn.v", "line": 8}]}]}, + + {"name": "an unknown terminal lists nothing", + "request": {"request": "get_sources", "kind": "term", "id_path": [0], "terminal": "nope"}, + "expect": [{"response": "sources_response", "sources": []}]}, + + {"name": "an unresolvable path lists nothing", + "request": {"request": "get_sources", "kind": "instance", "id_path": [7]}, + "expect": [{"response": "sources_response", "subject": "", "sources": []}]}, + + {"name": "load_source reads an origin's file, found under the source root", + "request": {"request": "load_source", "kind": "instance", "id_path": [0, 0], + "role": "origin", "index": 0}, + "expect": [{"response": "source_response", "found": true, "role": "origin", "index": 0, + "file": "rtl/alu.v", "line": 2, "column": 14, "end_line": 2, "end_column": 24, + "first_line": 1, "truncated": false, "stale": false, + "text": "module alu(input [1:0] a, output y);\n assign y = a[0] & a[1];\nendmodule\n"}]}, + + {"name": "load_source reads a file recorded on another machine through the source map", + "request": {"request": "load_source", "kind": "instance", "id_path": [0, 2], + "role": "origin", "index": 0}, + "expect": [{"response": "source_response", "found": true, + "file": "/synth/machine/rtl/alu.v", "line": 2, "first_line": 1, + "text": "module alu(input [1:0] a, output y);\n assign y = a[0] & a[1];\nendmodule\n"}]}, + + {"name": "load_source reads a gate's netlist declaration", + "request": {"request": "load_source", "kind": "instance", "path": ["u_alu", "g0"], + "role": "declaration"}, + "expect": [{"response": "source_response", "found": true, "file": "gate/alu_syn.v", "line": 10, + "provider": "naja-verilog", "first_line": 1}]}, + + {"name": "load_source reports a referenced file that is not there", + "request": {"request": "load_source", "kind": "instance", "id_path": [0], + "role": "origin", "index": 0}, + "expect": [{"response": "source_response", "found": false, "file": "rtl/top.v", "text": ""}]}, + + {"name": "load_source with no such reference reads nothing", + "request": {"request": "load_source", "kind": "instance", "id_path": [0, 0], + "role": "origin", "index": 5}, + "expect": [{"response": "source_response", "found": false, "text": ""}], + "absent": ["file"]}, + + {"name": "load_source never reads a path the viewer names", + "request": {"request": "load_source", "file": "rtl/alu.v", "line": 1}, + "expect": [{"response": "source_response", "found": false, "text": ""}], + "absent": ["file"]} + ] +} diff --git a/tests/data/sources/gate/alu_syn.v b/tests/data/sources/gate/alu_syn.v new file mode 100644 index 0000000..91cde1e --- /dev/null +++ b/tests/data/sources/gate/alu_syn.v @@ -0,0 +1,22 @@ +(* src = "rtl/alu.v:1.1-3.9" *) +module alu(a, y); + (* src = "rtl/alu.v:1.12-1.24" *) + input [1:0] a; + (* src = "rtl/alu.v:1.27-1.34" *) + output y; + wire n1; + wire [1:0] s; + (* src = "rtl/alu.v:2.14-2.24|rtl/alu.v:1.12-1.24" *) + and g0 (y, a[0], a[1]); + (* src = "rtl/alu.v:2.14" *) + and (n1, a[0], a[1]); + (* src = "/synth/machine/rtl/alu.v:2.14-2.24" *) + and g2 (s[0], a[0], a[1]); +endmodule + +module top(x, z); + input [1:0] x; + output z; + (* src = "rtl/top.v:2.3-2.30" *) + alu u_alu (.a(x), .y(z)); +endmodule diff --git a/tests/data/sources/rtl/alu.v b/tests/data/sources/rtl/alu.v new file mode 100644 index 0000000..39c2d5c --- /dev/null +++ b/tests/data/sources/rtl/alu.v @@ -0,0 +1,3 @@ +module alu(input [1:0] a, output y); + assign y = a[0] & a[1]; +endmodule diff --git a/tests/provider/LocalSNLProviderProtocolTest.cpp b/tests/provider/LocalSNLProviderProtocolTest.cpp index 0bf2457..1f13718 100644 --- a/tests/provider/LocalSNLProviderProtocolTest.cpp +++ b/tests/provider/LocalSNLProviderProtocolTest.cpp @@ -1,10 +1,13 @@ // LocalSNLProvider against the wire-protocol cases shared with protocol.py -// (tests/data/anonymous_protocol_cases.json, also run by -// python/tests/test_anonymous_instances.py): both backends must give the -// same answers, notably for anonymous instances addressed by id_path. +// (tests/data/anonymous_protocol_cases.json and source_protocol_cases.json, +// also run by python/tests/test_anonymous_instances.py and +// test_source_references.py): both backends must give the same answers, +// notably for anonymous instances addressed by id_path and for source +// references. #include "LocalSNLProvider.h" #include +#include #include #include #include @@ -16,19 +19,25 @@ #include "NLUniverse.h" #include "NLDB.h" #include "NLLibrary.h" +#include "SNLBusNet.h" +#include "SNLBusNetBit.h" +#include "SNLBusTerm.h" +#include "SNLBusTermBit.h" #include "SNLDesign.h" #include "SNLInstance.h" #include "SNLInstTerm.h" +#include "SNLRTLInfos.h" #include "SNLScalarNet.h" #include "SNLScalarTerm.h" +#include "SNLVRLConstructor.h" using json = nlohmann::json; using namespace naja::NL; namespace { -json loadSpec() { - std::ifstream in(NAJA_SCHEMATIC_PROTOCOL_CASES); +json loadSpec(const std::string& name) { + std::ifstream in(std::string(NAJA_SCHEMATIC_TEST_DATA) + "/" + name); return json::parse(in); } @@ -38,22 +47,55 @@ SNLTerm::Direction direction(const std::string& d) { return SNLTerm::Direction::Input; } +// [name, direction] for a scalar term, [name, direction, msb, lsb] for a bus. +void createTerms(SNLDesign* d, const json& terms) { + for (const auto& t : terms) { + if (t.size() == 4) + SNLBusTerm::create(d, direction(t[1]), t[2].get(), t[3].get(), + NLName(t[0].get())); + else + SNLScalarTerm::create(d, direction(t[1]), NLName(t[0].get())); + } +} + +// Loads the spec'd gate-level Verilog (same as load_verilog_design() on the +// Python side): from `root` as the current directory, so recorded paths +// stay relative, Yosys src decoding on, into a DB of its own. Returns the +// top and every model below it by name. +std::map loadVerilogDesign(NLDB* db, const json& spec, + const std::filesystem::path& root) { + auto* lib = NLLibrary::create(db, NLName("case_work")); + SNLVRLConstructor ctor(lib); + ctor.config_.yosysSourceAttributes_ = true; + auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + ctor.construct(std::vector{spec["verilog"].get()}); + std::filesystem::current_path(previous); + std::map designs; + std::vector pending{lib->getSNLDesign(NLName(spec["top"].get()))}; + while (!pending.empty()) { + auto* design = pending.back(); + pending.pop_back(); + if (!designs.emplace(design->getName().getString(), design).second) continue; + for (auto* inst : design->getInstances()) pending.push_back(inst->getModel()); + } + return designs; +} + // Builds the spec'd netlist (same as build_design() on the Python side): // instances in list order, so their ids are their list indices. std::map buildDesign(NLDB* db, const json& spec) { std::map designs; - auto* prims = NLLibrary::create(db, NLLibrary::Type::Primitives, NLName("anon_prims")); + auto* prims = NLLibrary::create(db, NLLibrary::Type::Primitives, NLName("case_prims")); for (const auto& p : spec["primitives"]) { auto* d = SNLDesign::create(prims, SNLDesign::Type::Primitive, NLName(p["name"].get())); - for (const auto& t : p["terms"]) - SNLScalarTerm::create(d, direction(t[1]), NLName(t[0].get())); + createTerms(d, p["terms"]); designs[p["name"]] = d; } - auto* lib = NLLibrary::create(db, NLName("anon_work")); + auto* lib = NLLibrary::create(db, NLName("case_work")); for (const auto& s : spec["designs"]) { auto* d = SNLDesign::create(lib, NLName(s["name"].get())); - for (const auto& t : s["terms"]) - SNLScalarTerm::create(d, direction(t[1]), NLName(t[0].get())); + createTerms(d, s["terms"]); std::vector instances; for (const auto& i : s["instances"]) { const std::string name = i[0]; @@ -62,9 +104,15 @@ std::map buildDesign(NLDB* db, const json& spec) { instances.push_back(inst); } for (const auto& n : s["nets"]) { + if (n.contains("msb")) { + SNLBusNet::create(d, n["msb"].get(), n["lsb"].get(), + NLName(n["name"].get())); + continue; + } auto* net = SNLScalarNet::create(d, NLName(n["name"].get())); - d->getScalarTerm(NLName(n["term"].get()))->setNet(net); - for (const auto& pin : n["pins"]) { + if (n.contains("term")) + d->getScalarTerm(NLName(n["term"].get()))->setNet(net); + for (const auto& pin : n.value("pins", json::array())) { auto* inst = instances.at(pin[0].get()); inst->getInstTerm(inst->getModel()->getScalarTerm(NLName(pin[1].get())))->setNet(net); } @@ -137,59 +185,134 @@ std::string matchesUnordered(const json& expected, const json& actual, const std return ""; } -class ProviderProtocol : public ::testing::Test { - protected: - static void SetUpTestSuite() { - spec_ = new json(loadSpec()); +// One case file: its design built in a DB of its own, served by a provider +// of its own (with the file's source root, if any). +struct CaseSuite { + json spec; + std::map designs; + LocalSNLProvider* provider = nullptr; + json replies = json::array(); + + explicit CaseSuite(const std::string& file): spec(loadSpec(file)) { if (!NLUniverse::get()) NLUniverse::create(); auto* db = NLDB::create(NLUniverse::get()); - designs_ = new std::map(buildDesign(db, (*spec_)["design"])); - auto* top = designs_->at((*spec_)["design"]["top"]); + const std::string data = NAJA_SCHEMATIC_TEST_DATA; + designs = spec["design"].contains("verilog") + ? loadVerilogDesign(db, spec["design"], data + "/" + spec["source_root"].get()) + : buildDesign(db, spec["design"]); + auto* top = designs.at(spec["design"]["top"]); db->setTopDesign(top); NLUniverse::get()->setTopDesign(top); - provider_ = new LocalSNLProvider(); - provider_->setDB(db); - provider_->on_message([](const std::string& msg) { replies_.push_back(json::parse(msg)); }); + provider = new LocalSNLProvider(); + provider->setDB(db); + if (spec.contains("source_root")) + provider->addSourceRoot(data + "/" + spec["source_root"].get()); + for (const auto& m : spec.value("source_map", json::array())) + provider->addSourceMap(m[0].get(), data + "/" + m[1].get()); + provider->on_message([this](const std::string& msg) { replies.push_back(json::parse(msg)); }); } json ask(const json& request) { - replies_ = json::array(); - provider_->send(request.dump()); - return replies_; + replies = json::array(); + provider->send(request.dump()); + return replies; + } + + void runCases() { + ASSERT_FALSE(spec["cases"].empty()); + for (const auto& c : spec["cases"]) { + SCOPED_TRACE(c["name"].get()); + json got = ask(substitute(c["request"], designs)); + json expect = c["expect"]; + for (const auto& key : c.value("unordered", json::array())) { + ASSERT_FALSE(got.empty()); + json expectedList = expect[0][key.get()]; + expect[0].erase(key.get()); + EXPECT_EQ(matchesUnordered(expectedList, got[0].value(key.get(), json()), + "replies[0]." + key.get()), ""); + } + EXPECT_EQ(matches(expect, got, "replies"), ""); + for (const auto& key : c.value("absent", json::array())) { + ASSERT_FALSE(got.empty()); + EXPECT_FALSE(got[0].contains(key.get())) << key << " in " << got[0].dump(); + } + } } +}; + +class ProviderProtocol : public ::testing::Test { + protected: + static void SetUpTestSuite() { suite_ = new CaseSuite("anonymous_protocol_cases.json"); } + static CaseSuite* suite_; + LocalSNLProvider* provider_ = suite_->provider; +}; + +CaseSuite* ProviderProtocol::suite_ = nullptr; - static json* spec_; - static std::map* designs_; - static LocalSNLProvider* provider_; - static json replies_; +class SourceProtocol : public ::testing::Test { + protected: + static void SetUpTestSuite() { suite_ = new CaseSuite("source_protocol_cases.json"); } + static CaseSuite* suite_; }; -json* ProviderProtocol::spec_ = nullptr; -std::map* ProviderProtocol::designs_ = nullptr; -LocalSNLProvider* ProviderProtocol::provider_ = nullptr; -json ProviderProtocol::replies_ = json::array(); +CaseSuite* SourceProtocol::suite_ = nullptr; } // namespace TEST_F(ProviderProtocol, AnswersEverySharedCaseLikeProtocolPy) { - ASSERT_FALSE((*spec_)["cases"].empty()); - for (const auto& c : (*spec_)["cases"]) { - SCOPED_TRACE(c["name"].get()); - json replies = ask(substitute(c["request"], *designs_)); - json expect = c["expect"]; - for (const auto& key : c.value("unordered", json::array())) { - ASSERT_FALSE(replies.empty()); - json expectedList = expect[0][key.get()]; - expect[0].erase(key.get()); - EXPECT_EQ(matchesUnordered(expectedList, replies[0].value(key.get(), json()), - "replies[0]." + key.get()), ""); - } - EXPECT_EQ(matches(expect, replies, "replies"), ""); - for (const auto& key : c.value("absent", json::array())) { - ASSERT_FALSE(replies.empty()); - EXPECT_FALSE(replies[0].contains(key.get())) << key << " in " << replies[0].dump(); - } + suite_->runCases(); +} + +TEST_F(SourceProtocol, AnswersEverySharedCaseLikeProtocolPy) { + suite_->runCases(); +} + +// What a frontend predating typed references leaves: only the unclassified +// legacy location, listed as such (protocol.py: test_legacy_location_...). +// On the anonymous-instance design, which has no source information. +TEST_F(ProviderProtocol, ListsTheLegacyLocationWhenNoTypedReference) { + auto* mid = suite_->designs.at("mid"); + auto* net = mid->getNet(NLName("o")); + SNLRTLInfos::create(net)->setSourceLoc({NLName("a.sv"), 3, 4, 5, 0}); + json reply = suite_->ask({{"request", "get_sources"}, {"kind", "net"}, + {"id_path", {0}}, {"net", "o"}})[0]; + json nets = suite_->ask({{"request", "load_nets"}, {"gui_id", 0}, + {"design_ref", designRef(mid)}})[0]; + net->getRTLInfos()->destroy(); + EXPECT_EQ(reply["sources"], json::parse(R"([{"role": "legacy", "file": "a.sv", + "line": 3, "column": 5, "end_line": 4, "end_column": 0, + "provider": "", "language": "", "representation": ""}])")); + EXPECT_FALSE(nets["children"][0].contains("has_sources")); // net i + EXPECT_EQ(nets["children"][1]["has_sources"], true); // net o +} + +// A big file (a gate-level netlist) only sends the lines around the reference. +TEST_F(SourceProtocol, SendsAWindowOfALargeFile) { + auto dir = std::filesystem::temp_directory_path() / "naja_schematic_big_source"; + std::filesystem::create_directories(dir); + { + std::ofstream out(dir / "big.v"); + for (int n = 1; n <= 200000; ++n) out << "line " << n << " ..........................\n"; } + auto* top = suite_->designs.at("top"); + auto* infos = top->getRTLInfos(); + auto saved = *infos->getSourceDeclaration(); + SNLSourceReference ref = saved; + ref.range.file = NLName((dir / "big.v").string()); + ref.range.line = 100000; + ref.range.endLine = 100002; + infos->setSourceDeclaration(ref); + json reply = suite_->ask({{"request", "load_source"}, {"kind", "instance"}, + {"id_path", json::array()}, {"role", "declaration"}})[0]; + infos->setSourceDeclaration(saved); + std::filesystem::remove_all(dir); + EXPECT_EQ(reply["found"], true); + EXPECT_EQ(reply["stale"], true); // written after the design was loaded + EXPECT_EQ(reply["truncated"], true); + EXPECT_EQ(reply["first_line"], 99500); + std::string text = reply["text"]; + EXPECT_EQ(text.rfind("line 99500 ", 0), 0u); + EXPECT_EQ(std::count(text.begin(), text.end(), '\n'), 100502 - 99500 + 1); } // termRequest(): a terminal named by a script (headless --trace / @@ -222,3 +345,24 @@ TEST_F(ProviderProtocol, TermRequestRejectsWhatDoesNotResolve) { EXPECT_FALSE(provider_->termRequest(json{{"id_path", {3}}, {"terminal", "i"}, {"bit", 0}}, error)); // scalar EXPECT_FALSE(provider_->termRequest(json{{"id_path", {3}}}, error)); // no terminal } + +// A source map rewrites a prefix only at a path boundary, and a map that +// leads nowhere falls through to the next (protocol.py: test_source_maps). +TEST_F(SourceProtocol, SourceMapsMatchAtPathBoundaries) { + const std::string sources = std::string(NAJA_SCHEMATIC_TEST_DATA) + "/sources"; + auto ask = [&](const std::vector>& maps) { + LocalSNLProvider provider; + provider.setDB(suite_->designs.at("top")->getDB()); + for (const auto& [from, to] : maps) provider.addSourceMap(from, to); + json reply; + provider.on_message([&](const std::string& msg) { reply = json::parse(msg); }); + provider.send(json{{"request", "load_source"}, {"kind", "instance"}, {"id_path", {0, 2}}, + {"role", "origin"}, {"index", 0}}.dump()); + return reply["found"].get(); + }; + EXPECT_TRUE(ask({{"/synth/machine", sources}})); + EXPECT_TRUE(ask({{"/synth/machine/", sources + "/"}})); + EXPECT_FALSE(ask({{"/synth/mach", sources}})); // not a boundary + EXPECT_TRUE(ask({{"/synth", "/nowhere"}, {"/synth/machine", sources}})); // falls through + EXPECT_FALSE(ask({})); +} diff --git a/thirdparty/ImGuiColorTextEdit b/thirdparty/ImGuiColorTextEdit new file mode 160000 index 0000000..3837e43 --- /dev/null +++ b/thirdparty/ImGuiColorTextEdit @@ -0,0 +1 @@ +Subproject commit 3837e431b99f2b629951b7f3e4402761f1909d9c diff --git a/thirdparty/imgui b/thirdparty/imgui index 0f4b927..778992f 160000 --- a/thirdparty/imgui +++ b/thirdparty/imgui @@ -1 +1 @@ -Subproject commit 0f4b927e819823006a878bcb7fe93be129fd8d3f +Subproject commit 778992face67ecefaa5c2932db4b2101545ea4df diff --git a/thirdparty/naja b/thirdparty/naja index f96226d..5835954 160000 --- a/thirdparty/naja +++ b/thirdparty/naja @@ -1 +1 @@ -Subproject commit f96226daaaa29d65500f5e176115b9bb3a0f77ba +Subproject commit 583595484305a596ae2c555f0908826467d69377 From 5786d8aacd80bf097cf854dd1576a6ebde1ce1e0 Mon Sep 17 00:00:00 2001 From: xtof Date: Tue, 13 Oct 2026 11:10:50 +0200 Subject: [PATCH 2/2] Bump to 0.2.0; test the new minimum najaeda in CI 0.2.0: the source-references protocol replaces source_loc and load_source no longer reads a path the viewer names, and najaeda >= 0.7.29 is required. The CI matrix's oldest-najaeda entry follows pyproject.toml to 0.7.29. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/python-package.yml | 2 +- python/naja_schematic/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a9a7678..c104738 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -82,7 +82,7 @@ jobs: os: [ ubuntu-latest, macos-latest ] python-version: [ "3.10", "3.13" ] # Oldest najaeda the package declares support for, and the newest. - najaeda: [ "==0.7.27", "" ] + najaeda: [ "==0.7.29", "" ] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/python/naja_schematic/__init__.py b/python/naja_schematic/__init__.py index bacb2d1..9501b10 100644 --- a/python/naja_schematic/__init__.py +++ b/python/naja_schematic/__init__.py @@ -9,7 +9,7 @@ """ # Single version for the whole project: CMakeLists.txt reads this line into the # C++ app too (NAJA_SCHEMATIC_VERSION_STRING), so keep it a plain literal. -__version__ = "0.1.9" +__version__ = "0.2.0" from .protocol import diagnosis_response, handle_request from .server import ViewerServer