From cc1cdc7afbfceae56e42ba0f1e03d609e64eeb9a Mon Sep 17 00:00:00 2001 From: ZeroJustMe <619378845@qq.com> Date: Thu, 7 May 2026 12:36:00 +0000 Subject: [PATCH 01/15] Add persistent SageFlow ICPP demo --- .github/agents/icpp-demo.agent.md | 220 + .gitignore | 3 +- .../paper-icpp-demo/acmart-primary/.gitignore | 34 + .../acmart-primary/ACM-Reference-Format.bst | 3224 ++++++ docs/paper-icpp-demo/acmart-primary/Makefile | 187 + docs/paper-icpp-demo/acmart-primary/README | 412 + .../acmart-primary/References.bib | 54 + .../acmart-primary/acmart-tagged.cls | 3580 +++++++ .../paper-icpp-demo/acmart-primary/acmart.bib | 95 + .../paper-icpp-demo/acmart-primary/acmart.dtx | 9034 +++++++++++++++++ .../paper-icpp-demo/acmart-primary/acmart.ins | 31 + .../acmart-primary/acmauthoryear.bbx | 908 ++ .../acmart-primary/acmauthoryear.cbx | 219 + .../acmart-primary/acmdatamodel.dbx | 33 + .../acmart-primary/acmnumeric.bbx | 893 ++ .../acmart-primary/acmnumeric.cbx | 5 + .../data/prepared_demo_evidence.json | 76 + .../data/prepared_demo_run.json | 374 + .../figures/brisk_architecture.png | Bin 0 -> 1291942 bytes .../figures/brisk_demo_flow.png | Bin 0 -> 1046270 bytes .../generated/prepared_run_result_figure.tex | 46 + docs/paper-icpp-demo/acmart-primary/main.pdf | Bin 0 -> 2734830 bytes docs/paper-icpp-demo/acmart-primary/main.tex | 185 + .../scripts/generate_prepared_run_figure.py | 110 + sage_flow/__init__.py | 5 +- sage_flow/bindings.cpp | 296 + 26 files changed, 20022 insertions(+), 2 deletions(-) create mode 100644 .github/agents/icpp-demo.agent.md create mode 100644 docs/paper-icpp-demo/acmart-primary/.gitignore create mode 100644 docs/paper-icpp-demo/acmart-primary/ACM-Reference-Format.bst create mode 100644 docs/paper-icpp-demo/acmart-primary/Makefile create mode 100644 docs/paper-icpp-demo/acmart-primary/README create mode 100644 docs/paper-icpp-demo/acmart-primary/References.bib create mode 100644 docs/paper-icpp-demo/acmart-primary/acmart-tagged.cls create mode 100644 docs/paper-icpp-demo/acmart-primary/acmart.bib create mode 100644 docs/paper-icpp-demo/acmart-primary/acmart.dtx create mode 100644 docs/paper-icpp-demo/acmart-primary/acmart.ins create mode 100644 docs/paper-icpp-demo/acmart-primary/acmauthoryear.bbx create mode 100644 docs/paper-icpp-demo/acmart-primary/acmauthoryear.cbx create mode 100644 docs/paper-icpp-demo/acmart-primary/acmdatamodel.dbx create mode 100644 docs/paper-icpp-demo/acmart-primary/acmnumeric.bbx create mode 100644 docs/paper-icpp-demo/acmart-primary/acmnumeric.cbx create mode 100644 docs/paper-icpp-demo/acmart-primary/data/prepared_demo_evidence.json create mode 100644 docs/paper-icpp-demo/acmart-primary/data/prepared_demo_run.json create mode 100644 docs/paper-icpp-demo/acmart-primary/figures/brisk_architecture.png create mode 100644 docs/paper-icpp-demo/acmart-primary/figures/brisk_demo_flow.png create mode 100644 docs/paper-icpp-demo/acmart-primary/generated/prepared_run_result_figure.tex create mode 100644 docs/paper-icpp-demo/acmart-primary/main.pdf create mode 100644 docs/paper-icpp-demo/acmart-primary/main.tex create mode 100644 docs/paper-icpp-demo/acmart-primary/scripts/generate_prepared_run_figure.py diff --git a/.github/agents/icpp-demo.agent.md b/.github/agents/icpp-demo.agent.md new file mode 100644 index 0000000..da6cdce --- /dev/null +++ b/.github/agents/icpp-demo.agent.md @@ -0,0 +1,220 @@ +--- +name: icpp-demo +description: "ICPP demo 专用 Agent:用于 BriskSnapshot / SAGE + SageFlow 演示论文、pipeline、实时后端、准备证据和 UI 联调,保证 demo story、代码事实、实验口径与可运行性一致。" +argument-hint: "说明要处理的 demo 区域:paper / sage-examples pipeline / brisksnapshot-ui / sageFlow runtime / prepared evidence / live replay。" +tools: + - "vscode" + - "execute" + - "read" + - "agent" + - "edit" + - "search" + - "web" + - "todo" + - "vscode.mermaid-chat-features/renderMermaidDiagram" + - "ms-vscode.cpp-devtools/Build_CMakeTools" + - "ms-vscode.cpp-devtools/RunCtest_CMakeTools" + - "ms-vscode.cpp-devtools/ListBuildTargets_CMakeTools" + - "ms-vscode.cpp-devtools/ListTests_CMakeTools" +--- + +# ICPP Demo Agent + +## Use When + +- Developing the ICPP demo around `docs/paper-icpp-demo/` in `sageFlow`. +- Updating the BriskSnapshot paper, live demo script, prepared evidence, generated figures, or demo narrative. +- Coordinating `SAGE` core runtime/service surfaces, `sageFlow` vector-stream operators, `sage-examples` demo pipelines, and `brisksnapshot-ui`. +- Debugging the live replay path, JSON contract import/export, or UI panels for snapshot/routing/escalation contracts. + +## Mission + +Act as the demo integrator for **BriskSnapshot: Runtime-Backed Semantic Windows for Streaming AI Pipelines**. Keep the demo coherent across paper, code, evidence, and UI. The user should be able to rehearse the demo, regenerate artifacts, and explain every visible panel from code-backed facts. + +The central story is: + +1. A public vulnerability-intelligence stream arrives from sources such as NVD, vendor advisories, CISA KEV/alerts, and research notes. +2. SAGE normalizes and schedules the application workflow. +3. SageFlow provides vector-window operators such as join, filter, window, and ITopK for semantic-state materialization. +4. BriskSnapshot exports bounded, application-readable contracts: snapshot, routing, and escalation. +5. The UI shows both the live replay path and prepared variants for threshold, retention horizon, and arrival order. + +## Hard Guardrails + +- Never create `.venv` or `venv`; reuse the configured Conda or existing non-venv Python environment. +- Do not introduce new `ray` imports or dependencies. SAGE is FlowNet/stream-first, and this demo should stay local, service-shaped, and stream-first. +- Fail fast with actionable errors. Do not add silent fallback logic that hides missing `sage`, `sage_flow`, dataset, or UI backend problems. +- Do not add local editable installs of sibling SAGE subpackages to setup scripts or docs. Use explicit `PYTHONPATH` for local demo commands when needed. +- Keep SAGE core clean: do not move demo UI, app-specific code, or paper-only utilities into `SAGE/src/sage`. +- Keep claims honest. The current demo uses a long-lived SAGE `BaseService` wrapper and a persistent Python-side adapter, while SageFlow `StreamEnvironment` instances are still short-lived inside adapter calls. Do not claim a persistent native C++ SageFlow execution graph unless it has been implemented and validated. +- Treat prepared evidence as evidence, not as fake live execution. If numbers are shown in the paper/UI, they must be traceable to `prepared_demo_evidence.json`, `prepared_demo_run.json`, or an explicitly documented measurement script. + +## Repository Map + +### SAGE Core (`/root/SAGE`) + +Use SAGE only through stable public surfaces: + +- `sage.foundation`: function bases, configuration, `SagePorts`, user paths. +- `sage.runtime`: `LocalEnvironment`, `BaseService`, service registration, job execution. +- `sage.stream`: dataflow composition surface. +- `sage.serving`: serving integration and gateway helpers when needed. + +Respect the current 4-layer direction: + +- L4 application repos -> L3 `sage.cli` -> L2 `sage.runtime` / `sage.stream` -> L1 `sage.foundation`. +- Demo code belongs in application/demo repos, not in lower layers. + +Use `SagePorts` from `sage.foundation.config.ports` for SAGE-owned service ports. Legacy UI-specific ports are not part of SAGE foundation; BriskSnapshot's live backend currently uses `BRISKSNAPSHOT_LIVE_PORT` with default `8765` in the UI repo. + +### SageFlow Engine (`/root/sageFlow`) + +SageFlow is a C++20 vector-native stream processing engine for semantic state snapshots. Its demo-relevant operators are: + +- `Join`: vector similarity join over bounded windows. +- `Filter`: candidate filtering before downstream operators. +- `Window`: count/time-bounded state materialization. +- `ITopK`: representative context retrieval for routing-style demos. + +Key files to inspect before changing engine behavior: + +- `docs/JOIN_PIPELINE_GUIDE.md` +- `include/operator/join_operator.h` +- `src/operator/join_operator.cpp` +- `include/operator/join_operator_methods/` +- `include/operator/utils/join_strategy_config.h` +- `src/operator/join_strategy_factory.cpp` +- `src/operator/utils/join_config_validator.cpp` +- `test/test_utils/` + +Join and state invariants: + +- RoundRobin partitioning needs shared state for reliable recall. +- Partitioned strategies need matching partitioned state and index strategy. +- ClusteredJoin requires `num_partitions == parallelism`. +- All index operations must go through `ConcurrencyManager`; do not directly manipulate index internals. +- QIQ-style paths are experimental/unsafe unless explicitly enabled and justified. + +When changing SageFlow C++ build/test behavior, use the VS Code CMake Tools build/test tools. For Join-related changes, run the relevant Join integration tests, especially `test_join_datasource_modes` when datasource modes or Join pipeline behavior changes. + +### SAGE Examples Demo (`/root/sage-examples`) + +The ICPP demo's application contract lives in `sage-examples/apps/src/sage/apps/sageflow_service_demo/`. + +Important files: + +- `examples/run_sageflow_service_demo.py`: console/JSON entry point. +- `apps/src/sage/apps/sageflow_service_demo/models.py`: payload contracts shared with the UI. +- `apps/src/sage/apps/sageflow_service_demo/operators.py`: SAGE operators and service-call steps. +- `apps/src/sage/apps/sageflow_service_demo/workflow.py`: `LocalEnvironment`, `BaseService`, and pipeline wiring. +- `apps/src/sage/apps/sageflow_service_demo/adapter.py`: SageFlow adapter used by snapshot, triage, and security paths. +- `apps/src/sage/apps/sageflow_service_demo/demo_data.py`: demo datasets and replay ordering. + +Current pipeline facts: + +- Snapshot path: dual-source events -> normalize -> embed -> `SageFlowDualSourceSnapshotStep` -> derive correlated/emerging insights. +- Triage path: events -> normalize -> embed -> SageFlow Top-K context -> route decision. +- Security path: events -> normalize -> embed -> SageFlow security pattern query -> escalation signal. +- Embeddings are deterministic hashed vectors in `EmbedTextSignalStep`, so demo replay should be reproducible. +- `InMemorySageFlowSnapshotAdapter` delegates pair discovery and filtering/top-k steps to `sage_flow`, but it also keeps Python-side window lists and builds application snapshots in Python. + +Useful local command for JSON output: + +```bash +cd /root/sage-examples +PYTHONPATH=/root/SAGE/src:/root/sage-examples/apps/src:/root/sageFlow \ +python examples/run_sageflow_service_demo.py --json --dataset medium +``` + +### BriskSnapshot UI (`/root/brisksnapshot-ui`) + +The UI is the audience-facing console. Keep it as an operational demo surface, not a marketing landing page. + +Important files: + +- `src/types.ts`: canonical TypeScript contract for UI payloads. +- `src/App.tsx`: main React application and live API calls. +- `src/data/demoData.ts`: built-in scenario, variant metrics, pipeline panels, and prepared outcomes. +- `src/styles.css`: layout and visual system. +- `backend/live_demo_server.py`: lightweight live replay server that drives `InMemorySageFlowSnapshotAdapter` one event at a time. +- `README.md`: run/import instructions. + +Current live API facts: + +- Frontend expects `LIVE_API_ROOT = http://127.0.0.1:8765`. +- Backend endpoints: `GET /health`, `GET /api/live/state`, `POST /api/live/start`, `POST /api/live/reset`, `POST /api/live/stop`. +- Backend port is controlled by `BRISKSNAPSHOT_LIVE_PORT`, default `8765`. + +Useful commands: + +```bash +cd /root/brisksnapshot-ui +npm run backend +npm run dev -- --host 0.0.0.0 --port 4173 +npm run build +``` + +UI rules: + +- The first screen should be the usable demo console: replay controls, state counters, pipeline trace, runtime view, snapshot/clusters, outputs, and metrics. +- Preserve the data contract in `src/types.ts`; update the backend, prepared data, import parser, and UI together when a field changes. +- Do not add visible instructional copy that explains how the UI works unless the demo script explicitly needs it. Let controls and labels carry the interaction. +- Keep panels dense, readable, and stable under live updates. Avoid layout shift from changing labels, metric values, or long event IDs. +- When adding icons or controls, prefer familiar UI affordances and keep the interface suitable for a systems demo. + +### ICPP Paper and Prepared Evidence (`/root/sageFlow/docs/paper-icpp-demo`) + +Important files: + +- `acmart-primary/main.tex`: ACM demo paper. +- `acmart-primary/data/prepared_demo_run.json`: baseline replay output contract. +- `acmart-primary/data/prepared_demo_evidence.json`: prepared pipeline/variant metrics. +- `acmart-primary/generated/prepared_run_result_figure.tex`: generated TikZ figure. +- `acmart-primary/scripts/generate_prepared_run_figure.py`: generator for the prepared figure. +- `reference-icpp-style/`: style references for ICPP-style demo writing. + +Paper narrative rules: + +- The paper is about BriskSnapshot as a demo system, not a full SAGE or SageFlow architecture paper. +- Lead with the demo problem: downstream AI services need fresh, bounded semantic state from a streaming runtime. +- Use application-visible evidence: active window size, cluster count, source coverage, emitted contracts, throughput, end-to-end P95, and snapshot-export P95. +- Separate live replay claims from prepared measurement claims. +- Keep the three contracts consistent: snapshot, routing, escalation. +- Do not describe a feature as implemented unless the corresponding code path exists and can run. + +Prepared evidence rules: + +- Treat JSON inputs as the source of truth for generated figures. +- After changing `prepared_demo_run.json` or `prepared_demo_evidence.json`, regenerate `generated/prepared_run_result_figure.tex` with the script. +- Keep metrics synchronized across `main.tex`, generated figures, `src/data/demoData.ts`, and README/demo script notes. +- If numbers differ between generated files and source JSON, stop and reconcile before editing prose. + +## Development Workflow + +1. Identify which layer is affected: paper/evidence, SAGE app pipeline, SageFlow engine, UI backend, or UI frontend. +2. Read the nearest README and the concrete source files listed above before editing. +3. Make the smallest cross-repo change that keeps the demo story and contracts consistent. +4. For contract changes, update Python dataclasses/models, TypeScript types, prepared JSON, UI parsing, and paper tables together. +5. For runtime or adapter changes, verify both console JSON output and live backend behavior. +6. For UI changes, run `npm run build` and, when practical, start the backend and frontend to inspect the demo flow. +7. For paper/evidence changes, regenerate generated artifacts and ensure LaTeX-facing numbers match the JSON evidence. +8. Report exactly what was validated and what was not run. + +## Validation Checklist + +Use the narrowest validation that covers the change: + +- SAGE app pipeline: run `examples/run_sageflow_service_demo.py --json` with the local `PYTHONPATH` command. +- UI type/build changes: run `npm run build` in `brisksnapshot-ui`. +- Live backend changes: run `npm run backend` or `python backend/live_demo_server.py`, then check `/health` or the UI live replay. +- Prepared figure changes: run `python acmart-primary/scripts/generate_prepared_run_figure.py` from `docs/paper-icpp-demo` context or by absolute path. +- SageFlow C++ changes: use CMake Tools build/test; for Join changes include relevant Join integration/performance tests. +- Paper-only changes: inspect generated artifacts and compile/check LaTeX when the environment supports it. + +## Demo Quality Bar + +- A viewer should understand the causal path from event arrival to exported contract. +- Every metric shown in the UI or paper should have a source file or measurement path. +- Every UI panel should correspond to a live or prepared artifact, not decorative filler. +- The demo should remain replayable without hidden manual steps beyond documented commands. +- The final explanation should be able to answer: what state is maintained, who owns it, what contract is exported, and which downstream action consumes it. diff --git a/.gitignore b/.gitignore index 1ca2fd1..89fc76e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ log.cfg # Experiment outputs *.png +!docs/paper-icpp-demo/acmart-primary/figures/*.png *.tsv *.otf @@ -125,4 +126,4 @@ test/data/ # Task documentation (internal use only) docs/tasks/ -wheelhouse/ \ No newline at end of file +wheelhouse/ diff --git a/docs/paper-icpp-demo/acmart-primary/.gitignore b/docs/paper-icpp-demo/acmart-primary/.gitignore new file mode 100644 index 0000000..a39596f --- /dev/null +++ b/docs/paper-icpp-demo/acmart-primary/.gitignore @@ -0,0 +1,34 @@ +acmart.cls +acmart.pdf +acmguide.pdf +samples/sample-*.pdf +*.log +*.aux +*.cfg +*.glo +*.idx +*.toc +*.ilg +*.ind +*.out +*.lof +*.lot +*.bbl +*.blg +*.gls +*.cut +*.hd +*.dvi +*.ps +*.thm +*.tgz +*.zip +*.rpi +*~ +*.bcf +*.run.xml +samples/ACM-Reference-Format.bst +samples/*.tex +samples/*.bbx +samples/*.cbx +samples/*.dbx \ No newline at end of file diff --git a/docs/paper-icpp-demo/acmart-primary/ACM-Reference-Format.bst b/docs/paper-icpp-demo/acmart-primary/ACM-Reference-Format.bst new file mode 100644 index 0000000..ac35934 --- /dev/null +++ b/docs/paper-icpp-demo/acmart-primary/ACM-Reference-Format.bst @@ -0,0 +1,3224 @@ +%%% -*-BibTeX-*- +%%% ==================================================================== +%%% @BibTeX-style-file{ +%%% author = "Nelson H. F. Beebe, Boris Veytsman and Gerald Murray", +%%% version = "2.1", +%%% acmart-version = "1.90", +%%% date = "Mar 26 2023", +%%% filename = "ACM-Reference-Format.bst", +%%% email = "borisv@lk.net, boris@varphi.com", +%%% codetable = "ISO/ASCII", +%%% keywords = "ACM Transactions bibliography style; BibTeX", +%%% license = "public domain", +%%% supported = "yes", +%%% abstract = "", +%%% } +%%% ==================================================================== + +%%% Revision history: see source in git + +ENTRY + { address + advisor + archiveprefix + author + booktitle + chapter + city + date + edition + editor + eprint + eprinttype + eprintclass + howpublished + institution + journal + key + location + month + note + number + organization + pages + primaryclass + publisher + school + series + title + type + volume + year + % New keys recognized + issue % UTAH: used in, e.g., ACM SIGSAM Bulletin and ACM Communications in Computer Algebra + articleno + eid + day % UTAH: needed for newspapers, weeklies, bi-weeklies + doi % UTAH + url % UTAH + bookpages % UTAH + numpages + lastaccessed % UTAH: used only for @Misc{...} + coden % UTAH + isbn % UTAH + isbn-13 % UTAH + issn % UTAH + lccn % UTAH + distinctURL % whether to print url if doi is present + archived % Where the url is archived + venue % for presentations + } + {} + { label.year extra.label sort.year sort.label basic.label.year} + +INTEGERS { output.state before.all mid.sentence after.sentence after.block } + +INTEGERS { show-isbn-10-and-13 } % initialized below in begin.bib + +INTEGERS { nameptr namesleft numnames } + +INTEGERS { multiresult } + +INTEGERS { len } + +INTEGERS { last.extra.num } + +STRINGS { s t t.org u } + +STRINGS { last.label next.extra } + +STRINGS { p1 p2 p3 page.count } + + +FUNCTION { not } +{ + { #0 } + { #1 } + if$ +} + +FUNCTION { and } +{ + 'skip$ + { pop$ #0 } + if$ +} + +FUNCTION { or } +{ + { pop$ #1 } + 'skip$ + if$ +} + + +FUNCTION { dump.stack.1 } +{ + duplicate$ "STACK[top] = [" swap$ * "]" * warning$ +} + +FUNCTION { dump.stack.2 } +{ + duplicate$ "STACK[top ] = [" swap$ * "]" * warning$ + swap$ + duplicate$ "STACK[top-1] = [" swap$ * "]" * warning$ + swap$ +} + +FUNCTION { empty.or.unknown } +{ + %% Examine the top stack entry, and push 1 if it is empty, or + %% consists only of whitespace, or is a string beginning with two + %% queries (??), and otherwise, push 0. + %% + %% This function provides a replacement for empty$, with the + %% convenient feature that unknown values marked by two leading + %% queries are treated the same as missing values, and thus, do not + %% appear in the output .bbl file, and yet, their presence in .bib + %% file(s) serves to mark values which are temporarily missing, but + %% are expected to be filled in eventually once more data is + %% obtained. The TeX User Group and BibNet bibliography archives + %% make extensive use of this practice. + %% + %% An empty string cannot serve the same purpose, because just as in + %% statistics data processing, an unknown value is not the same as an + %% empty value. + %% + %% At entry: stack = ... top:[string] + %% At exit: stack = ... top:[0 or 1] + + duplicate$ empty$ + { pop$ #1 } + { #1 #2 substring$ "??" = } + if$ +} + +FUNCTION { empty.or.zero } +{ + %% Examine the top entry and push 1 if it is empty, or is zero + duplicate$ empty$ + { pop$ #1 } + { "0" = } + if$ +} + + +FUNCTION { writeln } +{ + %% In BibTeX style files, the sequences + %% + %% ... "one" "two" output + %% ... "one" "two" output.xxx + %% + %% ship "one" to the output file, possibly following by punctuation, + %% leaving the stack with + %% + %% ... "two" + %% + %% There is thus a one-string lag in output processing that must be + %% carefully handled to avoid duplicating a string in the output + %% file. Unless otherwise noted, all output.xxx functions leave + %% just one new string on the stack, and that model should be born + %% in mind when reading or writing function code. + %% + %% BibTeX's asynchronous buffering of output from strings from the + %% stack is confusing because newline$ bypasses the buffer. It + %% would have been so much easier for newline to be a character + %% rather than a state of the output-in-progress. + %% + %% The documentation in btxhak.dvi is WRONG: it says + %% + %% newline$ Writes onto the bbl file what's accumulated in the + %% output buffer. It writes a blank line if and only + %% if the output buffer is empty. Since write$ does + %% reasonable line breaking, you should use this + %% function only when you want a blank line or an + %% explicit line break. + %% + %% write$ Pops the top (string) literal and writes it on the + %% output buffer (which will result in stuff being + %% written onto the bbl file when the buffer fills + %% up). + %% + %% Examination of the BibTeX source code shows that write$ does + %% indeed behave as claimed, but newline$ sends a newline character + %% directly to the output file, leaving the stack unchanged. The + %% first line "Writes onto ... buffer." is therefore wrong. + %% + %% The original BibTeX style files almost always use "write$ newline$" + %% in that order, so it makes sense to hide that pair in a private + %% function like this one, named after a statement in Pascal, + %% the programming language embedded in the BibTeX Web program. + + write$ % output top-of-stack string + newline$ % immediate write of newline (not via stack) +} + +FUNCTION { init.state.consts } +{ + #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := +} + +FUNCTION { output.nonnull } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + ", " * write$ + } + { + output.state after.block = + { + add.period$ writeln + "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.dot.space } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = % { ". " * write$ } + { + ". " * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.remove } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + " " * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output.nonnull.removenospace } +{ % Stack in: ... R S T Stack out: ... R T File out: S + 's := + output.state mid.sentence = + { + "" * write$ + } + { + output.state after.block = + { + add.period$ writeln "\newblock " write$ + } + { + output.state before.all = + { + write$ + } + { + add.period$ " " * write$ + } + if$ + } + if$ + mid.sentence 'output.state := + } + if$ + s +} + +FUNCTION { output } +{ % discard top token if empty, else like output.nonnull + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull + if$ +} + +FUNCTION { output.dot.space } +{ % discard top token if empty, else like output.nonnull.dot.space + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull.dot.space + if$ +} + +FUNCTION { output.removenospace } +{ % discard top token if empty, else like output.nonnull.removenospace + duplicate$ empty.or.unknown + 'pop$ + 'output.nonnull.removenospace + if$ +} + +FUNCTION { output.check } +{ % like output, but warn if key name on top-of-stack is not set + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull + if$ +} + +FUNCTION { bibinfo.output.check } +{ % like output.check, adding bibinfo field + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + { "\bibinfo{" t "}{" * * swap$ * "}" * + output.nonnull } + if$ +} + +FUNCTION { output.check.dot.space } +{ % like output.dot.space, but warn if key name on top-of-stack is not set + 't := + duplicate$ empty.or.unknown + { pop$ "empty " t * " in " * cite$ * warning$ } + 'output.nonnull.dot.space + if$ +} + +FUNCTION { fin.block } +{ % functionally, but not logically, identical to fin.entry + add.period$ + writeln +} + +FUNCTION { fin.entry } +{ + add.period$ + writeln +} + +FUNCTION { new.sentence } +{ % update sentence state, with neither output nor stack change + output.state after.block = + 'skip$ + { + output.state before.all = + 'skip$ + { after.sentence 'output.state := } + if$ + } + if$ +} + +FUNCTION { fin.sentence } +{ + add.period$ + write$ + new.sentence + "" +} + +FUNCTION { new.block } +{ + output.state before.all = + 'skip$ + { after.block 'output.state := } + if$ +} + +FUNCTION { output.coden } % UTAH +{ % output non-empty CODEN as one-line sentence (stack untouched) + coden empty.or.unknown + { } + { "\showCODEN{" coden * "}" * writeln } + if$ +} + +% +% Sometimes articleno starts with the word 'Article' or 'Paper. +% (this is a bug of acmdl, sigh) +% We strip them. We assume eid or articleno is already on stack +% + +FUNCTION { strip.articleno.or.eid } +{ + 't := + t #1 #7 substring$ "Article" = + {t #8 t text.length$ substring$ 't :=} + { } + if$ + t #1 #7 substring$ "article" = + {t #8 t text.length$ substring$ 't :=} + { } + if$ + t #1 #5 substring$ "Paper" = + {t #6 t text.length$ substring$ 't :=} + { } + if$ + t #1 #5 substring$ "paper" = + {t #6 t text.length$ substring$ 't :=} + { } + if$ + % Strip any left trailing space or ~ + t #1 #1 substring$ " " = + {t #2 t text.length$ substring$ 't :=} + { } + if$ + t #1 #1 substring$ "~" = + {t #2 t text.length$ substring$ 't :=} + { } + if$ + t +} + + +FUNCTION { format.articleno } +{ + articleno empty.or.unknown not eid empty.or.unknown not and + { "Both articleno and eid are defined for " cite$ * warning$ } + 'skip$ + if$ + articleno empty.or.unknown eid empty.or.unknown and + { "" } + { + numpages empty.or.unknown + { "articleno or eid field, but no numpages field, in " + cite$ * warning$ } + { } + if$ + eid empty.or.unknown + { "Article \bibinfo{articleno}{" articleno strip.articleno.or.eid * "}" * } + { "Article \bibinfo{articleno}{" eid strip.articleno.or.eid * "}" * } + if$ + } + if$ +} + +FUNCTION { format.year } +{ % push year string or "[n.\,d.]" onto output stack + %% Because year is a mandatory field, we always force SOMETHING + %% to be output. If we do not know year but know date, we push date + %% as the last resort + "\bibinfo{year}{" + year empty.or.unknown + { date empty.or.unknown + { "[n.\,d.]" } + { date #1 #4 substring$ } + if$ + } + { year } + if$ + * "}" * +} + +FUNCTION { format.day.month } +{ % push "day month " or "month " or "" onto output stack + day empty.or.unknown + { + month empty.or.unknown + { "" } + { "\bibinfo{date}{" month * "} " *} + if$ + } + { + month empty.or.unknown + { "" } + { "\bibinfo{date}{" day * " " * month * "} " *} + if$ + } + if$ +} + +FUNCTION { format.day.month.year } % UTAH +{ % if month is empty, push "" else push "(MON.)" or "(DD MON.)" + % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) + % acm-*.bst addition: prefix parenthesized date string with + % ", Article nnn " + articleno empty.or.unknown eid empty.or.unknown and + { "" } + { output.state after.block = + {", " format.articleno * } + { format.articleno } + if$ + } + if$ + " (" * format.day.month * format.year * ")" * +} + +FUNCTION { output.day.month.year } % UTAH +{ % if month is empty value, do nothing; else output stack top and + % leave with new top string "(MON.)" or "(DD MON.)" + % Needed for frequent periodicals: 2008. ... New York Times C-1, C-2, C-17 (23 Oct.) + format.day.month.year + output.nonnull.remove +} + +FUNCTION { strip.doi } % UTAH +{ % Strip any Web address prefix to recover the bare DOI, leaving the + % result on the output stack, as recommended by CrossRef DOI + % documentation. + % For example, reduce "http://doi.acm.org/10.1145/1534530.1534545" to + % "10.1145/1534530.1534545". A suitable URL is later typeset and + % displayed as the LAST item in the reference list entry. Publisher Web + % sites wrap this with a suitable link to a real URL to resolve the DOI, + % and the master https://doi.org/ address is preferred, since publisher- + % specific URLs can disappear in response to economic events. All + % journals are encouraged by the DOI authorities to use that typeset + % format and link procedures for uniformity across all publications that + % include DOIs in reference lists. + % The numeric prefix is guaranteed to start with "10.", so we use + % that as a test. + % 2017-02-04 Added stripping of https:// (Boris) + doi #1 #3 substring$ "10." = + { doi } + { + doi 't := % get modifiable copy of DOI + + % Change https:// to http:// to strip both prefixes (BV) + + t #1 #8 substring$ "https://" = + { "http://" t #9 t text.length$ #8 - substring$ * 't := } + { } + if$ + + t #1 #7 substring$ "http://" = + { + t #8 t text.length$ #7 - substring$ 't := + + "INTERNAL STYLE-FILE ERROR" 's := + + % search for next "/" and assign its suffix to s + + { t text.length$ } + { + t #1 #1 substring$ "/" = + { + % save rest of string as true DOI (should be 10.xxxx/yyyy) + t #2 t text.length$ #1 - substring$ 's := + "" 't := % empty string t terminates the loop + } + { + % discard first character and continue loop: t <= substring(t,2,last) + t #2 t text.length$ #1 - substring$ 't := + } + if$ + } + while$ + + % check for valid DOI (should be 10.xxxx/yyyy) + s #1 #3 substring$ "10." = + { } + { "unrecognized DOI substring " s * " in DOI value [" * doi * "]" * warning$ } + if$ + + s % push the stripped DOI on the output stack + + } + { + "unrecognized DOI value [" doi * "]" * warning$ + doi % push the unrecognized original DOI on the output stack + } + if$ + } + if$ +} + +% +% Change by BV: added standard prefix to URL +% +FUNCTION { output.doi } % UTAH +{ % output non-empty DOI as one-line sentence (stack untouched) + doi empty.or.unknown + { } + { + "\href{https://doi.org/" strip.doi * "}{doi:\nolinkurl{" * + strip.doi * "}}" * writeln + } + if$ +} + +FUNCTION { output.isbn } % UTAH +{ % output non-empty ISBN-10 and/or ISBN-13 as one-line sentences (stack untouched) + show-isbn-10-and-13 + { + %% show both 10- and 13-digit ISBNs + isbn empty.or.unknown + { } + { + "\showISBNx{" isbn * "}" * writeln + } + if$ + isbn-13 empty.or.unknown + { } + { + "\showISBNxiii{" isbn-13 * "}" * writeln + } + if$ + } + { + %% show 10-digit ISBNs only if 13-digit ISBNs not available + isbn-13 empty.or.unknown + { + isbn empty.or.unknown + { } + { + "\showISBNx{" isbn * "}" * writeln + } + if$ + } + { + "\showISBNxiii{" isbn-13 * "}" * writeln + } + if$ + } + if$ +} + +FUNCTION { output.issn } % UTAH +{ % output non-empty ISSN as one-line sentence (stack untouched) + issn empty.or.unknown + { } + { "\showISSN{" issn * "}" * writeln } + if$ +} + +FUNCTION { output.issue } +{ % output non-empty issue number as a one-line sentence (stack untouched) + issue empty.or.unknown + { } + { "Issue " issue * "." * writeln } + if$ +} + +FUNCTION { output.lccn } % UTAH +{ % return with stack untouched + lccn empty.or.unknown + { } + { "\showLCCN{" lccn * "}" * writeln } + if$ +} + +FUNCTION { output.note } % UTAH +{ % return with stack empty + note empty.or.unknown + { } + { "\shownote{" note * "}" add.period$ * writeln } + if$ +} + +FUNCTION { output.note.check } % UTAH +{ % return with stack empty + note empty.or.unknown + { "empty note in " cite$ * warning$ } + { "\shownote{" note * "}" add.period$ * writeln } + if$ +} + +FUNCTION { output.eprint } % +{ % return with stack empty + eprint empty.or.unknown + { } + { "\showeprint" + archiveprefix empty.or.unknown + { eprinttype empty.or.unknown + { } + { "[" eprinttype "]" * * * } + if$ + } + { "[" archiveprefix "l" change.case$ "]" * * * } + if$ + "{" eprint "}" * * * + primaryclass empty.or.unknown + { eprintclass empty.or.unknown + { } + { "~[" eprintclass "]" * * * } + if$ + } + { "~[" primaryclass "]" * * * } + if$ + writeln + } + if$ +} + + +% +% Changes by BV 2011/04/15. Do not output +% url if doi is defined +% +% +% Changes by BV 2021/11/26. Output url even if doi is defined +% if distinctURL is not zero. +% +FUNCTION { output.url } % UTAH +{ % return with stack untouched + % output URL and associated lastaccessed fields + doi empty.or.unknown distinctURL empty.or.zero not or + { + url empty.or.unknown + { } + { new.sentence + %% Use \urldef, outside \showURL, so that %nn, #, etc in URLs work + %% correctly. Put the actual URL on its own line to reduce the + %% likelihood of BibTeX's nasty line wrapping after column 79. + %% \url{} can undo this, but if that doesn't work for some reason + %% the .bbl file would have to be repaired manually. + "\urldef\tempurl%" writeln + "\url{" url * "}" * writeln + + "\showURL{%" writeln + lastaccessed empty.or.unknown + { "" } + { "Retrieved " lastaccessed * " from " * } + if$ + "\tempurl" * + archived empty.or.unknown + { } + {"\urldef\tempurla%" writeln + "\url{" archived * "}%" * writeln + ", archived at [\tempurla]" * + } + if$ + "}" * writeln + } + if$ + } + { } + if$ +} + +FUNCTION { output.year.check } +{ % warn if year empty, output top string and leave " YEAR