diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..e5fe3642 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,48 @@ +name: Build & Deploy Docs + +on: + push: + branches: [main] + paths: ["sphinx-docs/**"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: pip install -r sphinx-docs/requirements.txt + + - name: Build HTML + run: sphinx-build -b html sphinx-docs sphinx-docs/_build/html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: sphinx-docs/_build/html + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index bf87b75d..c04defa6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ __pycache__ .release dist **/generated/timestamp.asm +sphinx-docs/_build/ **/generated/version.asm uv.lock diff --git a/sphinx-docs/Makefile b/sphinx-docs/Makefile new file mode 100644 index 00000000..0d4f9c25 --- /dev/null +++ b/sphinx-docs/Makefile @@ -0,0 +1,18 @@ +.PHONY: html pdf livehtml clean + +# Mermaid CLI needs a Chrome headless shell; point it at whatever is cached. +CHROME_HS := $(firstword $(wildcard $(HOME)/.cache/puppeteer/chrome-headless-shell/*/chrome-headless-shell-*/chrome-headless-shell)) +export PUPPETEER_EXECUTABLE_PATH := $(CHROME_HS) + +html: + uv run sphinx-build -b html . _build/html + +pdf: + uv run sphinx-build -b latex . _build/latex + cd _build/latex && xelatex f256-superbasic.tex && xelatex f256-superbasic.tex + +livehtml: + uv run sphinx-autobuild . _build/html --port 8000 + +clean: + rm -rf _build diff --git a/sphinx-docs/_static/custom.css b/sphinx-docs/_static/custom.css new file mode 100644 index 00000000..889fe673 --- /dev/null +++ b/sphinx-docs/_static/custom.css @@ -0,0 +1,133 @@ +/* Wildbits brand colors */ +:root { + --wb-navy: #272662; + --wb-red: #EE4025; + --wb-orange: #F1632B; + --wb-gold: #FDBB3A; + --wb-green: #44A348; + --wb-teal: #6BA7BB; +} + +/* Consistent table widths */ +table.docutils { + width: 100%; +} + +/* Mermaid diagram sizing */ +.mermaid { + margin: 0.75em 0; +} + +.mermaid svg { + max-height: 180px; +} + +/* Admonition styling — note */ +.admonition.note { + border-left: 4px solid var(--wb-navy); + background: color-mix(in srgb, var(--wb-navy) 8%, transparent); +} +.admonition.note > .admonition-title { + background: color-mix(in srgb, var(--wb-navy) 15%, transparent); + color: var(--wb-navy); +} + +/* Admonition styling — warning */ +.admonition.warning { + border-left: 4px solid var(--wb-orange); + background: color-mix(in srgb, var(--wb-orange) 8%, transparent); +} +.admonition.warning > .admonition-title { + background: color-mix(in srgb, var(--wb-orange) 15%, transparent); + color: var(--wb-orange); +} + +/* Admonition styling — tip / hint */ +.admonition.tip, .admonition.hint { + border-left: 4px solid var(--wb-green); + background: color-mix(in srgb, var(--wb-green) 8%, transparent); +} +.admonition.tip > .admonition-title, +.admonition.hint > .admonition-title { + background: color-mix(in srgb, var(--wb-green) 15%, transparent); + color: var(--wb-green); +} + +/* Admonition styling — danger / error */ +.admonition.danger, .admonition.error { + border-left: 4px solid var(--wb-red); + background: color-mix(in srgb, var(--wb-red) 8%, transparent); +} +.admonition.danger > .admonition-title, +.admonition.error > .admonition-title { + background: color-mix(in srgb, var(--wb-red) 15%, transparent); + color: var(--wb-red); +} + +/* Admonition styling — important */ +.admonition.important { + border-left: 4px solid var(--wb-gold); + background: color-mix(in srgb, var(--wb-gold) 8%, transparent); +} +.admonition.important > .admonition-title { + background: color-mix(in srgb, var(--wb-gold) 15%, transparent); + color: #8a6d00; +} + +/* Generic admonitions (custom title) */ +.admonition:not(.note):not(.warning):not(.tip):not(.hint):not(.danger):not(.error):not(.important) { + border-left: 4px solid var(--wb-teal); + background: color-mix(in srgb, var(--wb-teal) 8%, transparent); +} +.admonition:not(.note):not(.warning):not(.tip):not(.hint):not(.danger):not(.error):not(.important) > .admonition-title { + background: color-mix(in srgb, var(--wb-teal) 15%, transparent); + color: var(--wb-navy); +} + +/* Dark mode overrides */ +body[data-theme="dark"] .admonition.note { + background: color-mix(in srgb, var(--wb-navy) 15%, transparent); +} +body[data-theme="dark"] .admonition.note > .admonition-title { + background: color-mix(in srgb, var(--wb-navy) 25%, transparent); + color: var(--wb-teal); +} +body[data-theme="dark"] .admonition.warning { + background: color-mix(in srgb, var(--wb-orange) 12%, transparent); +} +body[data-theme="dark"] .admonition.warning > .admonition-title { + background: color-mix(in srgb, var(--wb-orange) 20%, transparent); + color: var(--wb-orange); +} +body[data-theme="dark"] .admonition.tip, +body[data-theme="dark"] .admonition.hint { + background: color-mix(in srgb, var(--wb-green) 12%, transparent); +} +body[data-theme="dark"] .admonition.tip > .admonition-title, +body[data-theme="dark"] .admonition.hint > .admonition-title { + background: color-mix(in srgb, var(--wb-green) 20%, transparent); + color: var(--wb-green); +} +body[data-theme="dark"] .admonition.danger, +body[data-theme="dark"] .admonition.error { + background: color-mix(in srgb, var(--wb-red) 12%, transparent); +} +body[data-theme="dark"] .admonition.danger > .admonition-title, +body[data-theme="dark"] .admonition.error > .admonition-title { + background: color-mix(in srgb, var(--wb-red) 20%, transparent); + color: var(--wb-red); +} +body[data-theme="dark"] .admonition.important { + background: color-mix(in srgb, var(--wb-gold) 12%, transparent); +} +body[data-theme="dark"] .admonition.important > .admonition-title { + background: color-mix(in srgb, var(--wb-gold) 20%, transparent); + color: var(--wb-gold); +} +body[data-theme="dark"] .admonition:not(.note):not(.warning):not(.tip):not(.hint):not(.danger):not(.error):not(.important) { + background: color-mix(in srgb, var(--wb-teal) 12%, transparent); +} +body[data-theme="dark"] .admonition:not(.note):not(.warning):not(.tip):not(.hint):not(.danger):not(.error):not(.important) > .admonition-title { + background: color-mix(in srgb, var(--wb-teal) 20%, transparent); + color: var(--wb-teal); +} diff --git a/sphinx-docs/conf.py b/sphinx-docs/conf.py new file mode 100644 index 00000000..678cb51e --- /dev/null +++ b/sphinx-docs/conf.py @@ -0,0 +1,155 @@ +# Configuration file for the Sphinx documentation builder. + +import sys +import os +sys.path.insert(0, os.path.abspath(".")) + +from superbasic_lexer import SuperBASICLexer +from sphinx.highlighting import lexers + +_lexer = SuperBASICLexer() +lexers["basic"] = _lexer +lexers["superbasic"] = _lexer + +project = "Wildbits SuperBASIC" +copyright = "2023-2026, Paul Robson & Wildbits Computing Company" +author = "Paul Robson & Wildbits Computing Company" +release = "1.1" + +extensions = [ + "myst_parser", + "sphinxcontrib.mermaid", + "sphinx_copybutton", + "sphinx_design", +] + +myst_enable_extensions = [ + "colon_fence", + "deflist", + "fieldlist", + "tasklist", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- + +html_theme = "furo" +html_static_path = ["_static"] +html_css_files = ["custom.css"] +html_title = "Wildbits SuperBASIC" + +html_theme_options = { + "navigation_with_keys": True, +} + +# -- Options for LaTeX output ------------------------------------------------ + +latex_documents = [ + ( + "index", + "f256-superbasic.tex", + "Wildbits SuperBASIC Reference Manual", + "Paul Robson", + "manual", + ), +] + +latex_elements = { + "papersize": "letterpaper", + "pointsize": "11pt", + "fncychap": r"\usepackage[Bjornstrup]{fncychap}", + "fontpkg": r""" +\usepackage{fontspec} +\setmainfont{NotoSerif}[ + Extension=.ttf, + UprightFont=*-Regular, + BoldFont=*-Bold, + ItalicFont=*-Italic, + BoldItalicFont=*-BoldItalic, +] +\setsansfont{NotoSans}[ + Extension=.ttf, + UprightFont=*-Regular, + BoldFont=*-Bold, + ItalicFont=*-Italic, + BoldItalicFont=*-BoldItalic, +] +\setmonofont{NotoSansMono}[ + Extension=.ttf, + UprightFont=*-Regular, + BoldFont=*-Bold, +] +""", + "geometry": r"\usepackage[letterpaper,inner=1.5in,outer=1.0in,top=0.75in,bottom=0.75in]{geometry}", + "preamble": r""" +% Match original reference manual styling +\definecolor{darkblue}{rgb}{0.1, 0.0, 0.6} +\definecolor{silver}{rgb}{0.85, 0.85, 0.85} +\ChNumVar{\color{darkblue}\fontsize{76}{80}\usefont{OT1}{pzc}{m}{n}\selectfont} +\ChTitleVar{\color{darkblue}\raggedleft\Huge\sffamily\bfseries} + +% Dark blue section headings +\usepackage{sectsty} +\allsectionsfont{\color{darkblue}\bfseries\sffamily} + +% Tighter TOC spacing +\usepackage{tocloft} +\setlength{\cftbeforechapskip}{6pt} +\setlength{\cftbeforesecskip}{2pt} +\renewcommand{\cftchapleader}{\cftdotfill{\cftdotsep}} + +% Reduce float spacing +\setlength{\floatsep}{8pt plus 2pt minus 2pt} +\setlength{\textfloatsep}{10pt plus 2pt minus 2pt} +\setlength{\intextsep}{8pt plus 2pt minus 2pt} + +% Black hyperlinks like the original +\hypersetup{colorlinks=true,linkcolor=black,urlcolor=darkblue} + +% Plain code blocks — no frame, no background (like the original verbatim style) +\sphinxsetup{ + VerbatimColor={rgb}{1,1,1}, + VerbatimBorderColor={rgb}{1,1,1}, + verbatimborder=0pt, +} +\fvset{fontsize=\small} +""", + "maketitle": r""" +\begin{titlepage} + \colorbox{silver}{\makebox[\textwidth][r]{ + \shortstack{ + \vspace{3cm} \\ + \color{darkblue}\bfseries\sffamily\Huge Wildbits SuperBASIC Reference Manual}} \\ + } + \vfill + \hfill\mbox{\color{darkblue}\bfseries\sffamily\Large Paul Robson} + \hfill\mbox{\color{darkblue}\bfseries\sffamily\large \today} +\end{titlepage} +""", + "tableofcontents": r"\sphinxtableofcontents", +} + +# -- Mermaid options --------------------------------------------------------- + +mermaid_init_js = """mermaid.initialize({ + startOnLoad: true, + theme: 'base', + themeVariables: { + primaryColor: '#272662', + primaryTextColor: '#fff', + primaryBorderColor: '#1a1a4a', + secondaryColor: '#F1632B', + secondaryTextColor: '#fff', + secondaryBorderColor: '#d14a1a', + tertiaryColor: '#44A348', + tertiaryTextColor: '#fff', + tertiaryBorderColor: '#358a38', + lineColor: '#272662', + textColor: '#272662', + nodeBorder: '#272662', + }, + themeCSS: '.node .label { color: #fff !important; } .edgeLabel { color: #272662 !important; }' +});""" +mermaid_pdfcrop = "pdfcrop" diff --git a/sphinx-docs/guide/assembler.md b/sphinx-docs/guide/assembler.md new file mode 100644 index 00000000..65779524 --- /dev/null +++ b/sphinx-docs/guide/assembler.md @@ -0,0 +1,45 @@ +# Inline Assembly + +SuperBASIC has a built-in inline assembler, closely modelled on that of BASIC in the British Acorn machines (Atom, BBC Micro, Archimedes). Assembled routines can be called via the `call` statement. + +## How It Works + +The instructions, named after their 65C02 opcode equivalents, generate code — so as a simple example the instruction `txa` generates the machine code `$8A` in memory. If the instruction has an operand (say) `lda #size*2` the expression is evaluated and the appropriate 2 bytes are stored in memory. + +Labels are specified using `.