Full Code of SaswatPadhi/pseudocode.js for AI

master 1fbd17c0100f cached
29 files
171.1 KB
47.6k tokens
16 symbols
1 requests
Download .txt
Repository: SaswatPadhi/pseudocode.js
Branch: master
Commit: 1fbd17c0100f
Files: 29
Total size: 171.1 KB

Directory structure:
gitextract_lpmuwsmr/

├── .eslintrc
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── docs/
│   ├── index.html
│   ├── javascripts/
│   │   └── scale.fix.js
│   ├── katex-samples.html
│   ├── mathjax-v2-samples.html
│   ├── mathjax-v3-samples.html
│   ├── mathjax-v4-samples.html
│   ├── params.json
│   ├── pseudocode.css
│   ├── pseudocode.js
│   └── stylesheets/
│       └── styles.css
├── package.json
├── pseudocode.js
├── src/
│   ├── Lexer.js
│   ├── ParseError.js
│   ├── Parser.js
│   ├── Renderer.js
│   └── utils.js
└── static/
    ├── body.html.part
    ├── footer.html.part
    ├── katex.html.part
    ├── mathjax-v2.html.part
    ├── mathjax-v3.html.part
    ├── mathjax-v4.html.part
    └── pseudocode.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc
================================================
{
    "rules": {
        "arrow-spacing": "error",
        "brace-style": ["error", "stroustrup"],
        "camelcase": "error",
        "comma-dangle": ["error", "always-multiline"],
        "comma-spacing": "error",
        "constructor-super": "error",
        "curly": ["error", "multi-or-nest", "consistent"],
        "eol-last": "error",
        "eqeqeq": ["error", "always"],
        "guard-for-in": "off",
        "indent": ["error", 4, {
            "CallExpression": {"arguments": "first"},
            "FunctionExpression": {"parameters": "first"},
            "flatTernaryExpressions": true,
            "SwitchCase": 1
        }],
        "keyword-spacing": "error",
        "linebreak-style": ["error", "unix"],
        "max-len": ["error", 128, 2, {
            "ignoreUrls": true,
            "ignorePattern": "\\brequire\\([\"']|eslint-disable",
            "ignoreComments": true
        }],
        "no-alert": "error",
        "no-array-constructor": "error",
        "no-console": "off",
        "no-const-assign": "error",
        "no-constant-condition": "off",
        "no-debugger": "error",
        "no-dupe-class-members": "error",
        "no-dupe-keys": "error",
        "no-duplicate-imports": "error",
        "no-extra-bind": "error",
        "no-new": "error",
        "no-new-func": "error",
        "no-new-object": "error",
        "no-spaced-func": "error",
        "no-this-before-super": "error",
        "no-throw-literal": "error",
        "no-trailing-spaces": "error",
        "no-undef": "off",
        "no-unexpected-multiline": "error",
        "no-unreachable": "error",
        "no-unused-vars": ["error", {"args": "none", "varsIgnorePattern": "^_*$"}],
        "no-useless-call": "error",
        "no-with": "error",
        "one-var": ["error", "never"],
        "prefer-const": "error",
        "prefer-spread": "error",
        "semi": ["error", "always"],
        "space-before-blocks": "error",
        "space-before-function-paren": ["error", "always"],
        "space-infix-ops": "error",
        "space-unary-ops": "error",
        "prefer-template": "error",
        "no-template-curly-in-string": "error",
        "template-curly-spacing": ["error", "never"],
        "arrow-parens": ["error", "always"],
        "arrow-body-style": "error",
        "prefer-arrow-callback": "error",
        "object-curly-spacing": ["error", "always"]
    },
    "env": {
        "es6": true,
        "node": true,
        "browser": true
    },
    "extends": "eslint:recommended",
    "root": true
}


================================================
FILE: .gitignore
================================================
_site
tex/
build/
node_modules/
static/katex/
static/fonts/
npm-debug.log


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2020-2023 Saswat Padhi (saswat.sourav@gmail.com)
Copyright (c) 2015-2019 Tate Tian (tatetian@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
.PHONY: all build clean docs default lint release

VERSION=2.4.1

# Building tools
BROWSERIFY = $(realpath ./node_modules/.bin/browserify)
CLEANCSS = $(realpath ./node_modules/.bin/cleancss)
ESLINT = $(realpath ./node_modules/.bin/eslint)
WATCHIFY = $(realpath ./node_modules/.bin/watchify)
UGLIFYJS = $(realpath ./node_modules/.bin/uglifyjs) \
	--mangle \
	--beautify \
	ascii_only=true,beautify=false

SAMPLES = build/katex-samples.html build/mathjax-v2-samples.html build/mathjax-v3-samples.html build/mathjax-v4-samples.html


default: build


all : clean
	@$(MAKE) --no-print-directory release


watch-js: pseudocode.js $(wildcard src/*.js)
	$(WATCHIFY) $< --standalone pseudocode -o build/pseudocode.js



build: build/pseudocode.js build/pseudocode.css $(SAMPLES)
	@echo "> Building succeeded\n"

build/pseudocode.js: pseudocode.js $(wildcard src/*.js)
	@$(MAKE) --no-print-directory lint
	$(BROWSERIFY) $< --exclude mathjax --exclude katex --standalone pseudocode -o $@

build/pseudocode.css: static/pseudocode.css
	cp static/pseudocode.css build/pseudocode.css

build/%-samples.html: static/%.html.part static/body.html.part static/footer.html.part
	cat $^ > $@



lint: pseudocode.js $(wildcard src/*.js)
	$(ESLINT) $^

fix-lint: pseudocode.js $(wildcard src/*.js)
	$(ESLINT) --fix $^



release: build docs build/pseudocode-js.tar.gz build/pseudocode-js.zip
	@echo "> Release package generated\n"

RELEASE_DIR=pseudocode.js-$(VERSION)/
build/pseudocode-js.tar.gz: build/$(RELEASE_DIR)
	cd build && tar czf pseudocode-js.tar.gz $(RELEASE_DIR)

build/pseudocode-js.zip: build/$(RELEASE_DIR)
	cd build && zip -rq pseudocode-js.zip $(RELEASE_DIR) || \
                7z a -r pseudocode-js.zip $(RELEASE_DIR)

build/$(RELEASE_DIR): build/pseudocode.js build/pseudocode.min.js build/pseudocode.css build/pseudocode.min.css $(SAMPLES) README.md
	mkdir -p build/$(RELEASE_DIR)
	cp -r $^ build/$(RELEASE_DIR)

build/pseudocode.min.js: build/pseudocode.js
	$(UGLIFYJS) < $< > $@

build/pseudocode.min.css: build/pseudocode.css
	$(CLEANCSS) -o $@ $<



docs: build/pseudocode.min.js build/pseudocode.min.css $(SAMPLES)
	cp build/pseudocode.min.css docs/pseudocode.css
	cp build/pseudocode.min.js docs/pseudocode.js
	cp $(SAMPLES) docs/



clean:
	@rm -rf build/*


================================================
FILE: README.md
================================================
# pseudocode.js

<img align="right" width="40%" src="docs/screenshot.png">

**pseudocode.js** is a JavaScript library that typesets pseudocode beautifully to 
HTML.

* _Intuitive grammar:_ Pseudocode.js takes a LaTeX-style input that supports 
  the algorithmic constructs from LaTeX's algorithm packages. With or without 
  LaTeX experience, a user should find the grammar fairly intuitive. 
* _Print quality:_ The HTML output produced by pseudocode.js is (almost) 
  identical with the pretty algorithms printed on publications that are 
  typeset by LaTeX.
* _Math formula support:_ Inserting math formulas in pseudocode.js is as easy 
  as LaTeX. Just enclose math expression in `$...$` or `\(...\)`.

It supports all modern browsers, including Chrome, Safari, Firefox, Edge, and Edge.

Visit the [project website](https://saswatpadhi.github.io/pseudocode.js) for a demo.


## Usage

### Quick Start

pseudocode.js can render math formulas using either
[KaTeX](https://github.com/Khan/KaTeX), or [MathJax](https://www.mathjax.org/).

#### Step 1A &middot; For KaTeX users
Include the following in the `<head>` of your page:

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"
        integrity="sha512-EKW5YvKU3hpyyOcN6jQnAxO/L8gts+YdYV6Yymtl8pk9YlYFtqJgihORuRoBXK8/cOIlappdU6Ms8KdK6yBCgA=="
        crossorigin="anonymous" referrerpolicy="no-referrer">
</script>
```

#### Step 1B &middot; For MathJax 2.x users
Include the following in the `<head>` of your page:

```html
<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.9/MathJax.js?config=TeX-AMS_CHTML-full"
        integrity="sha256-DViIOMYdwlM/axqoGDPeUyf0urLoHMN4QACBKyB58Uw="
        crossorigin="anonymous" referrerpolicy="no-referrer">
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
        tex2jax: {
            inlineMath: [['$','$'], ['\\(','\\)']],
            displayMath: [['$$','$$'], ['\\[','\\]']],
            processEscapes: true,
            processEnvironments: true,
        }
    });
</script>
```

> **Note**
> The `-full` configuration is larger and loads more extensions,
> but I recommend using it it just to avoid any hiccups later.
> You may want to use the standard configuration instead
> if you do not require additional packages.

#### Step 1C &middot; For MathJax 3.x users
Include the following in the `<head>` of your page:

```html
<script>
    MathJax = {
        tex: {
            inlineMath: [['$','$'], ['\\(','\\)']],
            displayMath: [['$$','$$'], ['\\[','\\]']],
            processEscapes: true,
            processEnvironments: true,
        }
    }
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-chtml-full.js"
        integrity="sha256-kbAFUDxdHwlYv01zraGjvjNZayxKtdoiJ38bDTFJtaQ="
        crossorigin="anonymous">
</script>
```

> **Note**
> The `-full` configuration is larger and loads more extensions,
> but I recommend using it it just to avoid any hiccups later.
> You may want to use the standard configuration instead
> if you do not require additional packages, such as `color`.

#### Step 2 &middot; Grab pseudocode.js
Include the following in the `<head>` of your page:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.css">
<script src="https://cdn.jsdelivr.net/npm/pseudocode@2.4.1/build/pseudocode.min.js">
</script>
```

You may also use the `latest` tag for pseudocode instead,
but jsDelivr might be delayed in updating the pointer for this tag.

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css">
<script src="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.js">
</script>
```

#### Step 3 &middot; Write your pseudocode inside a `<pre>`
We assume the pseudocode to be rendered is in a `<pre>` DOM element.
Here is an example that illustrates a quicksort algorithm:

```html
<pre id="quicksort" class="pseudocode">
    % This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
    \begin{algorithm}
    \caption{Quicksort}
    \begin{algorithmic}
    \PROCEDURE{Quicksort}{$A, p, r$}
        \IF{$p < r$} 
            \STATE $q = $ \CALL{Partition}{$A, p, r$}
            \STATE \CALL{Quicksort}{$A, p, q - 1$}
            \STATE \CALL{Quicksort}{$A, q + 1, r$}
        \ENDIF
    \ENDPROCEDURE
    \PROCEDURE{Partition}{$A, p, r$}
        \STATE $x = A[r]$
        \STATE $i = p - 1$
        \FOR{$j = p$ \TO $r - 1$}
            \IF{$A[j] < x$}
                \STATE $i = i + 1$
                \STATE exchange
                $A[i]$ with $A[j]$
            \ENDIF
            \STATE exchange $A[i]$ with $A[r]$
        \ENDFOR
    \ENDPROCEDURE
    \end{algorithmic}
    \end{algorithm}
</pre>
```

#### Step 4A &middot; Render the element using pseudocode.js
Insert the following Javascript snippet at the end of your document:

```html
<script>
    pseudocode.renderElement(document.getElementById("quicksort"));
</script>
```

#### Step 4B &middot; Render all elements of the class using pseudocode.js
Insert the following Javascript snippet at the end of your document:

```html
<script>
    pseudocode.renderClass("pseudocode");
</script>
```

### Grammar
There are several packages for typesetting algorithms in LaTeX, among which 
[`algorithmic`](http://mirror.ctan.org/tex-archive/macros/latex/contrib/algorithms/algorithms.pdf)
package is the most simple and intuitive, and is chosen by IEEE in its 
[LaTeX template file](http://www.ctan.org/tex-archive/macros/latex/contrib/IEEEtran). 
The grammar of pseudocode.js is mostly compatible with `algorithmic` package with 
a few improvement to make it even more easier to use.

Commands for typesetting algorithms must be enclosed in an `algorithmic` environment:
```tex
\begin{algorithmic}
# A precondition is optional
\REQUIRE <text>
# A postcondition is optional
\ENSURE <text>
# An input is optional
\INPUT <text>
# An output is optional
\OUTPUT <text>
# The body of your code is a <block>
\STATE ...
\end{algorithmic}
```

`<block>` can include zero or more `<statement>`, `<control>`,  `<comment>` 
and `<function>`:
```tex
# A <statement> can be:
\STATE <text>
\RETURN <text>
\PRINT <text>

# A <control> can be:
# A conditional
\IF{<condition>}
    <block>
\ELIF{<condition>}
    <block>
\ELSE
    <block>
\ENDIF
# Or a loop: \WHILE, \FOR or \FORALL
\WHILE{<condition>}
    <block>
\ENDWHILE
# Or a repeat: \REPEAT <block> \UNTIL{<cond>}
\REPEAT
    <block>
\UNTIL{<cond>}

# A <function> can by defined by either \FUNCTION or \PROCEDURE
# Both are exactly the same
\FUNCTION{<name>}{<params>}
    <block> 
\ENDFUNCTION

# A <comment> is:
\COMMENT{<text>}
```

A `<text>`, `<block>`, or `<condition>` may include the following:
```tex
% Normal characters
Hello world
% Escaped characters
\\, \{, \}, \$, \&, \#, \% and \_
% Math formula
$i \gets i + 1$
% Function call
\CALL{<func>}{<args>}
% Keywords
\AND, \OR, \XOR, \NOT, \TO, \DOWNTO, \TRUE, \FALSE, \BREAK, \CONTINUE
% LaTeX's sizing commands
\tiny, \scriptsize, \footnotesize, \small \normalsize, \large, \Large, \LARGE, 
\huge, \HUGE
% LaTeX's font declarations
\rmfamily, \sffamily, \ttfamily
\upshape, \itshape, \slshape, \scshape
\bfseries, \mdseries, \lfseries
% LaTeX's font commands
\textnormal{<text>}, \textrm{<text>}, \textsf{<text>}, \texttt{<text>}
\textup{<text>}, \textit{<text>}, \textsl{<text>}, \textsc{<text>}
\uppercase{<text>}, \lowercase{<text>}
\textbf, \textmd, \textlf
% And it's possible to group text with braces
normal text {\small the size gets smaller} back to normal again
```

> **Note**
> Although pseudocode.js recognizes some LaTeX commands, it is by no means a full-featured LaTeX implementation in JavaScript.
> It only support a subset of LaTeX commands that are most relevant to typesetting algorithms.


To display the caption of an algorithm, use `algorithm` environment as a 'float' wrapper :
```tex
\begin{algorithm}
\caption{The caption of your algorithm}
\begin{algorithmic}
\STATE ...
\end{algorithmic}
\end{algorithm}
```

### Options

#### Global Options

`pseudocode.renderElement` can accept an option object as the last argument, such as

```js
pseudocode.renderElement(document.getElementById("quicksort"),
                         { lineNumber: true });
```

The following options are currently supported:

 * `captionCount`:
   Reset the caption counter to this number, if defined.
 * `commentDelimiter`:
   The delimiters used to start and end a comment region.
   Note that only line comments are supported.
 * `indentSize`:
   The indent size of inside a control block, e.g. if, for etc.
   The unit must be in 'em'.
 * `lineNumber`:
   Whether line numbering is enabled.
 * `lineNumberPunc`:
   The punctuation that follows line number.
 * `noEnd`:
   Whether block ending, like `end if`, end procedure`, etc.
   are shown.
 * `scopeLines`:
   Whether vertical lines indicating the scopes of nested blocks
   are drawn.
 * `titlePrefix`:
   The title prefix (defaults to "Algorithm") for captions.

The default values of these options are:

```js
var DEFAULT_OPTIONS = {
    captionCount: undefined,
    commentDelimiter: '//',
    indentSize: '1.2em',
    lineNumber: false,
    lineNumberPunc: ':',
    noEnd: false,
    scopeLines: false,
    titlePrefix: 'Algorithm'
};
```

#### Per-Element Options

The above-mentioned global options may be overridden on a per-element basis
using [HTML `data-*` attributes](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset)
on the `<pre>` DOM element.

The following example demonstrates how to enable line numbers and change title prefix:

```html
<pre id="quicksort" class="pseudocode"
     data-line-number=true data-title-prefix="Algo">
   ...
</pre>
```

## Build and Test
pseudocode.js is written in JavaScript and built with [Node.js](https://nodejs.org).
So, make sure you have Node.js installed before building pseudocode.js.

To compile the project on Ubuntu Linux, run the following commands in terminal:

```bash
cd pseudocode.js/
npm install
make
```

Then, open one of the sample documents:
- `build/katex-samples.html`, or
- `build/mathjax-v2-samples.html`, or
- `build/mathjax-v3-samples.html`
in your favorite browser to check if the algorithms are typeset correctly.


## Author
pseudocode.js was originally written by Tate Tian ([@tatetian](https://github.com/tatetian)).
Together with [@ZJUGuoShuai](https://github.com/ZJUGuoShuai),
I ([@SaswatPadhi](https://github.com/SaswatPadhi)) added the MathJax support,
and I am the current maintainer of this project.
Suggestions, bug reports and pull requests are most welcome.

## Acknowledgement
pseudocode.js is partially inspired by [KaTeX](http://khan.github.io/KaTeX/).
Thanks Emily Eisenberg([@xymostech](https://github.com/xymostech))
and other contributors for building such a wonderful project.


================================================
FILE: docs/index.html
================================================
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">

    <title>pseudocode.js</title>

    <link rel="stylesheet" href="stylesheets/styles.css">
    <link rel="stylesheet" href="stylesheets/pygment_trac.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
          integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
          crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css"
          integrity="sha512-hasIneQUHlh06VNBe7f6ZcHmeRTLIaQWFd43YriJ0UND19bvYRauxthDg8E4eVNPm9bRUhr5JGeqH7FRFXQu5g=="
          crossorigin="anonymous" referrerpolicy="no-referrer" />
    <link rel="stylesheet" href="pseudocode.css">

    <script src="javascripts/autosize.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"
            integrity="sha512-rdhY3cbXURo13l/WU9VlaRyaIYeJ/KBakckXIvJNAQde8DgpOmE+eZf7ha4vdqVjTtwQt69bD2wH2LXob/LB7Q=="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"
            integrity="sha512-EKW5YvKU3hpyyOcN6jQnAxO/L8gts+YdYV6Yymtl8pk9YlYFtqJgihORuRoBXK8/cOIlappdU6Ms8KdK6yBCgA=="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>
    <script src="pseudocode.js"></script>

    <style type="text/css">
        body {
            color: #333;
            font-size: 18px;
        }
        h1 {
            font-size: 2em;
        }
        .demo-container {
            font-size: 1.2em;
            position: relative;
            padding-right: 2.4em;
        }
        .button {
            position: absolute;
            right: 0;
            top: -0.1em;
            font-size: 1.6em;
            color: #39c;
        }
        .demo-editor {
            width: 100%;
            min-height: 60px;
            border: none;
            vertical-align: top;
            font-size: 0.8em;
            margin: 0.8em 0;
            border-top: 3px solid #000;
            border-bottom: 3px solid #000;
            font-family: Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace; 
        }
        .demo-editor{
            font-size: 0.7em;
            display: none;
        }
        .demo-container.editing .btn-edit {
            display: none;
        }
        .btn-done {
            display: none;
        }
        .demo-container.editing .btn-done {
            display: inline;
        }
        .demo-container.editing .demo-editor {
            display: block;
        }
        .demo-container.editing .demo-result {
            display: none;
        }
    </style>
</head>
<body>
<div class="wrapper">
    <header>
        <h1>
            <a href="https://saswatpadhi.github.io/pseudocode.js/">pseudocode.js</a>
        </h1>
        <p>Beautiful pseudocode for the Web</p>

        <p class="view">
            <a href="https://github.com/SaswatPadhi/pseudocode.js">
                View the Project on GitHub 
                <small>SaswatPadhi/pseudocode.js</small>
            </a>
        </p>

        <ul>
            <li></li>
            <li>
                <a onclick="load('katex-samples.html')">
                    <strong>KaTeX</strong> Samples
                </a>
            </li>
            <li></li>
        </ul>

        <ul>
            <li>
                <a onclick="load('mathjax-v2-samples.html')">
                    <strong>MathJax 2</strong> Samples
                </a>
            </li>
            <li>
                <a onclick="load('mathjax-v3-samples.html')">
                    <strong>MathJax 3</strong> Samples
                </a>
            </li>
            <li>
                <a onclick="load('mathjax-v4-samples.html')">
                    <strong>MathJax 4</strong> Samples
                </a>
            </li>
        </ul>

        <ul>
            <li>
                <a href="https://github.com/SaswatPadhi/pseudocode.js/releases">
                    Download <strong>Releases</strong>
                </a>
            </li>
            <li>
                <a href="https://github.com/SaswatPadhi/pseudocode.js/zipball/master">
                    Download <strong>Source</strong>
                </a>
            </li>
            <li>
                <a href="https://github.com/SaswatPadhi/pseudocode.js">
                    View On <strong>GitHub</strong>
                </a>
            </li>
        </ul>
    </header>

    <section id='samples' style='display: none; padding-bottom: 0;'>
        <iframe id='samples-frame'>
        </iframe>
    </section>

    <section id='content'>
        <p>pseudocode.js enables JavaScript to typeset algorithms as <em>beautifully</em>
        as LaTeX does: </p>

        <div class="demo-container">
            <a class="btn-edit" href="#" onClick="toggleEdit(event)"><div class="fas fa-edit button"></div></a>
            <a class="btn-done" href="#" onClick="toggleEdit(event)"><div class="fas fa-caret-square-right button"></div></a>
            <div class="demo-result">
            </div>
            <textarea class="demo-editor">
% This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
\begin{algorithm}
\caption{Quicksort}
\begin{algorithmic}
\PROCEDURE{Quicksort}{$A, p, r$}
    \IF{$p < r$} 
        \STATE $q = $ \CALL{Partition}{$A, p, r$}
        \STATE \CALL{Quicksort}{$A, p, q - 1$}
        \STATE \CALL{Quicksort}{$A, q + 1, r$}
    \ENDIF
\ENDPROCEDURE
\PROCEDURE{Partition}{$A, p, r$}
    \STATE $x = A[r]$
    \STATE $i = p - 1$
    \FOR{$j = p$ \TO $r - 1$}
        \IF{$A[j] < x$}
            \STATE $i = i + 1$
            \STATE exchange
            $A[i]$ with     $A[j]$
        \ENDIF
        \STATE exchange $A[i]$ with $A[r]$
    \ENDFOR
\ENDPROCEDURE
\end{algorithmic}
\end{algorithm}</textarea>
        </div>
        
        <p> The demo above is editable. Feel free to experiment with it by clicking 
        on the edit button. </p>
        
        <h3>Features</h3>

        <p>pseudocode.js is a JavaScript library that typesets pseudocode 
        beautifully to HTML: </p>
        
        <ul>
            <li><strong>Intuitive grammar:</strong>
            pseudocode.js takes a LaTeX-style input that supports the 
            algorithmic constructs from LaTeX's algorithm packages. With or 
            without LaTeX experience, a user should find the grammar fairly intuitive. 
            </li>
            <li><strong>Print quality:</strong>
            The HTML output produced by pseudocode.js is (almost) identical 
            with the pretty algorithms printed on publications that
            are typeset by LaTeX.</li>
            <li><strong>Math formula support:</strong>
            Inserting math formulas in pseudocode.js is as easy as LaTeX. Just
            enclose math expression in <code>$...$</code> or 
            <code>\(...\)</code>.
            </li>
        </ul>
        </p>

        <p>It supports all modern browsers, including Chrome, Safari, Firefox, Opera, and Edge. </p>
        
        <h3>Usage</h3>

        <p style='text-align: justify;'>
            Pseudocode.js supports multiple backends to render math formulas.
            If you want to include any math formulas in your pseudocode,
            please make sure that either <a href="https://github.com/Khan/KaTeX#usage">KaTeX</a>
            or <a href="https://www.mathjax.org/#gettingstarted">MathJax</a>
            is properly setup in your document.
        </p>

        <p>
            Download <a href="https://github.com/SaswatPadhi/pseudocode.js/releases">pseudocode.js</a>,
            and host the files on your server. And then include the js and css files in your HTML files:
        </p>

        <pre><code class="language-html">&lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.css"&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/pseudocode@latest/build/pseudocode.min.js"&gt;&lt;/script&gt;</code></pre>

        <p>Place the pseudocode to be rendered in a <code>&lt;pre&gt;</code> element:</p>
        
        <pre><code class="language-html">&lt;pre id="hello-world-code"&gt;
    \begin{algorithmic}
    \PRINT \texttt{'hello world'}
    \end{algorithmic}
&lt;/pre&gt;</code></pre>

        <p>
            Finally, call <code>pseudocode.render</code> by placing the following Javascript snippet
            at the end of your document body:
        </p>

        <pre><code class="language-html">&lt;script&gt;
    pseudocode.renderElement(document.getElementById("hello-world-code"));
&lt;/script&gt;</code></pre>

        <p>
            For more details on available options and backends, please see the
            <a href="https://github.com/SaswatPadhi/pseudocode.js#usage">usage section of README</a>.
        </p>

        <h3>Author</h3>
        <p style='text-align: justify;'>
            pseudocode.js was originally written by Tate Tian (<a href="https://github.com/tatetian">@tatetian</a>).
            Together with <a href="https://github.com/ZJUGuoShuai">@ZJUGuoShuai</a>,
            I (<a href="https://github.com/SaswatPadhi">@SaswatPadhi</a>) added the MathJax support,
            and I am the current maintainer of this project.
            Suggestions, bug reports and pull requests are most welcome.
        </p>

        <h3>Acknowledgement</h3>
        <p style='text-align: justify;'>
            Pseudocode.js is partially inspired by <a href="http://khan.github.io/KaTeX/">KaTeX</a>
            and relies on it to render math formulas.
            Thanks Emily Eisenberg(<a href="https://github.com/xymostech">@xymostech</a>)
            and other contributers for building such a wonderful project.
        </p>
    </section>

    <footer>
        <p>Project Maintainer:
            <a href="https://github.com/SaswatPadhi">Saswat Padhi</a>
        </p>
        <p>
            <small>Hosted on GitHub Pages &mdash; Theme by 
                <a href="https://github.com/orderedlist">orderedlist</a>
            </small>
        </p>
    </footer>
</div>
<script src="javascripts/scale.fix.js"></script>
<script>
    hljs.highlightAll();

    function load(page) {
        document.getElementById('content').style.display = 'none';
        document.getElementById('samples').style.display = 'block';
        
        var frame = document.getElementById('samples-frame');
        frame.setAttribute('src', page);
        frame.height = window.innerHeight - frame.offsetTop - 32 + 'px';
    }

    var editorEl = document.getElementsByClassName('demo-editor')[0];

    function renderPseudocode() {
        var resultEl = document.getElementsByClassName('demo-result')[0];
        resultEl.innerHTML = '';
        var options = {
            captionCount: 0,
            lineNumber: true
        };
        pseudocode.render(editorEl.value, resultEl, options);
    }

    function toggleEdit(e) {
        if (e) e.preventDefault();

        var demoContainerEl = document.getElementsByClassName('demo-container')[0];
        var editing = demoContainerEl.classList.contains('editing');
        if (editing) {
            try {
                renderPseudocode();
            } catch(e) {
                console.log(e);
                return;
            }
            demoContainerEl.classList.remove('editing');
        }
        else {
            demoContainerEl.classList.add('editing');
            autosize(editorEl);
        }
    }

    renderPseudocode();
</script>
</body>
</html>


================================================
FILE: docs/javascripts/scale.fix.js
================================================
var metas = document.getElementsByTagName('meta');
var i;
if (navigator.userAgent.match(/iPhone/i)) {
  for (i=0; i<metas.length; i++) {
    if (metas[i].name == "viewport") {
      metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0";
    }
  }
  document.addEventListener("gesturestart", gestureStart, false);
}
function gestureStart() {
  for (i=0; i<metas.length; i++) {
    if (metas[i].name == "viewport") {
      metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6";
    }
  }
}

================================================
FILE: docs/katex-samples.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with KaTeX</title>

    <!-- Setup KaTeX -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"
            integrity="sha512-EKW5YvKU3hpyyOcN6jQnAxO/L8gts+YdYV6Yymtl8pk9YlYFtqJgihORuRoBXK8/cOIlappdU6Ms8KdK6yBCgA=="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>
</head>

<body>
    <pre id="test-basics" data-line-number=true>
        \begin{algorithm}
        \caption{Test text-style}
        \begin{algorithmic}
        \REQUIRE some preconditions
        \ENSURE some postconditions
        \INPUT some inputs
        \OUTPUT some outputs
        \PROCEDURE{Test-Declarations}{}
            \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
            \STATE font weights: {normal weight, \bfseries bold, \mdseries
            medium, \lfseries lighter. }
            \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
            \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
            footnotesize \small small \normalsize normal \large large \Large Large
            \LARGE LARGE \huge huge \Huge Huge \normalsize
        \ENDPROCEDURE
        \PROCEDURE{Test-Commands}{}
            \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
            \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
            \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
            \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
        \ENDPROCEDURE
        \PROCEDURE{Test-Colors}{}
            \STATE colors: $\color{red}{red}$, $\color{green}{green}$, $\color{blue}{blue}$
            \STATE colors: $\color{yellow}{yellow}$, $\color{cyan}{cyan}$, $\color{magenta}{magenta}$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}

        \begin{algorithm}
        \caption{Test atoms}
        \begin{algorithmic}
        \STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
        \STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
        \STATE \textbf{Carriage return:} first line \\ second line
        \STATE \textbf{Text-symbols:} \textbackslash
        \STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
        \STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
        \END{ALGORITHMIC}
        \END{ALGORITHM}
    </pre>
    <pre id="test-codes">
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELIF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELSE
                \STATE &lt;block&gt;
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Test-For}{$n$}
            \STATE $i \gets 0$
            \FOR{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-To}{$n$}
            \STATE $i \gets 0$
            \FOR{$i$ \TO $n$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-DownTo}{$n$}
            \FOR{$i \gets n$ \DOWNTO $0$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-All}{$n$}
            \FORALL{$i \in \{0, 1, \cdots, n\}$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-While}{$n$}
            \STATE $i \gets 0$
            \WHILE{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDWHILE
        \ENDPROCEDURE
        \PROCEDURE{Test-Repeat}{$n$}
            \STATE $i \gets 0$
            \REPEAT
                \PRINT $i$
                \STATE $i \gets i + 1$
            \UNTIL{$i>n$}
        \ENDPROCEDURE
        \PROCEDURE{Test-Break-Continue}{$n$}
            \FOR{$i = 0$ \TO $2n$}
                \IF{$i < n/2$}
                    \CONTINUE
                \ELIF{$i > n$}
                    \BREAK
                \ENDIF
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
        \begin{algorithm}
        \caption{Test statements and comments}
        \begin{algorithmic}
        \PROCEDURE{Test-Statements}{}
            \STATE This line is a normal statement
            \PRINT \texttt{`this is print statement'}
            \RETURN $retval$
        \ENDPROCEDURE

        \PROCEDURE{Test-Comments}{} \COMMENT{comment for procedure}
            \STATE a statement \COMMENT{inline comment}
            \STATE \COMMENT{line comment}
            \IF{some condition}\COMMENT{comment for if}
                \RETURN \TRUE \COMMENT{another inline comment}
            \ELSE \COMMENT{comment for else}
                \RETURN \FALSE \COMMENT{yet another inline comment}
            \ENDIF
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples" data-title-prefix="Procedure">
        % This quicksort algorithm is extracted from Chapter 7, Introduction
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$}
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples-custom-title">
        % This quicksort algorithm is extracted from Chapter 7, Introduction 
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$} 
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-line-number=true>
        \begin{algorithm}
        \caption{Classical Euclidean Algorithm}
        \begin{algorithmic}
            \PROCEDURE{Euclid}{$a,b$}
                \WHILE{$a \neq b$}
                    \IF{$a > b$}
                        \STATE $a \gets a - b$
                    \ELSE
                        \STATE $b \gets b - a$
                    \ENDIF
                \ENDWHILE
                \RETURN $a$
            \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-no-end=true>
        \begin{algorithm}
        \caption{DBSCAN}
        \begin{algorithmic}
        \INPUT A dataset $D$, the $\varepsilon$ distance threshold, and the minimum number of points $minPts$
        \OUTPUT A set of clusters $K$
        \PROCEDURE{DBSCAN}{$D, \varepsilon, minPts$}
            \STATE $K \gets \emptyset$
            \FORALL{$p \in D$}
                \IF{$p$ has not been visited}
                    \STATE mark $p$ as visited
                    \STATE $N_{\varepsilon}(p) \gets $ \textsc{RangeQuery}$(p, \varepsilon, D)$
                    \IF{$N_{\varepsilon}(p) < minPts$}
                        \STATE mark $p$ as noise
                    \ELSE
                        \COMMENT{p is a core object}
                        \STATE $C \gets $ \textsc{ExpandCluster}$(p, N_{\varepsilon}(p))$
                        \STATE $K \gets K \cup \{C\}$
                    \ENDIF
                \ENDIF
            \ENDFOR
            \RETURN $K$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <script>
        pseudocode.renderElement(document.getElementById("test-basics"),
                                 {
                                     lineNumber: false,
                                     noEnd: true
                                 });
        pseudocode.renderElement(document.getElementById("test-codes"));
        pseudocode.renderElement(document.getElementById("test-examples"),
                                 {
                                     lineNumber: true,
                                     noEnd: false
                                 });
        pseudocode.renderElement(document.getElementById("test-examples-custom-title"),
                                 {
                                     lineNumber: true,
                                     noEnd: false,
                                     titlePrefix: "My pretty Algorithm"
                                 });                                   
        pseudocode.renderClass("scopeline-pseudocode", { scopeLines: true });
    </script>
</body>
</html>


================================================
FILE: docs/mathjax-v2-samples.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.9/MathJax.js?config=TeX-AMS_CHTML-full"
            integrity="sha256-DViIOMYdwlM/axqoGDPeUyf0urLoHMN4QACBKyB58Uw="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>
    <script type="text/x-mathjax-config">
        MathJax.Hub.Config({
            tex2jax: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
                skipTags: ['code', 'script', 'noscript', 'style', 'textarea', 'pre'],
            }
        });
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>
</head>

<body>
    <pre id="test-basics" data-line-number=true>
        \begin{algorithm}
        \caption{Test text-style}
        \begin{algorithmic}
        \REQUIRE some preconditions
        \ENSURE some postconditions
        \INPUT some inputs
        \OUTPUT some outputs
        \PROCEDURE{Test-Declarations}{}
            \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
            \STATE font weights: {normal weight, \bfseries bold, \mdseries
            medium, \lfseries lighter. }
            \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
            \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
            footnotesize \small small \normalsize normal \large large \Large Large
            \LARGE LARGE \huge huge \Huge Huge \normalsize
        \ENDPROCEDURE
        \PROCEDURE{Test-Commands}{}
            \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
            \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
            \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
            \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
        \ENDPROCEDURE
        \PROCEDURE{Test-Colors}{}
            \STATE colors: $\color{red}{red}$, $\color{green}{green}$, $\color{blue}{blue}$
            \STATE colors: $\color{yellow}{yellow}$, $\color{cyan}{cyan}$, $\color{magenta}{magenta}$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}

        \begin{algorithm}
        \caption{Test atoms}
        \begin{algorithmic}
        \STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
        \STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
        \STATE \textbf{Carriage return:} first line \\ second line
        \STATE \textbf{Text-symbols:} \textbackslash
        \STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
        \STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
        \END{ALGORITHMIC}
        \END{ALGORITHM}
    </pre>
    <pre id="test-codes">
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELIF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELSE
                \STATE &lt;block&gt;
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Test-For}{$n$}
            \STATE $i \gets 0$
            \FOR{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-To}{$n$}
            \STATE $i \gets 0$
            \FOR{$i$ \TO $n$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-DownTo}{$n$}
            \FOR{$i \gets n$ \DOWNTO $0$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-All}{$n$}
            \FORALL{$i \in \{0, 1, \cdots, n\}$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-While}{$n$}
            \STATE $i \gets 0$
            \WHILE{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDWHILE
        \ENDPROCEDURE
        \PROCEDURE{Test-Repeat}{$n$}
            \STATE $i \gets 0$
            \REPEAT
                \PRINT $i$
                \STATE $i \gets i + 1$
            \UNTIL{$i>n$}
        \ENDPROCEDURE
        \PROCEDURE{Test-Break-Continue}{$n$}
            \FOR{$i = 0$ \TO $2n$}
                \IF{$i < n/2$}
                    \CONTINUE
                \ELIF{$i > n$}
                    \BREAK
                \ENDIF
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
        \begin{algorithm}
        \caption{Test statements and comments}
        \begin{algorithmic}
        \PROCEDURE{Test-Statements}{}
            \STATE This line is a normal statement
            \PRINT \texttt{`this is print statement'}
            \RETURN $retval$
        \ENDPROCEDURE

        \PROCEDURE{Test-Comments}{} \COMMENT{comment for procedure}
            \STATE a statement \COMMENT{inline comment}
            \STATE \COMMENT{line comment}
            \IF{some condition}\COMMENT{comment for if}
                \RETURN \TRUE \COMMENT{another inline comment}
            \ELSE \COMMENT{comment for else}
                \RETURN \FALSE \COMMENT{yet another inline comment}
            \ENDIF
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples" data-title-prefix="Procedure">
        % This quicksort algorithm is extracted from Chapter 7, Introduction
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$}
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples-custom-title">
        % This quicksort algorithm is extracted from Chapter 7, Introduction 
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$} 
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-line-number=true>
        \begin{algorithm}
        \caption{Classical Euclidean Algorithm}
        \begin{algorithmic}
            \PROCEDURE{Euclid}{$a,b$}
                \WHILE{$a \neq b$}
                    \IF{$a > b$}
                        \STATE $a \gets a - b$
                    \ELSE
                        \STATE $b \gets b - a$
                    \ENDIF
                \ENDWHILE
                \RETURN $a$
            \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-no-end=true>
        \begin{algorithm}
        \caption{DBSCAN}
        \begin{algorithmic}
        \INPUT A dataset $D$, the $\varepsilon$ distance threshold, and the minimum number of points $minPts$
        \OUTPUT A set of clusters $K$
        \PROCEDURE{DBSCAN}{$D, \varepsilon, minPts$}
            \STATE $K \gets \emptyset$
            \FORALL{$p \in D$}
                \IF{$p$ has not been visited}
                    \STATE mark $p$ as visited
                    \STATE $N_{\varepsilon}(p) \gets $ \textsc{RangeQuery}$(p, \varepsilon, D)$
                    \IF{$N_{\varepsilon}(p) < minPts$}
                        \STATE mark $p$ as noise
                    \ELSE
                        \COMMENT{p is a core object}
                        \STATE $C \gets $ \textsc{ExpandCluster}$(p, N_{\varepsilon}(p))$
                        \STATE $K \gets K \cup \{C\}$
                    \ENDIF
                \ENDIF
            \ENDFOR
            \RETURN $K$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <script>
        pseudocode.renderElement(document.getElementById("test-basics"),
                                 {
                                     lineNumber: false,
                                     noEnd: true
                                 });
        pseudocode.renderElement(document.getElementById("test-codes"));
        pseudocode.renderElement(document.getElementById("test-examples"),
                                 {
                                     lineNumber: true,
                                     noEnd: false
                                 });
        pseudocode.renderElement(document.getElementById("test-examples-custom-title"),
                                 {
                                     lineNumber: true,
                                     noEnd: false,
                                     titlePrefix: "My pretty Algorithm"
                                 });                                   
        pseudocode.renderClass("scopeline-pseudocode", { scopeLines: true });
    </script>
</body>
</html>


================================================
FILE: docs/mathjax-v3-samples.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script>
        MathJax = {
            tex: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
            }
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-chtml-full.js"
            integrity="sha256-kbAFUDxdHwlYv01zraGjvjNZayxKtdoiJ38bDTFJtaQ="
            crossorigin="anonymous">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>
</head>

<body>
    <pre id="test-basics" data-line-number=true>
        \begin{algorithm}
        \caption{Test text-style}
        \begin{algorithmic}
        \REQUIRE some preconditions
        \ENSURE some postconditions
        \INPUT some inputs
        \OUTPUT some outputs
        \PROCEDURE{Test-Declarations}{}
            \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
            \STATE font weights: {normal weight, \bfseries bold, \mdseries
            medium, \lfseries lighter. }
            \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
            \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
            footnotesize \small small \normalsize normal \large large \Large Large
            \LARGE LARGE \huge huge \Huge Huge \normalsize
        \ENDPROCEDURE
        \PROCEDURE{Test-Commands}{}
            \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
            \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
            \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
            \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
        \ENDPROCEDURE
        \PROCEDURE{Test-Colors}{}
            \STATE colors: $\color{red}{red}$, $\color{green}{green}$, $\color{blue}{blue}$
            \STATE colors: $\color{yellow}{yellow}$, $\color{cyan}{cyan}$, $\color{magenta}{magenta}$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}

        \begin{algorithm}
        \caption{Test atoms}
        \begin{algorithmic}
        \STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
        \STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
        \STATE \textbf{Carriage return:} first line \\ second line
        \STATE \textbf{Text-symbols:} \textbackslash
        \STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
        \STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
        \END{ALGORITHMIC}
        \END{ALGORITHM}
    </pre>
    <pre id="test-codes">
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELIF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELSE
                \STATE &lt;block&gt;
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Test-For}{$n$}
            \STATE $i \gets 0$
            \FOR{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-To}{$n$}
            \STATE $i \gets 0$
            \FOR{$i$ \TO $n$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-DownTo}{$n$}
            \FOR{$i \gets n$ \DOWNTO $0$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-All}{$n$}
            \FORALL{$i \in \{0, 1, \cdots, n\}$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-While}{$n$}
            \STATE $i \gets 0$
            \WHILE{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDWHILE
        \ENDPROCEDURE
        \PROCEDURE{Test-Repeat}{$n$}
            \STATE $i \gets 0$
            \REPEAT
                \PRINT $i$
                \STATE $i \gets i + 1$
            \UNTIL{$i>n$}
        \ENDPROCEDURE
        \PROCEDURE{Test-Break-Continue}{$n$}
            \FOR{$i = 0$ \TO $2n$}
                \IF{$i < n/2$}
                    \CONTINUE
                \ELIF{$i > n$}
                    \BREAK
                \ENDIF
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
        \begin{algorithm}
        \caption{Test statements and comments}
        \begin{algorithmic}
        \PROCEDURE{Test-Statements}{}
            \STATE This line is a normal statement
            \PRINT \texttt{`this is print statement'}
            \RETURN $retval$
        \ENDPROCEDURE

        \PROCEDURE{Test-Comments}{} \COMMENT{comment for procedure}
            \STATE a statement \COMMENT{inline comment}
            \STATE \COMMENT{line comment}
            \IF{some condition}\COMMENT{comment for if}
                \RETURN \TRUE \COMMENT{another inline comment}
            \ELSE \COMMENT{comment for else}
                \RETURN \FALSE \COMMENT{yet another inline comment}
            \ENDIF
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples" data-title-prefix="Procedure">
        % This quicksort algorithm is extracted from Chapter 7, Introduction
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$}
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples-custom-title">
        % This quicksort algorithm is extracted from Chapter 7, Introduction 
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$} 
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-line-number=true>
        \begin{algorithm}
        \caption{Classical Euclidean Algorithm}
        \begin{algorithmic}
            \PROCEDURE{Euclid}{$a,b$}
                \WHILE{$a \neq b$}
                    \IF{$a > b$}
                        \STATE $a \gets a - b$
                    \ELSE
                        \STATE $b \gets b - a$
                    \ENDIF
                \ENDWHILE
                \RETURN $a$
            \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-no-end=true>
        \begin{algorithm}
        \caption{DBSCAN}
        \begin{algorithmic}
        \INPUT A dataset $D$, the $\varepsilon$ distance threshold, and the minimum number of points $minPts$
        \OUTPUT A set of clusters $K$
        \PROCEDURE{DBSCAN}{$D, \varepsilon, minPts$}
            \STATE $K \gets \emptyset$
            \FORALL{$p \in D$}
                \IF{$p$ has not been visited}
                    \STATE mark $p$ as visited
                    \STATE $N_{\varepsilon}(p) \gets $ \textsc{RangeQuery}$(p, \varepsilon, D)$
                    \IF{$N_{\varepsilon}(p) < minPts$}
                        \STATE mark $p$ as noise
                    \ELSE
                        \COMMENT{p is a core object}
                        \STATE $C \gets $ \textsc{ExpandCluster}$(p, N_{\varepsilon}(p))$
                        \STATE $K \gets K \cup \{C\}$
                    \ENDIF
                \ENDIF
            \ENDFOR
            \RETURN $K$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <script>
        pseudocode.renderElement(document.getElementById("test-basics"),
                                 {
                                     lineNumber: false,
                                     noEnd: true
                                 });
        pseudocode.renderElement(document.getElementById("test-codes"));
        pseudocode.renderElement(document.getElementById("test-examples"),
                                 {
                                     lineNumber: true,
                                     noEnd: false
                                 });
        pseudocode.renderElement(document.getElementById("test-examples-custom-title"),
                                 {
                                     lineNumber: true,
                                     noEnd: false,
                                     titlePrefix: "My pretty Algorithm"
                                 });                                   
        pseudocode.renderClass("scopeline-pseudocode", { scopeLines: true });
    </script>
</body>
</html>


================================================
FILE: docs/mathjax-v4-samples.html
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script>
        MathJax = {
            tex: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
            }
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.3/tex-chtml.js"
            integrity="sha256-ljPODBK7Jf/VfUrVqec63xzZbysEmwB9Ab20TWRMQRU="
            crossorigin="anonymous">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>
</head>

<body>
    <pre id="test-basics" data-line-number=true>
        \begin{algorithm}
        \caption{Test text-style}
        \begin{algorithmic}
        \REQUIRE some preconditions
        \ENSURE some postconditions
        \INPUT some inputs
        \OUTPUT some outputs
        \PROCEDURE{Test-Declarations}{}
            \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
            \STATE font weights: {normal weight, \bfseries bold, \mdseries
            medium, \lfseries lighter. }
            \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
            \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
            footnotesize \small small \normalsize normal \large large \Large Large
            \LARGE LARGE \huge huge \Huge Huge \normalsize
        \ENDPROCEDURE
        \PROCEDURE{Test-Commands}{}
            \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
            \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
            \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
            \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
        \ENDPROCEDURE
        \PROCEDURE{Test-Colors}{}
            \STATE colors: $\color{red}{red}$, $\color{green}{green}$, $\color{blue}{blue}$
            \STATE colors: $\color{yellow}{yellow}$, $\color{cyan}{cyan}$, $\color{magenta}{magenta}$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}

        \begin{algorithm}
        \caption{Test atoms}
        \begin{algorithmic}
        \STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
        \STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
        \STATE \textbf{Carriage return:} first line \\ second line
        \STATE \textbf{Text-symbols:} \textbackslash
        \STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
        \STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
        \END{ALGORITHMIC}
        \END{ALGORITHM}
    </pre>
    <pre id="test-codes">
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELIF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELSE
                \STATE &lt;block&gt;
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Test-For}{$n$}
            \STATE $i \gets 0$
            \FOR{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-To}{$n$}
            \STATE $i \gets 0$
            \FOR{$i$ \TO $n$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-DownTo}{$n$}
            \FOR{$i \gets n$ \DOWNTO $0$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-All}{$n$}
            \FORALL{$i \in \{0, 1, \cdots, n\}$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-While}{$n$}
            \STATE $i \gets 0$
            \WHILE{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDWHILE
        \ENDPROCEDURE
        \PROCEDURE{Test-Repeat}{$n$}
            \STATE $i \gets 0$
            \REPEAT
                \PRINT $i$
                \STATE $i \gets i + 1$
            \UNTIL{$i>n$}
        \ENDPROCEDURE
        \PROCEDURE{Test-Break-Continue}{$n$}
            \FOR{$i = 0$ \TO $2n$}
                \IF{$i < n/2$}
                    \CONTINUE
                \ELIF{$i > n$}
                    \BREAK
                \ENDIF
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
        \begin{algorithm}
        \caption{Test statements and comments}
        \begin{algorithmic}
        \PROCEDURE{Test-Statements}{}
            \STATE This line is a normal statement
            \PRINT \texttt{`this is print statement'}
            \RETURN $retval$
        \ENDPROCEDURE

        \PROCEDURE{Test-Comments}{} \COMMENT{comment for procedure}
            \STATE a statement \COMMENT{inline comment}
            \STATE \COMMENT{line comment}
            \IF{some condition}\COMMENT{comment for if}
                \RETURN \TRUE \COMMENT{another inline comment}
            \ELSE \COMMENT{comment for else}
                \RETURN \FALSE \COMMENT{yet another inline comment}
            \ENDIF
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples" data-title-prefix="Procedure">
        % This quicksort algorithm is extracted from Chapter 7, Introduction
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$}
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples-custom-title">
        % This quicksort algorithm is extracted from Chapter 7, Introduction 
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$} 
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-line-number=true>
        \begin{algorithm}
        \caption{Classical Euclidean Algorithm}
        \begin{algorithmic}
            \PROCEDURE{Euclid}{$a,b$}
                \WHILE{$a \neq b$}
                    \IF{$a > b$}
                        \STATE $a \gets a - b$
                    \ELSE
                        \STATE $b \gets b - a$
                    \ENDIF
                \ENDWHILE
                \RETURN $a$
            \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-no-end=true>
        \begin{algorithm}
        \caption{DBSCAN}
        \begin{algorithmic}
        \INPUT A dataset $D$, the $\varepsilon$ distance threshold, and the minimum number of points $minPts$
        \OUTPUT A set of clusters $K$
        \PROCEDURE{DBSCAN}{$D, \varepsilon, minPts$}
            \STATE $K \gets \emptyset$
            \FORALL{$p \in D$}
                \IF{$p$ has not been visited}
                    \STATE mark $p$ as visited
                    \STATE $N_{\varepsilon}(p) \gets $ \textsc{RangeQuery}$(p, \varepsilon, D)$
                    \IF{$N_{\varepsilon}(p) < minPts$}
                        \STATE mark $p$ as noise
                    \ELSE
                        \COMMENT{p is a core object}
                        \STATE $C \gets $ \textsc{ExpandCluster}$(p, N_{\varepsilon}(p))$
                        \STATE $K \gets K \cup \{C\}$
                    \ENDIF
                \ENDIF
            \ENDFOR
            \RETURN $K$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <script>
        pseudocode.renderElement(document.getElementById("test-basics"),
                                 {
                                     lineNumber: false,
                                     noEnd: true
                                 });
        pseudocode.renderElement(document.getElementById("test-codes"));
        pseudocode.renderElement(document.getElementById("test-examples"),
                                 {
                                     lineNumber: true,
                                     noEnd: false
                                 });
        pseudocode.renderElement(document.getElementById("test-examples-custom-title"),
                                 {
                                     lineNumber: true,
                                     noEnd: false,
                                     titlePrefix: "My pretty Algorithm"
                                 });                                   
        pseudocode.renderClass("scopeline-pseudocode", { scopeLines: true });
    </script>
</body>
</html>


================================================
FILE: docs/params.json
================================================
{
    "name": "pseudocode.js",
    "tagline": "Beautiful TeX-style pseudocode for the Web",
    "body": "### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here using GitHub Flavored Markdown, select a template crafted by a designer, and publish. After your page is generated, you can check out the new branch:\r\n\r\n```\r\n$ cd your_repo_root/repo_name\r\n$ git fetch origin\r\n$ git checkout gh-pages\r\n```\r\n\r\nIf you're using the GitHub for Mac, simply sync your repository and you'll see the new branch.\r\n\r\n### Designer Templates\r\nWe've crafted some handsome templates for you to use. Go ahead and continue to layouts to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved if it remained markdown format.\r\n\r\n### Rather Drive Stick?\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator written by our own Tom Preston-Werner. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `<a>` element will link to the contributor's GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out the documentation at https://help.github.com/pages or contact support@github.com and we’ll help you sort it out.\r\n",
    "google": "",
    "note": "Don't delete this file! It's used internally to help with page regeneration."
}

================================================
FILE: docs/pseudocode.css
================================================
@import url(https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css);.ps-root{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-size:1em;font-weight:100;-webkit-font-smoothing:antialiased!important}.ps-root .ps-algorithm{margin:.8em 0;border-top:3px solid #000;border-bottom:2px solid #000}.ps-root .ps-algorithm.with-caption>.ps-line:first-child{border-bottom:2px solid #000}.ps-root .katex{text-indent:0;font-size:1em}.ps-root .MathJax,.ps-root .MathJax_CHTML{text-indent:0;font-size:1em!important}.ps-root .ps-line{margin:0;padding:0;line-height:1.2}.ps-root .ps-funcname{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:small-caps;font-style:normal;text-transform:none}.ps-root .ps-keyword{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:700;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-comment{font-family:KaTeX_Main,'Times New Roman',Times,serif;font-weight:400;font-variant:normal;font-style:normal;text-transform:none}.ps-root .ps-linenum{font-size:.8em;line-height:1em;width:1.6em;text-align:right;display:inline-block;position:relative;padding-right:.3em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code{text-indent:-1.6em}.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code>span{text-indent:0}.ps-root .ps-algorithmic.with-scopelines div.ps-block{border-left-style:solid;border-left-width:.1em;padding-left:.6em}.ps-root .ps-algorithmic.with-scopelines>div.ps-block{border-left:none}

================================================
FILE: docs/pseudocode.js
================================================
(function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.pseudocode=e()}})(function(){var e,t,n;return function(){function p(o,s,a){function l(n,e){if(!s[n]){if(!o[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(h)return h(n,!0);var i=new Error("Cannot find module '"+n+"'");throw i.code="MODULE_NOT_FOUND",i}var r=s[n]={exports:{}};o[n][0].call(r.exports,function(e){var t=o[n][1][e];return l(t||e)},r,r.exports,p,o,s,a)}return s[n].exports}for(var h="function"==typeof require&&require,e=0;e<a.length;e++)l(a[e]);return l}return p}()({1:[function(e,t,n){var i=e("./src/ParseError");var r=e("./src/Lexer");var o=e("./src/Parser");var s=e("./src/Renderer");function a(e,t){var n=new r(e);var i=new o(n);return new s(i,t)}t.exports={ParseError:i,render:function(e,t,n){if(e===null||e===undefined)throw new ReferenceError("Input cannot be empty");var i=a(e,n);var r=i.toDOM();if(t)t.appendChild(r);if(i.backend&&i.backend.name==="mathjax"){if(MathJax.version<3)MathJax.Hub.Queue(["Typeset",MathJax.Hub,r]);else if(MathJax.version>3)MathJax.typeset()}return r},renderToString:function(e,t){if(e===null||e===undefined)throw new ReferenceError("Input cannot be empty");var n=a(e,t);if(n.backend&&n.backend.name==="mathjax"&&n.backend.version<3){console.warn("`renderToString` is fully supported only on MathJax backend 3.x.\n"+"Math ($...$) will not be rendered to HTML and will be left as is.")}return n.toMarkup()},renderElement:function(e,t){if(!(e instanceof Element))throw new ReferenceError("A DOM element is required");e.style.display="none";var n=JSON.parse(JSON.stringify(t||{}));for(const o in e.dataset)n[o]=e.dataset[o];var i=a(e.textContent,n);var r=i.toDOM();e.replaceWith(r);if(i.backend&&i.backend.name==="mathjax"){if(MathJax.version<3)MathJax.Hub.Queue(["Typeset",MathJax.Hub,e]);else if(MathJax.version>3)MathJax.typeset()}},renderClass:function(e,t){[...document.getElementsByClassName(e)].forEach(e=>this.renderElement(e,t))}}},{"./src/Lexer":2,"./src/ParseError":3,"./src/Parser":4,"./src/Renderer":5}],2:[function(e,t,n){var i=e("./utils");var u=e("./ParseError");var r=function(e){this._input=e;this._remain=e;this._pos=0;this._nextAtom=this._currentAtom=null;this._next()};r.prototype.accept=function(e,t){if(this._nextAtom.type===e&&this._matchText(t)){this._next();return this._currentAtom.text}return null};r.prototype.expect=function(e,t){var n=this._nextAtom;if(n.type!==e){throw new u(`Expected an atom of ${e} but received ${n.type}`,this._pos,this._input)}if(!this._matchText(t)){throw new u(`Expected \`${t}\` but received \`${n.text}\``,this._pos,this._input)}this._next();return this._currentAtom.text};r.prototype.get=function(){return this._currentAtom};var o={exec:function(e){var t=[{start:"$",end:"$"},{start:"\\(",end:"\\)"}];var n=e.length;for(var i=0;i<t.length;i++){var r=t[i].start;if(e.indexOf(r)!==0)continue;var o=t[i].end;var s=r.length;var a=e.slice(s);while(s<n){var l=a.indexOf(o);if(l<0){throw new u("Math environment is not closed",this._pos,this._input)}if(l>0&&a[l-1]==="\\"){var h=l+o.length;a=a.slice(h);s+=h;continue}var p=[e.slice(0,s+l+o.length),e.slice(r.length,s+l)];return p}}return null}};var p={special:/^(\\\\|\\{|\\}|\\\$|\\&|\\#|\\%|\\_)/,math:o,func:/^\\([a-zA-Z]+)/,open:/^\{/,close:/^\}/,quote:/^(`|``|'|'')/,ordinary:/^[^\\{}$&#%_\s]+/};var c=/^%.*/;var f=/^\s+/;r.prototype._skip=function(e){this._pos+=e;this._remain=this._remain.slice(e)};r.prototype._next=function(){var e=false;while(1){var t=f.exec(this._remain);if(t){e=true;var n=t[0].length;this._skip(n)}var i=c.exec(this._remain);if(!i)break;var r=i[0].length;this._skip(r)}this._currentAtom=this._nextAtom;if(this._remain===""){this._nextAtom={type:"EOF",text:null,whitespace:false};return false}for(var o in p){var s=p[o];var a=s.exec(this._remain);if(!a)continue;var l=a[0];var h=a[1]?a[1]:l;this._nextAtom={type:o,text:h,whitespace:e};this._pos+=l.length;this._remain=this._remain.slice(a[0].length);return true}throw new u("Unrecoganizable atom",this._pos,this._input)};r.prototype._matchText=function(e){if(e===null||e===undefined)return true;if(i.isString(e))return e.toLowerCase()===this._nextAtom.text.toLowerCase();else return e.some(e=>e.toLowerCase()===this._nextAtom.text.toLowerCase())};t.exports=r},{"./ParseError":3,"./utils":6}],3:[function(e,t,n){function i(e,t,n){var i=`Error: ${e}`;if(t!==undefined&&n!==undefined){i+=` at position ${t}: \``;n=`${n.slice(0,t)}\u21B1${n.slice(t)}`;var r=Math.max(0,t-15);var o=t+15;i+=`${n.slice(r,o)}\``}this.message=i}i.prototype=Object.create(Error.prototype);i.prototype.constructor=i;t.exports=i},{}],4:[function(e,t,n){var s=e("./utils");var r=e("./ParseError");var a=function(e,t){this.type=e;this.value=t;this.children=[]};a.prototype.toString=function(e){if(!e)e=0;var t="";for(var n=0;n<e;n++)t+="  ";var i=`${t}<${this.type}>`;if(this.value)i+=` (${s.toString(this.value)})`;i+="\n";if(this.children){for(var r=0;r<this.children.length;r++){var o=this.children[r];i+=o.toString(e+1)}}return i};a.prototype.addChild=function(e){if(!e)throw new Error("Argument must not be null");this.children.push(e)};var o=function(e,t,n){this.type=e;this.value=t;this.children=null;this.whitespace=!!n};o.prototype=a.prototype;var i=function(e){this._lexer=e};i.prototype.parse=function(){var e=new a("root");while(true){var t=this._acceptEnvironment();if(t===null)break;var n;if(t==="algorithm")n=this._parseAlgorithmInner();else if(t==="algorithmic")n=this._parseAlgorithmicInner();else throw new r(`Unexpected environment ${t}`);this._closeEnvironment(t);e.addChild(n)}this._lexer.expect("EOF");return e};i.prototype._acceptEnvironment=function(){var e=this._lexer;if(!e.accept("func","begin"))return null;e.expect("open");var t=e.expect("ordinary");e.expect("close");return t};i.prototype._closeEnvironment=function(e){var t=this._lexer;t.expect("func","end");t.expect("open");t.expect("ordinary",e);t.expect("close")};i.prototype._parseAlgorithmInner=function(){var e=new a("algorithm");while(true){var t=this._acceptEnvironment();if(t!==null){if(t!=="algorithmic")throw new r(`Unexpected environment ${t}`);var n=this._parseAlgorithmicInner();this._closeEnvironment();e.addChild(n);continue}var i=this._parseCaption();if(i){e.addChild(i);continue}break}return e};i.prototype._parseAlgorithmicInner=function(){var e=new a("algorithmic");var t;while(true){t=this._parseStatement(l);if(t){e.addChild(t);continue}t=this._parseBlock();if(t.children.length>0){e.addChild(t);continue}break}return e};i.prototype._parseCaption=function(){var e=this._lexer;if(!e.accept("func","caption"))return null;var t=new a("caption");e.expect("open");t.addChild(this._parseCloseText());e.expect("close");return t};i.prototype._parseBlock=function(){var e=new a("block");while(true){var t=this._parseControl();if(t){e.addChild(t);continue}var n=this._parseFunction();if(n){e.addChild(n);continue}var i=this._parseStatement(h);if(i){e.addChild(i);continue}var r=this._parseCommand(p);if(r){e.addChild(r);continue}var o=this._parseComment();if(o){e.addChild(o);continue}break}return e};i.prototype._parseControl=function(){var e;if(e=this._parseIf())return e;if(e=this._parseLoop())return e;if(e=this._parseRepeat())return e;if(e=this._parseUpon())return e};i.prototype._parseFunction=function(){var e=this._lexer;if(!e.accept("func",["function","procedure"]))return null;var t=this._lexer.get().text;e.expect("open");var n=e.expect("ordinary");e.expect("close");e.expect("open");var i=this._parseCloseText();e.expect("close");var r=this._parseBlock();e.expect("func",`end${t}`);var o=new a("function",{type:t,name:n});o.addChild(i);o.addChild(r);return o};i.prototype._parseIf=function(){if(!this._lexer.accept("func","if"))return null;var e=new a("if");this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());var t=0;while(this._lexer.accept("func",["elif","elsif","elseif"])){this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());t++}var n=false;if(this._lexer.accept("func","else")){n=true;e.addChild(this._parseBlock())}this._lexer.expect("func","endif");e.value={numElif:t,hasElse:n};return e};i.prototype._parseLoop=function(){if(!this._lexer.accept("func",["FOR","FORALL","WHILE"]))return null;var e=this._lexer.get().text.toLowerCase();var t=new a("loop",e);this._lexer.expect("open");t.addChild(this._parseCond());this._lexer.expect("close");t.addChild(this._parseBlock());var n=e!=="forall"?`end${e}`:"endfor";this._lexer.expect("func",n);return t};i.prototype._parseRepeat=function(){if(!this._lexer.accept("func",["REPEAT"]))return null;var e=this._lexer.get().text.toLowerCase();var t=new a("repeat",e);t.addChild(this._parseBlock());this._lexer.expect("func","until");this._lexer.expect("open");t.addChild(this._parseCond());this._lexer.expect("close");return t};i.prototype._parseUpon=function(){if(!this._lexer.accept("func","upon"))return null;var e=new a("upon");this._lexer.expect("open");e.addChild(this._parseCond());this._lexer.expect("close");e.addChild(this._parseBlock());this._lexer.expect("func","endupon");return e};var l=["ensure","require","input","output"];var h=["state","print","return"];i.prototype._parseStatement=function(e){if(!this._lexer.accept("func",e))return null;var t=this._lexer.get().text.toLowerCase();var n=new a("statement",t);n.addChild(this._parseOpenText());return n};var p=["break","continue"];i.prototype._parseCommand=function(e){if(!this._lexer.accept("func",e))return null;var t=this._lexer.get().text.toLowerCase();var n=new a("command",t);return n};i.prototype._parseComment=function(){if(!this._lexer.accept("func","comment"))return null;var e=new a("comment");this._lexer.expect("open");e.addChild(this._parseCloseText());this._lexer.expect("close");return e};i.prototype._parseCall=function(){var e=this._lexer;if(!e.accept("func","call"))return null;var t=e.get().whitespace;e.expect("open");var n=e.expect("ordinary");e.expect("close");var i=new a("call");i.whitespace=t;i.value=n;e.expect("open");var r=this._parseCloseText();i.addChild(r);e.expect("close");return i};i.prototype._parseCond=i.prototype._parseCloseText=function(){return this._parseText("close")};i.prototype._parseOpenText=function(){return this._parseText("open")};i.prototype._parseText=function(e){var t=new a(`${e}-text`);var n=false;var i;while(true){i=this._parseAtom()||this._parseCall();if(i){if(n)i.whitespace|=n;t.addChild(i);continue}if(this._lexer.accept("open")){i=this._parseCloseText();n=this._lexer.get().whitespace;i.whitespace=n;t.addChild(i);this._lexer.expect("close");n=this._lexer.get().whitespace;continue}break}return t};var u={ordinary:{tokenType:"ordinary"},math:{tokenType:"math"},special:{tokenType:"special"},"cond-symbol":{tokenType:"func",tokenValues:["and","or","not","true","false","to","downto"]},"quote-symbol":{tokenType:"quote"},"sizing-dclr":{tokenType:"func",tokenValues:["tiny","scriptsize","footnotesize","small","normalsize","large","Large","LARGE","huge","Huge"]},"font-dclr":{tokenType:"func",tokenValues:["normalfont","rmfamily","sffamily","ttfamily","upshape","itshape","slshape","scshape","bfseries","mdseries","lfseries"]},"font-cmd":{tokenType:"func",tokenValues:["textnormal","textrm","textsf","texttt","textup","textit","textsl","textsc","uppercase","lowercase","textbf","textmd","textlf"]},"text-symbol":{tokenType:"func",tokenValues:["textbackslash"]}};i.prototype._parseAtom=function(){for(var e in u){var t=u[e];var n=this._lexer.accept(t.tokenType,t.tokenValues);if(n===null)continue;var i=this._lexer.get().whitespace;if(e!=="ordinary"&&e!=="math")n=n.toLowerCase();return new o(e,n,i)}return null};t.exports=i},{"./ParseError":3,"./utils":6}],5:[function(n,e,t){var a=n("./utils");function A(e){this._css={};this._fontSize=this._outerFontSize=e!==undefined?e:1}A.prototype.outerFontSize=function(e){if(e!==undefined)this._outerFontSize=e;return this._outerFontSize};A.prototype.fontSize=function(){return this._fontSize};A.prototype._fontCommandTable={normalfont:{"font-family":"KaTeX_Main"},rmfamily:{"font-family":"KaTeX_Main"},sffamily:{"font-family":"KaTeX_SansSerif"},ttfamily:{"font-family":"KaTeX_Typewriter"},bfseries:{"font-weight":"bold"},mdseries:{"font-weight":"medium"},lfseries:{"font-weight":"lighter"},upshape:{"font-style":"normal","font-variant":"normal"},itshape:{"font-style":"italic","font-variant":"normal"},scshape:{"font-style":"normal","font-variant":"small-caps"},slshape:{"font-style":"oblique","font-variant":"normal"},textnormal:{"font-family":"KaTeX_Main"},textrm:{"font-family":"KaTeX_Main"},textsf:{"font-family":"KaTeX_SansSerif"},texttt:{"font-family":"KaTeX_Typewriter"},textbf:{"font-weight":"bold"},textmd:{"font-weight":"medium"},textlf:{"font-weight":"lighter"},textup:{"font-style":"normal","font-variant":"normal"},textit:{"font-style":"italic","font-variant":"normal"},textsc:{"font-style":"normal","font-variant":"small-caps"},textsl:{"font-style":"oblique","font-variant":"normal"},uppercase:{"text-transform":"uppercase"},lowercase:{"text-transform":"lowercase"}};A.prototype._sizingScalesTable={tiny:.68,scriptsize:.8,footnotesize:.85,small:.92,normalsize:1,large:1.17,Large:1.41,LARGE:1.58,huge:1.9,Huge:2.28};A.prototype.updateByCommand=function(e){var t=this._fontCommandTable[e];if(t!==undefined){for(var n in t)this._css[n]=t[n];return}var i=this._sizingScalesTable[e];if(i!==undefined){this._outerFontSize=this._fontSize;this._fontSize=i;return}throw new ParserError("Unrecognized `text-style` command")};A.prototype.toCSS=function(){var e="";for(var t in this._css){var n=this._css[t];if(n===undefined)continue;e+=`${t}:${n};`}if(this._fontSize!==this._outerFontSize)e+=`font-size:${this._fontSize/this._outerFontSize}em;`;return e};function B(e,t){this._nodes=e;this._textStyle=t}B.prototype._renderCloseText=function(e,t){var n=new A(this._textStyle.fontSize());var i=new B(e.children,n);if(e.whitespace)this._html.putText(" ");this._html.putHTML(i.renderToHTML(t))};B.prototype.renderToHTML=function(e){this._html=new _;var t;while((t=this._nodes.shift())!==undefined){var n=t.type;var i=t.value;if(t.whitespace)this._html.putText(" ");switch(n){case"ordinary":this._html.putText(i);break;case"math":if(typeof e==="undefined"){throw EvalError("No math backend found. Please setup KaTeX or MathJax.")}else if(e.name==="katex"){this._html.putHTML(e.driver.renderToString(i))}else if(e.name==="mathjax"){if(e.version===3){this._html.putHTML(e.driver.tex2chtml(i,{display:false}).outerHTML)}else{this._html.putText(`$${i}$`)}}else{throw new EvalError(`Unknown math backend ${e}`)}break;case"cond-symbol":this._html.beginSpan("ps-keyword").putText(i.toLowerCase()).endSpan();break;case"special":if(i==="\\\\"){this._html.putHTML("<br/>");break}var r={"\\{":"{","\\}":"}","\\$":"$","\\&":"&","\\#":"#","\\%":"%","\\_":"_"};var o=r[i];this._html.putText(o);break;case"text-symbol":var s={textbackslash:"\\"};var a=s[i];this._html.putText(a);break;case"quote-symbol":var l={"`":"\u2018","``":"\u201c","'":"\u2019","''":"\u201d"};var h=l[i];this._html.putText(h);break;case"call":this._html.beginSpan("ps-funcname").putText(i).endSpan();this._html.write("(");var p=t.children[0];this._renderCloseText(p,e);this._html.write(")");break;case"close-text":this._renderCloseText(t,e);break;case"font-dclr":case"sizing-dclr":this._textStyle.updateByCommand(i);this._html.beginSpan(null,this._textStyle.toCSS());var u=new B(this._nodes,this._textStyle);this._html.putHTML(u.renderToHTML(e));this._html.endSpan();break;case"font-cmd":var c=this._nodes[0];if(c.type!=="close-text")continue;var f=new A(this._textStyle.fontSize());f.updateByCommand(i);this._html.beginSpan(null,f.toCSS());var d=new B(c.children,f);this._html.putHTML(d.renderToHTML(e));this._html.endSpan();break;default:throw new ParseError(`Unexpected ParseNode of type ${t.type}`)}}return this._html.toMarkup()};function _(){this._body=[];this._textBuf=[]}_.prototype.beginDiv=function(e,t,n){this._beginTag("div",e,t,n);this._body.push("\n");return this};_.prototype.endDiv=function(){this._endTag("div");this._body.push("\n");return this};_.prototype.beginP=function(e,t,n){this._beginTag("p",e,t,n);this._body.push("\n");return this};_.prototype.endP=function(){this._flushText();this._endTag("p");this._body.push("\n");return this};_.prototype.beginSpan=function(e,t,n){this._flushText();return this._beginTag("span",e,t,n)};_.prototype.endSpan=function(){this._flushText();return this._endTag("span")};_.prototype.putHTML=function(e){this._flushText();this._body.push(e);return this};_.prototype.putText=function(e){this._textBuf.push(e);return this};_.prototype.write=function(e){this._body.push(e)};_.prototype.toMarkup=function(){this._flushText();var e=this._body.join("");return e.trim()};_.prototype.toDOM=function(){var e=this.toMarkup();var t=document.createElement("div");t.innerHTML=e;return t.firstChild};_.prototype._flushText=function(){if(this._textBuf.length===0)return;var e=this._textBuf.join("");this._body.push(this._escapeHtml(e));this._textBuf=[]};_.prototype._beginTag=function(e,t,n,i){var r=`<${e}`;if(t)r+=` class="${t}"`;if(n){var o;if(a.isString(n)){o=n}else{o="";for(var s in n){attrVal=n[s];o+=`${s}:${attrVal};`}}if(i)o+=i;r+=` style="${o}"`}r+=">";this._body.push(r);return this};_.prototype._endTag=function(e){this._body.push(`</${e}>`);return this};var i={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;"};_.prototype._escapeHtml=function(e){return String(e).replace(/[&<>"'/]/g,e=>i[e])};function r(e){e=e||{};this.indentSize=e.indentSize?this._parseEmVal(e.indentSize):1.2;this.commentDelimiter=e.commentDelimiter!==undefined?e.commentDelimiter:" // ";this.lineNumberPunc=e.lineNumberPunc!==undefined?e.lineNumberPunc:":";this.lineNumber=e.lineNumber!==undefined?e.lineNumber:false;this.noEnd=e.noEnd!==undefined?e.noEnd:false;this.scopeLines=e.scopeLines!==undefined?e.scopeLines:false;if(e.captionCount!==undefined)F.captionCount=e.captionCount;this.titlePrefix=e.titlePrefix!==undefined?e.titlePrefix:"Algorithm"}r.prototype._parseEmVal=function(e){e=e.trim();if(e.indexOf("em")!==e.length-2)throw new TypeError("Unit error; expected `em` suffix");return Number(e.substring(0,e.length-2))};function F(e,t){this._root=e.parse();this._options=new r(t);this._openLine=false;this._blockLevel=0;this._textLevel=-1;this._globalTextStyle=new A;this.backend=undefined;try{if(typeof katex==="undefined")katex=n("katex")}catch(e){}try{if(typeof MathJax==="undefined")MathJax=n("mathjax")}catch(e){}if(typeof katex!=="undefined"){this.backend={name:"katex",driver:katex}}else if(typeof MathJax!=="undefined"){this.backend={name:"mathjax",version:parseInt(MathJax.version.split(".")[0]),driver:MathJax}}}F.captionCount=0;F.prototype.toMarkup=function(){var e=this._html=new _;this._buildTree(this._root);delete this._html;return e.toMarkup()};F.prototype.toDOM=function(){var e=this.toMarkup();var t=document.createElement("div");t.innerHTML=e;return t.firstChild};F.prototype._beginGroup=function(e,t,n){this._closeLineIfAny();this._html.beginDiv(`ps-${e}${t?` ${t}`:""}`,n)};F.prototype._endGroup=function(e){this._closeLineIfAny();this._html.endDiv()};F.prototype._beginBlock=function(){var e=this._options.lineNumber&&this._blockLevel===0?.6:0;var t=this._options.indentSize+e;if(this._options.scopeLines)t/=2;this._beginGroup("block",null,{"margin-left":`${t}em`});this._blockLevel++};F.prototype._endBlock=function(){this._closeLineIfAny();this._endGroup();this._blockLevel--};F.prototype._newLine=function(){this._closeLineIfAny();this._openLine=true;this._globalTextStyle.outerFontSize(1);var e=this._options.indentSize;if(this._blockLevel>0){this._numLOC++;this._html.beginP("ps-line ps-code",this._globalTextStyle.toCSS());var t=this._options.lineNumber?e*1.25:0;t+=this._options.scopeLines?e*.1:0;if(this._options.lineNumber){this._html.beginSpan("ps-linenum",{left:`${-((this._blockLevel-1)*t)}em`}).putText(this._numLOC+this._options.lineNumberPunc).endSpan()}}else{this._html.beginP("ps-line",{"text-indent":`${-e}em`,"padding-left":`${e}em`},this._globalTextStyle.toCSS())}};F.prototype._closeLineIfAny=function(){if(!this._openLine)return;this._html.endP();this._openLine=false};F.prototype._typeKeyword=function(e){this._html.beginSpan("ps-keyword").putText(e).endSpan()};F.prototype._typeFuncName=function(e){this._html.beginSpan("ps-funcname").putText(e).endSpan()};F.prototype._typeText=function(e){this._html.write(e)};F.prototype._buildTreeForAllChildren=function(e){var t=e.children;for(var n=0;n<t.length;n++)this._buildTree(t[n])};F.prototype._buildCommentsFromBlock=function(e){var t=e.children;while(t.length>0&&t[0].type==="comment"){var n=t.shift();this._buildTree(n)}};F.prototype._buildTree=function(e){var t;var n;var i;switch(e.type){case"root":this._beginGroup("root");this._buildTreeForAllChildren(e);this._endGroup();break;case"algorithm":var r;for(t=0;t<e.children.length;t++){n=e.children[t];if(n.type!=="caption")continue;r=n;F.captionCount++}if(r){this._beginGroup("algorithm","with-caption");this._buildTree(r)}else{this._beginGroup("algorithm")}for(t=0;t<e.children.length;t++){n=e.children[t];if(n.type==="caption")continue;this._buildTree(n)}this._endGroup();break;case"algorithmic":var o=this._options.lineNumber?" with-linenum ":"";o+=this._options.scopeLines?" with-scopelines ":"";if(o){this._beginGroup("algorithmic",o);this._numLOC=0}else{this._beginGroup("algorithmic")}this._buildTreeForAllChildren(e);this._endGroup();break;case"block":this._beginBlock();this._buildTreeForAllChildren(e);this._endBlock();break;case"function":var s=e.value.type.toLowerCase();var a=e.value.name;i=e.children[0];var l=e.children[1];this._newLine();this._typeKeyword(`${s} `);this._typeFuncName(a);this._typeText("(");this._buildTree(i);this._typeText(")");this._buildCommentsFromBlock(l);this._buildTree(l);if(!this._options.noEnd){this._newLine();this._typeKeyword(`end ${s}`)}break;case"if":this._newLine();this._typeKeyword("if ");ifCond=e.children[0];this._buildTree(ifCond);this._typeKeyword(" then");var h=e.children[1];this._buildCommentsFromBlock(h);this._buildTree(h);var p=e.value.numElif;for(var u=0;u<p;u++){this._newLine();this._typeKeyword("else if ");var c=e.children[2+2*u];this._buildTree(c);this._typeKeyword(" then");var f=e.children[2+2*u+1];this._buildCommentsFromBlock(f);this._buildTree(f)}var d=e.value.hasElse;if(d){this._newLine();this._typeKeyword("else");var _=e.children[e.children.length-1];this._buildCommentsFromBlock(_);this._buildTree(_)}if(!this._options.noEnd){this._newLine();this._typeKeyword("end if")}break;case"loop":this._newLine();var m=e.value;var x={for:"for",forall:"for all",while:"while"};this._typeKeyword(`${x[m]} `);var v=e.children[0];this._buildTree(v);this._typeKeyword(" do");var y=e.children[1];this._buildCommentsFromBlock(y);this._buildTree(y);if(!this._options.noEnd){this._newLine();var b=m==="while"?"end while":"end for";this._typeKeyword(b)}break;case"repeat":this._newLine();this._typeKeyword("repeat");var w=e.children[0];this._buildCommentsFromBlock(w);this._buildTree(w);this._newLine();this._typeKeyword("until ");var T=e.children[1];this._buildTree(T);break;case"upon":this._newLine();this._typeKeyword("upon ");uponCond=e.children[0];this._buildTree(uponCond);var g=e.children[1];this._buildCommentsFromBlock(g);this._buildTree(g);if(!this._options.noEnd){this._newLine();this._typeKeyword("end upon")}break;case"command":var k=e.value;var C={break:"break",continue:"continue"}[k];this._newLine();if(C)this._typeKeyword(C);break;case"caption":this._newLine();this._typeKeyword(`${this._options.titlePrefix} ${F.captionCount} `);i=e.children[0];this._buildTree(i);break;case"comment":i=e.children[0];this._html.beginSpan("ps-comment");this._html.putText(this._options.commentDelimiter);this._buildTree(i);this._html.endSpan();break;case"statement":var S=e.value;var L={state:"",ensure:"Ensure: ",require:"Require: ",input:"Input: ",output:"Output: ",print:"print ",return:"return "}[S];this._newLine();if(L)this._typeKeyword(L);i=e.children[0];this._buildTree(i);break;case"open-text":var E=new B(e.children,this._globalTextStyle);this._html.putHTML(E.renderToHTML(this.backend));break;case"close-text":var M=this._globalTextStyle.fontSize();var $=new A(M);var z=new B(e.children,$);this._html.putHTML(z.renderToHTML(this.backend));break;default:throw new ParseError(`Unexpected ParseNode of type ${e.type}`)}};e.exports=F},{"./utils":6,katex:undefined,mathjax:undefined}],6:[function(e,t,n){t.exports={isString:e=>typeof e==="string"||e instanceof String,isObject:e=>typeof e==="object"&&e instanceof Object,toString:function(e){if(!this.isObject(e))return`${e}`;var t=[];for(var n in e)t.push(`${n}: ${this.toString(e[n])}`);return t.join(", ")}}},{}]},{},[1])(1)});


================================================
FILE: docs/stylesheets/styles.css
================================================
body {
  padding:50px;
  font:14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
  color:#777;
  font-weight:300;
}

iframe {
  border: none;
  width: 115%;
}

h1, h2, h3, h4, h5, h6 {
  color:#222;
  margin:0 0 20px;
  font-weight: bold;
}

p, ul, ol, table, pre, dl {
  margin:0 0 20px;
}

h1, h2, h3 {
  line-height:1.1;
}

h1 {
  font-size:28px;
}

h2 {
  color:#393939;
}

h3, h4, h5, h6 {
  color:#494949;
}

a {
  color:#39c;
  font-weight:400;
  text-decoration:none;
}

a small {
  font-size:11px;
  color:#777;
  margin-top:-0.6em;
  display:block;
}

.wrapper {
  width:860px;
  margin:0 auto;
}

blockquote {
  border-left:1px solid #e5e5e5;
  margin:0;
  padding:0 0 0 20px;
  font-style:italic;
}

code, pre {
  font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal;
  color:#333;
  font-size:0.85em;
}

pre {
  border-radius:5px;
  border:1px solid #e5e5e5;
  font-size:0.75em;
}

table {
  width:100%;
  border-collapse:collapse;
}

th, td {
  text-align:left;
  padding:5px 10px;
  border-bottom:1px solid #e5e5e5;
}

dt {
  color:#444;
  font-weight:700;
}

th {
  color:#444;
}

img {
  max-width:100%;
}

header {
  width:270px;
  float:left;
  position:fixed;
}

header ul {
  list-style:none;
  height:40px;
  
  padding:0;
  
  background: #eee;
  background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);
  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));
  background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
  background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
  background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
  background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%);
  
  border-radius:5px;
  border:1px solid #d2d2d2;
  box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;
  width:270px;
}

header li {
  width:89px;
  float:left;
  border-right:1px solid #d2d2d2;
  height:40px;
}

header ul a {
  line-height:1.15;
  font-size:11px;
  color:#999;
  display:block;
  text-align:center;
  padding-top:6px;
  height:40px;
}

strong {
  color:#222;
  font-weight:700;
}

header ul li:first-child{
  width:88px;
  border-left:1px solid #fff;
}

header ul li:last-child {
  border-right:none;
  width:89px;
}

header ul a strong {
  font-size:14px;
  display:block;
  color:#222;
}

section {
  width:500px;
  float:right;
  padding-bottom:50px;
}

small {
  font-size:11px;
}

hr {
  border:0;
  background:#e5e5e5;
  height:1px;
  margin:0 0 20px;
}

footer {
  width:270px;
  float:left;
  position:fixed;
  bottom:50px;
}

@media print, screen and (max-width: 960px) {
  
  div.wrapper {
    width:auto;
    margin:0;
  }
  
  header, section, footer {
    float:none;
    position:static;
    width:auto;
  }
  
  header {
    padding-right:320px;
  }
  
  section {
    border:1px solid #e5e5e5;
    border-width:1px 0;
    padding:20px 0;
    margin:0 0 20px;
  }
  
  header a small {
    display:inline;
  }
  
  header ul {
    position:absolute;
    right:50px;
    top:52px;
  }
}

@media print, screen and (max-width: 720px) {
  body {
    word-wrap:break-word;
  }
  
  header {
    padding:0;
  }
  
  header ul, header p.view {
    position:static;
  }
  
  pre, code {
    word-wrap:normal;
  }
}

@media print, screen and (max-width: 480px) {
  body {
    padding:15px;
  }
  
  header ul {
    display:none;
  }
}

@media print {
  body {
    padding:0.4in;
    font-size:12pt;
    color:#444;
  }
}


================================================
FILE: package.json
================================================
{
    "name": "pseudocode",
    "version": "2.4.1",
    "author": {
        "name": "Saswat Padhi",
        "email": "saswat.sourav@gmail.com",
        "url": "https://saswatpadhi.github.io/"
    },
    "description": "Beautiful pseudocode for the Web",
    "main": "pseudocode.js",
    "repository": {
        "type": "git",
        "url": "git://github.com/SaswatPadhi/pseudocode.js"
    },
    "files": [
        "pseudocode.js",
        "src/",
        "build/pseudocode.min.css",
        "build/pseudocode.min.js"
    ],
    "dependencies": {
        "katex": "^0.16.7"
    },
    "devDependencies": {
        "browserify": "^17.0.0",
        "clean-css-cli": "^5.3.0",
        "eslint": "^8.45.0",
        "uglify-js": "^3.17.4",
        "watchify": "^4.0.0",
        "clean-css": "^5.2.4"
    },
    "optionalDependencies": {
        "mathjax": "^3.2.2"
    },
    "license": "MIT"
}


================================================
FILE: pseudocode.js
================================================
/*
 * The entry points of pseudocode-js
 **/

var ParseError = require('./src/ParseError');
var Lexer = require('./src/Lexer');
var Parser = require('./src/Parser');
var Renderer = require('./src/Renderer');

function makeRenderer (data, options) {
    var lexer = new Lexer(data);
    var parser = new Parser(lexer);
    return new Renderer(parser, options);
}

module.exports = {
    ParseError: ParseError,

    render: function (input, baseDomEle, options) {
        if (input === null || input === undefined)
            throw new ReferenceError('Input cannot be empty');

        var R = makeRenderer(input, options);
        var elem = R.toDOM();
        if (baseDomEle)
            baseDomEle.appendChild(elem);

        if (R.backend && R.backend.name === 'mathjax') {
            if (MathJax.version < 3)
                MathJax.Hub.Queue(["Typeset", MathJax.Hub, elem]);
            else if (MathJax.version > 3)
                MathJax.typeset();
            // We use synchronous conversion in MathJax 3.x
        }

        return elem;
    },

    renderToString: function (input, options) {
        if (input === null || input === undefined)
            throw new ReferenceError('Input cannot be empty');

        var R = makeRenderer(input, options);
        if (R.backend && R.backend.name === 'mathjax' && R.backend.version < 3) {
            console.warn('`renderToString` is fully supported only on MathJax backend 3.x.\n' +
                         'Math ($...$) will not be rendered to HTML and will be left as is.');
        }

        return R.toMarkup();
    },

    renderElement: function (elem, options) {
        if (!(elem instanceof Element))
            throw new ReferenceError('A DOM element is required');

        elem.style.display = 'none';

        var elemOptions = JSON.parse(JSON.stringify(options || {}));
        for (const dataProp in elem.dataset)
            elemOptions[dataProp] = elem.dataset[dataProp];
        var R = makeRenderer(elem.textContent, elemOptions);

        var newElem = R.toDOM();
        elem.replaceWith(newElem);

        if (R.backend && R.backend.name === 'mathjax') {
            if (MathJax.version < 3)
                MathJax.Hub.Queue(["Typeset", MathJax.Hub, elem]);
            else if (MathJax.version > 3)
                MathJax.typeset();
            // We use synchronous conversion in MathJax 3.x
        }
    },

    renderClass: function (className, options) {
        [...document.getElementsByClassName(className)].forEach(
            (el) => this.renderElement(el, options)
        );
    },
};


================================================
FILE: src/Lexer.js
================================================
/**
 * The Lexer class tokenizes the input sequentially, looking ahead only one
 * token.
 */
var utils = require('./utils');
var ParseError = require('./ParseError');

var Lexer = function (input) {
    this._input = input;
    this._remain = input;
    this._pos = 0;
    this._nextAtom = this._currentAtom = null;
    this._next(); // get the next atom
};

Lexer.prototype.accept = function (type, text) {
    if (this._nextAtom.type === type && this._matchText(text)) {
        this._next();
        return this._currentAtom.text;
    }
    return null;
};

Lexer.prototype.expect = function (type, text) {
    var nextAtom = this._nextAtom;
    // The next atom is NOT of the right type
    if (nextAtom.type !== type) {
        throw new ParseError(
            `Expected an atom of ${type} but received ${nextAtom.type}`,
            this._pos,
            this._input
        );
    }
    // Check whether the text is exactly the same
    if (!this._matchText(text)) {
        throw new ParseError(
            `Expected \`${text}\` but received \`${nextAtom.text}\``,
            this._pos,
            this._input
        );
    }

    this._next();
    return this._currentAtom.text;
};

Lexer.prototype.get = function () {
    return this._currentAtom;
};

/* Math pattern
    Math environtment like $ $ or \( \) cannot be matched using regular
    expression. This object simulates a RegEx object
*/
var mathPattern = {
    exec: function (str) {
        var delimiters = [
            { start: '$', end: '$' },
            { start: '\\(', end: '\\)' },
        ];
        var totalLen = str.length;

        for (var di = 0; di < delimiters.length; di++) {
            var startDel = delimiters[di].start;
            if (str.indexOf(startDel) !== 0) continue;

            var endDel = delimiters[di].end;
            var endPos = startDel.length;
            var remain = str.slice(endPos);
            while (endPos < totalLen) {
                var pos = remain.indexOf(endDel);
                if (pos < 0) {
                    throw new ParseError('Math environment is not closed',
                                         this._pos, this._input);
                }

                // false positive, it's escaped, not a match
                if (pos > 0 && remain[pos - 1] === '\\') {
                    var skipLen = pos + endDel.length;
                    remain = remain.slice(skipLen);
                    endPos += skipLen;
                    continue;
                }

                var res = [str.slice(0, endPos + pos + endDel.length),
                    str.slice(startDel.length, endPos + pos)];
                return res;
            }
        }

        return null;
    },
};
var atomRegex = {
    // TODO: which is correct? func: /^\\(?:[a-zA-Z]+|.)/,
    special: /^(\\\\|\\{|\\}|\\\$|\\&|\\#|\\%|\\_)/,
    math: mathPattern, ///^\$.*\$/
    func: /^\\([a-zA-Z]+)/,
    open: /^\{/,
    close: /^\}/,
    quote: /^(`|``|'|'')/,
    ordinary: /^[^\\{}$&#%_\s]+/,
};
var commentRegex = /^%.*/;
var whitespaceRegex = /^\s+/;

Lexer.prototype._skip = function (len) {
    this._pos += len;
    this._remain = this._remain.slice(len);
};

/* Get the next atom */
Lexer.prototype._next = function () {
    var anyWhitespace = false;
    while (1) {
        // Skip whitespace (one or more)
        var whitespaceMatch = whitespaceRegex.exec(this._remain);
        if (whitespaceMatch) {
            anyWhitespace = true;
            var whitespaceLen = whitespaceMatch[0].length;
            this._skip(whitespaceLen);
        }

        // Skip comment
        var commentMatch = commentRegex.exec(this._remain);
        if (!commentMatch) break;
        var commentLen = commentMatch[0].length;
        this._skip(commentLen);
    }

    // Remember the current atom
    this._currentAtom = this._nextAtom;

    // Reach the end of string
    if (this._remain === '') {
        this._nextAtom = {
            type: 'EOF',
            text: null,
            whitespace: false,
        };
        return false;
    }

    // Try all kinds of atoms
    for (var type in atomRegex) {
        var regex = atomRegex[type];

        var match = regex.exec(this._remain);
        if (!match) continue; // not matched

        // match[1] is the useful part, e.g. '123' of '$123$', 'it' of '\\it'
        var matchText = match[0];
        var usefulText = match[1] ? match[1] : matchText;

        this._nextAtom = {
            type: type, /* special, func, open, close, ordinary, math */
            text: usefulText, /* the text value of the atom */
            whitespace: anyWhitespace, /* any whitespace before the atom */
        };

        this._pos += matchText.length;
        this._remain = this._remain.slice(match[0].length);

        return true;
    }

    throw new ParseError('Unrecoganizable atom', this._pos, this._input);
};

/* Check whether the text of the next atom matches */
Lexer.prototype._matchText = function (text) {
    // don't need to match
    if (text === null || text === undefined) return true;

    // using case-insensitive comparisons,
    // check if text is the same as next atom,
    // or if text is an array that contains the next atom
    if (utils.isString(text))
        return text.toLowerCase() === this._nextAtom.text.toLowerCase();
    else
        return text.some((str) => str.toLowerCase() === this._nextAtom.text.toLowerCase());
};

module.exports = Lexer;


================================================
FILE: src/ParseError.js
================================================
function ParseError (message, pos, input) {
    var error = `Error: ${message}`;
    // If we have the input and a position, make the error a bit fancier
    if (pos !== undefined && input !== undefined) {
        error += ` at position ${pos}: \``;

        // Insert a combining underscore at the correct position
        input = `${input.slice(0, pos)}\u21B1${input.slice(pos)}`;

        // Extract some context from the input and add it to the error
        var begin = Math.max(0, pos - 15);
        var end = pos + 15;
        error += `${input.slice(begin, end)}\``;
    }

    this.message = error;
}
ParseError.prototype = Object.create(Error.prototype);
ParseError.prototype.constructor = ParseError;

module.exports = ParseError;


================================================
FILE: src/Parser.js
================================================
/**
 * The Parser class parses the token stream from Lexer into an abstract syntax
 * tree, represented by ParseNode.
 *
 * The grammar of pseudocode required by Pseudocode.js mimics that of TeX/Latex
 * and its algorithm packages. It is designed intentionally to be less powerful
 * than Tex/LaTeX for the convinience of implementation. As a consequence, the
 * grammar is context-free, which can be expressed in production rules:
 *
 *     <pseudo>        :== ( <algorithm> | <algorithmic> )[0..n]
 *
 *     <algorithm>     :== \begin{algorithm}
 *                           ( <caption> | <algorithmic> )[0..n]
 *                         \end{algorithm}
 *     <caption>       :== \caption{ <close-text> }
 *
 *     <algorithmic>   :== \begin{algorithmic}
 *                           ( <ensure> | <require> | <block> )[0..n]
 *                         \end{algorithmic}
 *     <require>       :== \REQUIRE <open-text>
 *     <ensure>        :== \ENSURE <open-text>
 *
 *     <block>         :== ( <comment> | <command> | <control> | <function> |
 *                           <statement> )[0..n]
 *
 *     <control>       :== <if> | <for> | <while> | <repeat> | <upon>
 *     <if>            :== \IF{<cond>} <block>
 *                         ( \ELIF{<cond>} <block> )[0..n]
 *                         ( \ELSE <block> )[0..1]
 *                         \ENDIF
 *
 *     <for>           :== \FOR{<cond>} <block> \ENDFOR
 *     <while>         :== \WHILE{<cond>} <block> \ENDWHILE
 *     <repeat>        :== \REPEAT <block> \UNTIL{<cond>}
 *     <upon>          :== \UPON{<cond>} <block> \EDNUPON
 *
 *     <function>      :== \FUNCTION{<name>}{<params>} <block> \ENDFUNCTION
 *                         (same for <procedure>)
 *
 *     <statement>     :== <state> | <return> | <print>
 *     <state>         :== \STATE <open-text>
 *     <return>        :== \RETURN <open-text>
 *     <print>         :== \PRINT <open-text>
 *
 *     <commands>      :== <break> | <continue>
 *     <break>         :== \BREAK
 *     <continue>      :== \CONTINUE
 *
 *     <comment>       :== \COMMENT{<close-text>}
 *
 *     <cond>          :== <close-text>
 *     <open-text>     :== ( <atom> | <call> ) <open-text> |
 *                         { <close-text> } | <empty>
 *     <close-text>    :== ( <atom> | <call> ) <close-text> |
 *                         { <close-text> } | <empty>
 *
 *     <atom>          :== <ordinary>[1..n] | <special> | <symbol>
 *                         | <size> | <font> | <bool> | <math>
 *     <name>          :== <ordinary>
 *
 *     <call>          :== \CALL{<name>}({<close-text>})
 *     <special>       :== \\ | \{ | \} | \$ | \& | \# | \% | \_
 *     <cond-symbol>   :== \AND | \OR | \NOT | \TRUE | \FALSE | \TO | \DOWNTO
 *     <text-symbol>   :== \textbackslash
 *     <quote-symbol>  :== ` | `` | ' | ''
 *     (More LaTeX symbols can be added if necessary. See
 *     http://tug.ctan.org/info/symbols/comprehensive/symbols-a4.pdf)
 *     <math>          :== \( ... \) | $ ... $
 *     (Math is handled by a backend, KaTeX or MathJax)
 *     <size>          :== \tiny | \scriptsize | \footnotesize | \small
 *                         | \normalsize | \large | \Large | \LARGE | \huge
 *                         | \HUGE
 *     <font>          :== \rmfamily | \sffamily | \ttfamily
 *                         | \upshape | \itshape | \slshape | \scshape
 *     <ordinary>      :== not any of \ { } $ & # % _
 *     <empty>         :==
 *
 * There are many well-known ways to parse a context-free grammar, like the
 * top-down approach LL(k) or the bottom-up approach like LR(k). Both methods
 * are usually implemented in a table-driven fashion, which is not suitable to
 * write by hand. As our grammar is simple enough and its input is not expected
 * to be large, the performance wouldn't be a problem. Thus, I choose to write
 * the parser in the most natural form--- a (predictive) recursive descent
 * parser. The major benefit of a recursive descent parser is **simplity** for
 * the structure of resulting program closely mirrors that of the grammar. *
 *
 */
var utils = require('./utils');
var ParseError = require('./ParseError');

var ParseNode = function (type, val) {
    this.type = type;
    this.value = val;
    this.children = [];
};

ParseNode.prototype.toString = function (level) {
    if (!level) level = 0;

    var indent = '';
    for (var i = 0; i < level; i++) indent += '  ';

    var res = `${indent}<${this.type}>`;
    if (this.value) res += ` (${utils.toString(this.value)})`;
    res += '\n';

    if (this.children) {
        for (var ci = 0; ci < this.children.length; ci++) {
            var child = this.children[ci];
            res += child.toString(level + 1);
        }
    }

    return res;
};

ParseNode.prototype.addChild = function (childNode) {
    if (!childNode)
        throw new Error('Argument must not be null');

    this.children.push(childNode);
};

/* AtomNode is the leaf node of parse tree */
var AtomNode = function (type, value, whitespace) {
    // ParseNode.call(this, type, val);
    this.type = type;
    this.value = value;
    this.children = null; // leaf node, thus no children
    this.whitespace = !!whitespace; // is there any whitespace before the atom
};
AtomNode.prototype = ParseNode.prototype;

var Parser = function (lexer) {
    this._lexer = lexer;
};

Parser.prototype.parse = function () {
    var root = new ParseNode('root');

    while (true) {
        var envName = this._acceptEnvironment();
        if (envName === null) break;

        var envNode;
        if (envName === 'algorithm')
            envNode = this._parseAlgorithmInner();
        else if (envName === 'algorithmic')
            envNode = this._parseAlgorithmicInner();
        else
            throw new ParseError(`Unexpected environment ${envName}`);

        this._closeEnvironment(envName);
        root.addChild(envNode);
    }
    this._lexer.expect('EOF');
    return root;
};

Parser.prototype._acceptEnvironment = function () {
    var lexer = this._lexer;
    // \begin{XXXXX}
    if (!lexer.accept('func', 'begin')) return null;

    lexer.expect('open');
    var envName = lexer.expect('ordinary');
    lexer.expect('close');
    return envName;
};

Parser.prototype._closeEnvironment = function (envName) {
    // \close{XXXXX}
    var lexer = this._lexer;
    lexer.expect('func', 'end');
    lexer.expect('open');
    lexer.expect('ordinary', envName);
    lexer.expect('close');
};

Parser.prototype._parseAlgorithmInner = function () {
    var algNode = new ParseNode('algorithm');
    while (true) {
        var envName = this._acceptEnvironment();
        if (envName !== null) {
            if (envName !== 'algorithmic')
                throw new ParseError(`Unexpected environment ${envName}`);
            var algmicNode = this._parseAlgorithmicInner();
            this._closeEnvironment();
            algNode.addChild(algmicNode);
            continue;
        }

        var captionNode = this._parseCaption();
        if (captionNode) {
            algNode.addChild(captionNode);
            continue;
        }

        break;
    }
    return algNode;
};

Parser.prototype._parseAlgorithmicInner = function () {
    var algmicNode = new ParseNode('algorithmic');
    var node;
    while (true) {
        node = this._parseStatement(IO_STATEMENTS);
        if (node) {
            algmicNode.addChild(node);
            continue;
        }

        node = this._parseBlock();
        if (node.children.length > 0) {
            algmicNode.addChild(node);
            continue;
        }

        break;
    }
    return algmicNode;
};

Parser.prototype._parseCaption = function () {
    var lexer = this._lexer;
    if (!lexer.accept('func', 'caption')) return null;

    var captionNode = new ParseNode('caption');
    lexer.expect('open');
    captionNode.addChild(this._parseCloseText());
    lexer.expect('close');

    return captionNode;
};

Parser.prototype._parseBlock = function () {
    var blockNode = new ParseNode('block');

    while (true) {
        var controlNode = this._parseControl();
        if (controlNode) {
            blockNode.addChild(controlNode);
            continue;
        }

        var functionNode = this._parseFunction();
        if (functionNode) {
            blockNode.addChild(functionNode);
            continue;
        }

        var statementNode = this._parseStatement(STATEMENTS);
        if (statementNode) {
            blockNode.addChild(statementNode);
            continue;
        }

        var commandNode = this._parseCommand(COMMANDS);
        if (commandNode) {
            blockNode.addChild(commandNode);
            continue;
        }

        var commentNode = this._parseComment();
        if (commentNode) {
            blockNode.addChild(commentNode);
            continue;
        }

        break;
    }

    return blockNode;
};

Parser.prototype._parseControl = function () {
    var controlNode;
    if ((controlNode = this._parseIf())) return controlNode;
    if ((controlNode = this._parseLoop())) return controlNode;
    if ((controlNode = this._parseRepeat())) return controlNode;
    if ((controlNode = this._parseUpon())) return controlNode;
};

Parser.prototype._parseFunction = function () {
    var lexer = this._lexer;
    if (!lexer.accept('func', ['function', 'procedure'])) return null;

    // \FUNCTION{funcName}{funcArgs}
    var funcType = this._lexer.get().text; // FUNCTION or PROCEDURE
    lexer.expect('open');
    var funcName = lexer.expect('ordinary');
    lexer.expect('close');
    lexer.expect('open');
    var argsNode = this._parseCloseText();
    lexer.expect('close');
    // <block>
    var blockNode = this._parseBlock();
    // \ENDFUNCTION
    lexer.expect('func', `end${funcType}`);

    var functionNode = new ParseNode('function',
                                     { type: funcType, name: funcName });
    functionNode.addChild(argsNode);
    functionNode.addChild(blockNode);
    return functionNode;
};

Parser.prototype._parseIf = function () {
    if (!this._lexer.accept('func', 'if')) return null;

    var ifNode = new ParseNode('if');

    // { <cond> } <block>
    this._lexer.expect('open');
    ifNode.addChild(this._parseCond());
    this._lexer.expect('close');
    ifNode.addChild(this._parseBlock());

    // ( \ELIF { <cond> } <block> )[0...n]
    var numElif = 0;
    while (this._lexer.accept('func', ['elif', 'elsif', 'elseif'])) {
        this._lexer.expect('open');
        ifNode.addChild(this._parseCond());
        this._lexer.expect('close');
        ifNode.addChild(this._parseBlock());
        numElif++;
    }

    // ( \ELSE <block> )[0..1]
    var hasElse = false;
    if (this._lexer.accept('func', 'else')) {
        hasElse = true;
        ifNode.addChild(this._parseBlock());
    }

    // \ENDIF
    this._lexer.expect('func', 'endif');

    ifNode.value = { numElif: numElif, hasElse: hasElse };
    return ifNode;
};

Parser.prototype._parseLoop = function () {
    if (!this._lexer.accept('func', ['FOR', 'FORALL', 'WHILE'])) return null;

    var loopName = this._lexer.get().text.toLowerCase();
    var loopNode = new ParseNode('loop', loopName);

    // { <cond> } <block>
    this._lexer.expect('open');
    loopNode.addChild(this._parseCond());
    this._lexer.expect('close');
    loopNode.addChild(this._parseBlock());

    // \ENDFOR
    var endLoop = loopName !== 'forall' ? `end${loopName}` : 'endfor';
    this._lexer.expect('func', endLoop);

    return loopNode;
};

Parser.prototype._parseRepeat = function () {
    if (!this._lexer.accept('func', ['REPEAT'])) return null;

    var repeatName = this._lexer.get().text.toLowerCase();
    var repeatNode = new ParseNode('repeat', repeatName);

    // <block>
    repeatNode.addChild(this._parseBlock());

    // \UNTIL
    this._lexer.expect('func', 'until');

    // {<cond>}
    this._lexer.expect('open');
    repeatNode.addChild(this._parseCond());
    this._lexer.expect('close');

    return repeatNode;
};

Parser.prototype._parseUpon = function () {
    if (!this._lexer.accept('func', 'upon')) return null;

    var uponNode = new ParseNode('upon');

    // { <cond> } <block>
    this._lexer.expect('open');
    uponNode.addChild(this._parseCond());
    this._lexer.expect('close');
    uponNode.addChild(this._parseBlock());

    // \ENDUPON
    this._lexer.expect('func', 'endupon');

    return uponNode;
};

var IO_STATEMENTS = ['ensure', 'require', 'input', 'output'];
var STATEMENTS = ['state', 'print', 'return'];
Parser.prototype._parseStatement = function (acceptStatements) {
    if (!this._lexer.accept('func', acceptStatements)) return null;

    var stmtName = this._lexer.get().text.toLowerCase();
    var stmtNode = new ParseNode('statement', stmtName);

    stmtNode.addChild(this._parseOpenText());

    return stmtNode;
};

var COMMANDS = ['break', 'continue'];
Parser.prototype._parseCommand = function (acceptCommands) {
    if (!this._lexer.accept('func', acceptCommands)) return null;

    var cmdName = this._lexer.get().text.toLowerCase();
    var cmdNode = new ParseNode('command', cmdName);

    return cmdNode;
};

Parser.prototype._parseComment = function () {
    if (!this._lexer.accept('func', 'comment')) return null;

    var commentNode = new ParseNode('comment');

    // { \text }
    this._lexer.expect('open');
    commentNode.addChild(this._parseCloseText());
    this._lexer.expect('close');

    return commentNode;
};

Parser.prototype._parseCall = function () {
    var lexer = this._lexer;
    if (!lexer.accept('func', 'call')) return null;

    var anyWhitespace = lexer.get().whitespace;

    // \CALL { <ordinary> } ({ <text> })[0..1]
    lexer.expect('open');
    var funcName = lexer.expect('ordinary');
    lexer.expect('close');

    var callNode = new ParseNode('call');
    callNode.whitespace = anyWhitespace;
    callNode.value = funcName;

    lexer.expect('open');
    var argsNode = this._parseCloseText();
    callNode.addChild(argsNode);
    lexer.expect('close');
    return callNode;
};

Parser.prototype._parseCond =
Parser.prototype._parseCloseText = function () {
    return this._parseText('close');
};
Parser.prototype._parseOpenText = function () {
    return this._parseText('open');
};

Parser.prototype._parseText = function (openOrClose) {
    var textNode = new ParseNode(`${openOrClose}-text`);
    // any whitespace between Atom and CloseText
    var anyWhitespace = false;
    var subTextNode;
    while (true) {
        // atom or call
        subTextNode = this._parseAtom() || this._parseCall();
        if (subTextNode) {
            if (anyWhitespace) subTextNode.whitespace |= anyWhitespace;
            textNode.addChild(subTextNode);
            continue;
        }

        // or close text
        if (this._lexer.accept('open')) {
            subTextNode = this._parseCloseText();

            anyWhitespace = this._lexer.get().whitespace;
            subTextNode.whitespace = anyWhitespace;

            textNode.addChild(subTextNode);
            this._lexer.expect('close');
            anyWhitespace = this._lexer.get().whitespace;
            continue;
        }

        break;
    }

    return textNode;
};

/* The token accepted by atom of specific type */
var ACCEPTED_TOKEN_BY_ATOM = {
    'ordinary': { tokenType: 'ordinary' },
    'math': { tokenType: 'math' },
    'special': { tokenType: 'special' },
    'cond-symbol': {
        tokenType: 'func',
        tokenValues: ['and', 'or', 'not', 'true', 'false', 'to', 'downto'],
    },
    'quote-symbol': {
        tokenType: 'quote',
    },
    'sizing-dclr': {
        tokenType: 'func',
        tokenValues: ['tiny', 'scriptsize', 'footnotesize', 'small', 'normalsize',
            'large', 'Large', 'LARGE', 'huge', 'Huge'],
    },
    'font-dclr': {
        tokenType: 'func',
        tokenValues: ['normalfont', 'rmfamily', 'sffamily', 'ttfamily',
            'upshape', 'itshape', 'slshape', 'scshape',
            'bfseries', 'mdseries', 'lfseries'],
    },
    'font-cmd': {
        tokenType: 'func',
        tokenValues: ['textnormal', 'textrm', 'textsf', 'texttt', 'textup',
            'textit', 'textsl', 'textsc', 'uppercase', 'lowercase', 'textbf',
            'textmd', 'textlf'],
    },
    'text-symbol': {
        tokenType: 'func',
        tokenValues: ['textbackslash'],
    },
};

Parser.prototype._parseAtom = function () {
    for (var atomType in ACCEPTED_TOKEN_BY_ATOM) {
        var acceptToken = ACCEPTED_TOKEN_BY_ATOM[atomType];
        var tokenText = this._lexer.accept(acceptToken.tokenType,
                                           acceptToken.tokenValues);
        if (tokenText === null) continue;

        var anyWhitespace = this._lexer.get().whitespace;
        if (atomType !== 'ordinary' && atomType !== 'math')
            tokenText = tokenText.toLowerCase();
        return new AtomNode(atomType, tokenText, anyWhitespace);
    }
    return null;
};

module.exports = Parser;


================================================
FILE: src/Renderer.js
================================================
/*
* */
var utils = require('./utils');

/*
 * TextStyle - used by TextEnvironment class to handle LaTeX text-style
 * commands or declarations.
 *
 * The font declarations are:
 *  \normalfont, \rmfamily, \sffamily, \ttfamily,
 *  \bfseries, \mdseries, \lfseries,
 *  \upshape, \itshape, \scshape, \slshape.
 *
 * The font commands are:
 *  \textnormal{}, \textrm{}, \textsf{}, \texttt{},
 *  \textbf{}, \textmd{}, \textlf{},
 *  \textup{}, \textit{}, \textsc{}, \textsl{},
 *  \uppercase{}, \lowercase{}.
 *
 * The sizing commands are:
 *  \tiny, \scriptsize, \footnotesize, \small, \normalsize,
 *  \large, \Large, \LARGE, \huge, \Huge.
 **/
function TextStyle (outerFontSize) {
    this._css = {};

    this._fontSize = this._outerFontSize =
        outerFontSize !== undefined ? outerFontSize : 1.0;
}

/*
 * Remember the font size of outer TextStyle object.
 *
 * As we use relative font size 'em', the outer span (has its own TextStyle
 * object) affects the size of the span to which this TextStyle object attached.
 * */
TextStyle.prototype.outerFontSize = function (size) {
    if (size !== undefined) this._outerFontSize = size;
    return this._outerFontSize;
};

TextStyle.prototype.fontSize = function () {
    return this._fontSize;
};

/* Update the font state by TeX command
    cmd - the name of TeX command that alters current font
*/
TextStyle.prototype._fontCommandTable = {
    // -------------- declaration --------------
    // font-family
    normalfont: { 'font-family': 'KaTeX_Main' },
    rmfamily: { 'font-family': 'KaTeX_Main' },
    sffamily: { 'font-family': 'KaTeX_SansSerif' },
    ttfamily: { 'font-family': 'KaTeX_Typewriter' },
    // weight
    bfseries: { 'font-weight': 'bold' },
    mdseries: { 'font-weight': 'medium' },
    lfseries: { 'font-weight': 'lighter' },
    // shape
    upshape: { 'font-style': 'normal', 'font-variant': 'normal' },
    itshape: { 'font-style': 'italic', 'font-variant': 'normal' },
    scshape: { 'font-style': 'normal', 'font-variant': 'small-caps' },
    slshape: { 'font-style': 'oblique', 'font-variant': 'normal' },
    // -------------- command --------------
    // font-family
    textnormal: { 'font-family': 'KaTeX_Main' },
    textrm: { 'font-family': 'KaTeX_Main' },
    textsf: { 'font-family': 'KaTeX_SansSerif' },
    texttt: { 'font-family': 'KaTeX_Typewriter' },
    // weight
    textbf: { 'font-weight': 'bold' },
    textmd: { 'font-weight': 'medium' },
    textlf: { 'font-weight': 'lighter' },
    // shape
    textup: { 'font-style': 'normal', 'font-variant': 'normal' },
    textit: { 'font-style': 'italic', 'font-variant': 'normal' },
    textsc: { 'font-style': 'normal', 'font-variant': 'small-caps' },
    textsl: { 'font-style': 'oblique', 'font-variant': 'normal' },
    // case
    uppercase: { 'text-transform': 'uppercase' },
    lowercase: { 'text-transform': 'lowercase' },
};

TextStyle.prototype._sizingScalesTable = {
    tiny:           0.68,
    scriptsize:     0.80,
    footnotesize:   0.85,
    small:          0.92,
    normalsize:     1.00,
    large:          1.17,
    Large:          1.41,
    LARGE:          1.58,
    huge:           1.90,
    Huge:           2.28,
};

TextStyle.prototype.updateByCommand = function (cmd) {
    // Font command
    var cmdStyles = this._fontCommandTable[cmd];
    if (cmdStyles !== undefined) {
        for (var attr in cmdStyles)
            this._css[attr] = cmdStyles[attr];
        return;
    }

    // Sizing command
    var fontSize = this._sizingScalesTable[cmd];
    if (fontSize !== undefined) {
        this._outerFontSize = this._fontSize;
        this._fontSize = fontSize;
        return;
    }

    throw new ParserError('Unrecognized `text-style` command');
};

TextStyle.prototype.toCSS = function () {
    var cssStr = '';
    for (var attr in this._css) {
        var val = this._css[attr];
        if (val === undefined) continue;
        cssStr += `${attr}:${val};`;
    }
    if (this._fontSize !== this._outerFontSize)
        cssStr += `font-size:${this._fontSize / this._outerFontSize}em;`;

    return cssStr;
};

/*
 * TextEnvironment - renders the children nodes in a ParseNode of type
 * 'close-text' or 'open-text' to HTML.
 **/
function TextEnvironment (nodes, textStyle) {
    this._nodes = nodes;
    this._textStyle = textStyle;
}

TextEnvironment.prototype._renderCloseText = function (node, backend) {
    var newTextStyle = new TextStyle(this._textStyle.fontSize());
    var closeTextEnv = new TextEnvironment(node.children, newTextStyle);
    if (node.whitespace) this._html.putText(' ');
    this._html.putHTML(closeTextEnv.renderToHTML(backend));
};

TextEnvironment.prototype.renderToHTML = function (backend) {
    this._html = new HTMLBuilder();

    var node;
    while ((node = this._nodes.shift()) !== undefined) {
        var type = node.type;
        var text = node.value;

        // Insert whitespace before the atom if necessary
        if (node.whitespace) this._html.putText(' ');

        switch (type) {
            case 'ordinary':
                this._html.putText(text);
                break;
            case 'math':
                if (typeof backend === 'undefined') {
                    throw EvalError('No math backend found. Please setup KaTeX or MathJax.');
                }
                else if (backend.name === 'katex') {
                    this._html.putHTML(backend.driver.renderToString(text));
                }
                else if (backend.name === 'mathjax') {
                    if (backend.version === 3) {
                        // use synchronous conversion available in 3.x
                        this._html.putHTML(backend.driver.tex2chtml(text, { display: false }).outerHTML);
                    }
                    else {
                        // keep math text, typeset later
                        this._html.putText(`$${text}$`);
                    }
                }
                else {
                    throw new EvalError(`Unknown math backend ${backend}`);
                }
                break;
            case 'cond-symbol':
                this._html
                    .beginSpan('ps-keyword')
                    .putText(text.toLowerCase())
                    .endSpan();
                break;
            case 'special':
                if (text === '\\\\') {
                    this._html.putHTML('<br/>');
                    break;
                }
                var replace = {
                    '\\{': '{',
                    '\\}': '}',
                    '\\$': '$',
                    '\\&': '&',
                    '\\#': '#',
                    '\\%': '%',
                    '\\_': '_',
                };
                var replaceStr = replace[text];
                this._html.putText(replaceStr);
                break;
            case 'text-symbol':
                var name2Values = {
                    'textbackslash': '\\',
                };
                var symbolValue = name2Values[text];
                this._html.putText(symbolValue);
                break;
            case 'quote-symbol':
                var quoteReplace = {
                    '`': '‘',
                    '``': '“',
                    '\'': '’',
                    '\'\'': '”',
                };
                var realQuote = quoteReplace[text];
                this._html.putText(realQuote);
                break;
            case 'call':
                // \CALL{funcName}{funcArgs}
                // ==>
                // funcName(funcArgs)
                this._html.beginSpan('ps-funcname').putText(text).endSpan();
                this._html.write('(');
                var argsTextNode = node.children[0];
                this._renderCloseText(argsTextNode, backend);
                this._html.write(')');
                break;
            case 'close-text':
                this._renderCloseText(node, backend);
                break;
            // There are two kinds of typestyle commands:
            //      command (e.g. \textrm{...}).
            // and
            //      declaration (e.g. { ... \rmfamily ... })
            //
            // For typestyle commands, it works as following:
            //      \textsf     --> create a new typestyle
            //      {           --> save the current typestyle, and then use the new one
            //      ...         --> the new typestyle is in use
            //      }           --> restore the last typestyle
            //
            // For typestyle declaration, it works a little bit diferrently:
            //      {           --> save the current typestyle, and then create and use
            //                      an identical one
            //      ...         --> the new typestyle is in use
            //      \rmfamily   --> create a new typestyle
            //      ...         --> the new typestyle is in use
            //      }           --> restore the last typestyle
            case 'font-dclr':
            case 'sizing-dclr':
                this._textStyle.updateByCommand(text);
                this._html.beginSpan(null, this._textStyle.toCSS());
                var textEnvForDclr = new TextEnvironment(this._nodes,
                                                         this._textStyle);
                this._html.putHTML(textEnvForDclr.renderToHTML(backend));
                this._html.endSpan();
                break;
            case 'font-cmd':
                var textNode = this._nodes[0];
                if (textNode.type !== 'close-text') continue;

                var innerTextStyle = new TextStyle(this._textStyle.fontSize());
                innerTextStyle.updateByCommand(text);
                this._html.beginSpan(null, innerTextStyle.toCSS());
                var textEnvForCmd = new TextEnvironment(textNode.children,
                                                        innerTextStyle);
                this._html.putHTML(textEnvForCmd.renderToHTML(backend));
                this._html.endSpan();
                break;
            default:
                throw new ParseError(`Unexpected ParseNode of type ${node.type}`);
        }
    }

    return this._html.toMarkup();
};

/* HTMLBuilder - A helper class for constructing HTML */
function HTMLBuilder () {
    this._body = [];
    this._textBuf = [];
}

HTMLBuilder.prototype.beginDiv = function (className, style, extraStyle) {
    this._beginTag('div', className, style, extraStyle);
    this._body.push('\n'); // make the generated HTML more human friendly
    return this;
};

HTMLBuilder.prototype.endDiv = function () {
    this._endTag('div');
    this._body.push('\n'); // make the generated HTML more human friendly
    return this;
};

HTMLBuilder.prototype.beginP = function (className, style, extraStyle) {
    this._beginTag('p', className, style, extraStyle);
    this._body.push('\n'); // make the generated HTML more human friendly
    return this;
};

HTMLBuilder.prototype.endP = function () {
    this._flushText();
    this._endTag('p');
    this._body.push('\n'); // make the generated HTML more human friendly
    return this;
};

HTMLBuilder.prototype.beginSpan = function (className, style, extraStyle) {
    this._flushText();
    return this._beginTag('span', className, style, extraStyle);
};

HTMLBuilder.prototype.endSpan = function () {
    this._flushText();
    return this._endTag('span');
};

HTMLBuilder.prototype.putHTML = function (html) {
    this._flushText();
    this._body.push(html);
    return this;
};

HTMLBuilder.prototype.putText = function (text) {
    this._textBuf.push(text);
    return this;
};

HTMLBuilder.prototype.write = function (html) {
    this._body.push(html);
};

HTMLBuilder.prototype.toMarkup = function () {
    this._flushText();
    var html = this._body.join('');
    return html.trim();
};

HTMLBuilder.prototype.toDOM = function () {
    var html = this.toMarkup();
    var div = document.createElement('div');
    div.innerHTML = html;
    return div.firstChild;
};

HTMLBuilder.prototype._flushText = function () {
    if (this._textBuf.length === 0) return;

    var text = this._textBuf.join('');
    this._body.push(this._escapeHtml(text));
    // this._body.push(text);
    this._textBuf = [];
};

/* Write the beginning of a DOM element
    tag - the tag of the element
    className - the className for the tag
    style - CSS style that applies directly on the tag. This parameter can be
            either a string, e.g., 'color:red', or an object, e.g.
            { color: 'red', margin-left: '1em'}
*/
HTMLBuilder.prototype._beginTag = function (tag, className, style, extraStyle) {
    var spanHTML = `<${tag}`;
    if (className) spanHTML += ` class="${className}"`;
    if (style) {
        var styleCode;
        if (utils.isString(style)) {
            styleCode = style;
        }
        else { // style
            styleCode = '';
            for (var attrName in style) {
                attrVal = style[attrName];
                styleCode += `${attrName}:${attrVal};`;
            }
        }
        if (extraStyle) styleCode += extraStyle;
        spanHTML += ` style="${styleCode}"`;
    }
    spanHTML += '>';
    this._body.push(spanHTML);
    return this;
};

HTMLBuilder.prototype._endTag = function (tag) {
    this._body.push(`</${tag}>`);
    return this;
};

var entityMap = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': '&quot;',
    "'": '&#39;',
    "/": '&#x2F;',
};

HTMLBuilder.prototype._escapeHtml = function (string) {
    return String(string).replace(
        /[&<>"'/]/g,
        (s) => entityMap[s]
    );
};

/*
 * RendererOptions - represents options that Renderer accepts.
 *
 * The following are possible options:
 *      indentSize - The indent size of inside a control block, e.g. if, for,
 *          etc. The unit must be in 'em'. Default value: '1.2em'.
 *      commentDelimiter  - The delimiters used to start and end a comment region.
 *          Note that only line comments are supported. Default value: '//'.
 *      lineNumber - Whether line numbering is enabled. Default value: false.
 *      lineNumberPunc - The punctuation that follows line number. Default
 *          value: ':'.
 *      noEnd - Whether block ending, like `end if`, end procedure`, etc., are
 *          showned. Default value: false.
 *      captionCount - Set the caption counter to this new value.
 *      titlePrefix - The prefix in the title of the algorithm. Default value: 'Algorithm'.
 *
 **/
function RendererOptions (options) {
    options = options || {};
    this.indentSize =
        options.indentSize ? this._parseEmVal(options.indentSize) : 1.2;
    this.commentDelimiter  = options.commentDelimiter !== undefined ? options.commentDelimiter : ' // ';
    this.lineNumberPunc = options.lineNumberPunc !== undefined ? options.lineNumberPunc : ':';
    this.lineNumber = options.lineNumber !== undefined ? options.lineNumber : false;
    this.noEnd = options.noEnd !== undefined ? options.noEnd : false;
    this.scopeLines = options.scopeLines !== undefined ? options.scopeLines : false;
    if (options.captionCount !== undefined)
        Renderer.captionCount = options.captionCount;
    this.titlePrefix = options.titlePrefix !== undefined ? options.titlePrefix : 'Algorithm';
}

RendererOptions.prototype._parseEmVal = function (emVal) {
    emVal = emVal.trim();
    if (emVal.indexOf('em') !== emVal.length - 2)
        throw new TypeError('Unit error; expected `em` suffix');

    return Number(emVal.substring(0, emVal.length - 2));
};

/*
 * Renderer - Converts a parse tree to HTML
 *
 * There are three levels for renderer: Group (Block), Line and Segment,
 * which are rendered to HTML tag, <div>, <p>, and <span>, respectively.
 **/
function Renderer (parser, options) {
    this._root = parser.parse();
    this._options = new RendererOptions(options);
    this._openLine = false;
    this._blockLevel = 0;
    this._textLevel = -1;
    this._globalTextStyle = new TextStyle();
    this.backend = undefined;

    try {
        if (typeof katex === 'undefined')
            katex = require('katex');
    }
    catch (_) { /* ignore */ }

    try {
        if (typeof MathJax === 'undefined')
            MathJax = require('mathjax');
    }
    catch (_) { /* ignore */ }

    if (typeof katex !== 'undefined') {
        this.backend = {
            'name' : 'katex',
            'driver' : katex,
        };
    }
    else if (typeof MathJax !== 'undefined') {
        this.backend = {
            'name' : 'mathjax',
            'version': parseInt(MathJax.version.split('.')[0]),
            'driver' : MathJax,
        };
    }
}

/*  The global counter for the numbering of the algorithm environment */
Renderer.captionCount = 0;

Renderer.prototype.toMarkup = function () {
    var html = this._html = new HTMLBuilder();
    this._buildTree(this._root);
    delete this._html;
    return html.toMarkup();
};

Renderer.prototype.toDOM = function () {
    var html = this.toMarkup();
    var div = document.createElement('div');
    div.innerHTML = html;
    return div.firstChild;
};

Renderer.prototype._beginGroup = function (name, extraClass, style) {
    this._closeLineIfAny();
    this._html.beginDiv(`ps-${name}${extraClass ? ` ${extraClass}` : ''}`,
                        style);
};

Renderer.prototype._endGroup = function (name) {
    this._closeLineIfAny();
    this._html.endDiv();
};

Renderer.prototype._beginBlock = function () {
    // The first block have to extra left margin when line number are displayed
    var extraIndentForFirstBlock =
        this._options.lineNumber && this._blockLevel === 0 ? 0.6 : 0;
    var blockIndent = this._options.indentSize + extraIndentForFirstBlock;

    // We also need to handle the extra margin for scope lines
    // We divide the block indent by 2 because the other margin will be after the indent symbol
    if (this._options.scopeLines)
        blockIndent /= 2;

    this._beginGroup('block', null, {
        'margin-left': `${blockIndent}em`,
    });
    this._blockLevel++;
};

Renderer.prototype._endBlock = function () {
    this._closeLineIfAny();
    this._endGroup();
    this._blockLevel--;
};

Renderer.prototype._newLine = function () {
    this._closeLineIfAny();

    this._openLine = true;

    // For every new line, reset the relative sizing of text style
    this._globalTextStyle.outerFontSize(1.0);

    var indentSize = this._options.indentSize;
    // if this line is for code (e.g. \STATE)
    if (this._blockLevel > 0) {
        this._numLOC++;

        this._html.beginP('ps-line ps-code', this._globalTextStyle.toCSS());

        // We need to consider the indent width for linenumbers and scopelines
        var extraIndentSize = this._options.lineNumber ? indentSize * 1.25 : 0;
        extraIndentSize += this._options.scopeLines ? indentSize * 0.1 : 0;

        // We add this width if we need to pad the line (e.g., with linenumber).
        // We don't need to handle scope lines here, as they do not add any extra text in the line.
        if (this._options.lineNumber) {
            this._html
                .beginSpan('ps-linenum', {
                    'left': `${-((this._blockLevel - 1) * (extraIndentSize))}em`,
                })
                .putText(this._numLOC + this._options.lineNumberPunc)
                .endSpan();
        }
    }
    // if this line is for pre-conditions (e.g. \REQUIRE)
    else {
        this._html.beginP('ps-line', {
            'text-indent': `${-indentSize}em`,
            'padding-left': `${indentSize}em`,
        }, this._globalTextStyle.toCSS());
    }
};

Renderer.prototype._closeLineIfAny = function () {
    if (!this._openLine) return;

    this._html.endP();

    this._openLine = false;
};

Renderer.prototype._typeKeyword = function (keyword) {
    this._html.beginSpan('ps-keyword').putText(keyword).endSpan();
};

Renderer.prototype._typeFuncName = function (funcName) {
    this._html.beginSpan('ps-funcname').putText(funcName).endSpan();
};

Renderer.prototype._typeText = function (text) {
    this._html.write(text);
};

Renderer.prototype._buildTreeForAllChildren = function (node) {
    var children = node.children;
    for (var ci = 0; ci < children.length; ci++)
        this._buildTree(children[ci]);
};

// The comment nodes at the beginning of blockNode are comments for controls
// Thus they should be rendered out of block
Renderer.prototype._buildCommentsFromBlock = function (blockNode) {
    var children = blockNode.children;
    while (children.length > 0 && children[0].type === 'comment') {
        var commentNode = children.shift();
        this._buildTree(commentNode);
    }
};

Renderer.prototype._buildTree = function (node) {
    var ci; var child; var textNode;
    switch (node.type) {
        // The hierarchicy of build tree: Group (Block) > Line > Text
        // ----------------- Groups -------------------------------------
        case 'root':
            this._beginGroup('root');
            this._buildTreeForAllChildren(node);
            this._endGroup();
            break;
        case 'algorithm':
            // First, decide the caption if any
            var lastCaptionNode;
            for (ci = 0; ci < node.children.length; ci++) {
                child = node.children[ci];
                if (child.type !== 'caption') continue;
                lastCaptionNode = child;
                Renderer.captionCount++;
            }
            // Then, build the header for algorithm
            if (lastCaptionNode) {
                this._beginGroup('algorithm', 'with-caption');
                this._buildTree(lastCaptionNode);
            }
            else {
                this._beginGroup('algorithm');
            }
            // Then, build other nodes
            for (ci = 0; ci < node.children.length; ci++) {
                child = node.children[ci];
                if (child.type === 'caption') continue;
                this._buildTree(child);
            }
            this._endGroup();
            break;
        case 'algorithmic':
            // Check if we need to add additional classes for the provided options
            var divClasses = this._options.lineNumber ? ' with-linenum ' : '';
            divClasses += this._options.scopeLines ? ' with-scopelines ' : '';

            if (divClasses) {
                this._beginGroup('algorithmic', divClasses);
                this._numLOC = 0;
            }
            else {
                this._beginGroup('algorithmic');
            }
            this._buildTreeForAllChildren(node);
            this._endGroup();
            break;
        case 'block':
            // node: <block>
            // ==>
            // HTML: <div class="ps-block"> ... </div>
            this._beginBlock();
            this._buildTreeForAllChildren(node);
            this._endBlock();
            break;
        // ----------------- Mixture (Groups + Lines) -------------------
        case 'function':
            // \FUNCTION{<ordinary>}{<text>} <block> \ENDFUNCTION
            // ==>
            // function <ordinary>(<text>)
            // ...
            // end function
            var funcType = node.value.type.toLowerCase();
            var defFuncName = node.value.name;
            textNode = node.children[0];
            var blockNode = node.children[1];
            this._newLine();
            this._typeKeyword(`${funcType} `);
            this._typeFuncName(defFuncName);
            this._typeText('(');
            this._buildTree(textNode);
            this._typeText(')');

            this._buildCommentsFromBlock(blockNode);
            this._buildTree(blockNode);

            if (!this._options.noEnd) {
                this._newLine();
                this._typeKeyword(`end ${funcType}`);
            }
            break;
        case 'if':
            // \IF { <cond> }
            // ==>
            // <p class="ps-line">
            //      <span class="ps-keyword">if</span>
            //      ...
            //      <span class="ps-keyword">then</span>
            // </p>
            this._newLine();
            this._typeKeyword('if ');
            ifCond = node.children[0];
            this._buildTree(ifCond);
            this._typeKeyword(' then');
            // <block>
            var ifBlock = node.children[1];
            this._buildCommentsFromBlock(ifBlock);
            this._buildTree(ifBlock);

            // ( \ELIF {<cond>} <block> )[0..n]
            var numElif = node.value.numElif;
            for (var ei = 0 ; ei < numElif; ei++) {
                // \ELIF {<cond>}
                // ==>
                // <p class="ps-line">
                //      <span class="ps-keyword">elif</span>
                //      ...
                //      <span class="ps-keyword">then</span>
                // </p>
                this._newLine();
                this._typeKeyword('else if ');
                var elifCond = node.children[2 + 2 * ei];
                this._buildTree(elifCond);
                this._typeKeyword(' then');

                // <block>
                var elifBlock = node.children[2 + 2 * ei + 1];
                this._buildCommentsFromBlock(elifBlock);
                this._buildTree(elifBlock);
            }

            // ( \ELSE <block> )[0..1]
            var hasElse = node.value.hasElse;
            if (hasElse) {
                // \ELSE
                // ==>
                // <p class="ps-line">
                //      <span class="ps-keyword">else</span>
                // </p>
                this._newLine();
                this._typeKeyword('else');

                // <block>
                var elseBlock = node.children[node.children.length - 1];
                this._buildCommentsFromBlock(elseBlock);
                this._buildTree(elseBlock);
            }

            if (!this._options.noEnd) {
                // ENDIF
                this._newLine();
                this._typeKeyword('end if');
            }
            break;
        case 'loop':
            // \FOR{<cond>} or \WHILE{<cond>}
            // ==>
            // <p class="ps-line">
            //      <span class="ps-keyword">for</span>
            //      ...
            //      <span class="ps-keyword">do</span>
            // </p>
            this._newLine();
            var loopType = node.value;
            var displayLoopName = {
                'for': 'for',
                'forall': 'for all',
                'while': 'while',
            };
            this._typeKeyword(`${displayLoopName[loopType]} `);
            var loopCond = node.children[0];
            this._buildTree(loopCond);
            this._typeKeyword(' do');

            // <block>
            var block = node.children[1];
            this._buildCommentsFromBlock(block);
            this._buildTree(block);

            if (!this._options.noEnd) {
                // \ENDFOR or \ENDWHILE
                // ==>
                // <p class="ps-line">
                //      <span class="ps-keyword">end for</span>
                // </p>
                this._newLine();
                var endLoopName = loopType === 'while' ? 'end while' : 'end for';
                this._typeKeyword(endLoopName);
            }
            break;
        case 'repeat':
            // \REPEAT
            // ==>
            // <p class="ps-line">
            //     <span class="ps-keyword">repeat</span>
            // </p>
            this._newLine();
            this._typeKeyword('repeat');

            // block
            var repeatBlock = node.children[0];
            this._buildCommentsFromBlock(repeatBlock);
            this._buildTree(repeatBlock);


            // \UNTIL{<cond>}
            // ==>
            // <p class="ps-line">
            //     <span class="ps-keyword">until</span>
            // </p>
            this._newLine();
            this._typeKeyword('until ');
            var repeatCond = node.children[1];
            this._buildTree(repeatCond);

            break;
        case 'upon':
            // \UPON { <cond> }
            // ==>
            // <p class="ps-line">
            //      <span class="ps-keyword">upon</span>
            // </p>
            this._newLine();
            this._typeKeyword('upon ');
            uponCond = node.children[0];
            this._buildTree(uponCond);
            // <block>
            var uponBlock = node.children[1];
            this._buildCommentsFromBlock(uponBlock);
            this._buildTree(uponBlock);

            if (!this._options.noEnd) {
                // ENDUPON
                this._newLine();
                this._typeKeyword('end upon');
            }
            break;
        // ------------------- Lines -------------------
        case 'command':
            var cmdName = node.value;
            var displayCmdName = {
                'break': 'break',
                'continue': 'continue',
            }[cmdName];

            this._newLine();
            if (displayCmdName) this._typeKeyword(displayCmdName);
            break;
        case 'caption':
            this._newLine();
            this._typeKeyword(`${this._options.titlePrefix
            } ${Renderer.captionCount} `);
            textNode = node.children[0];
            this._buildTree(textNode);
            break;
        case 'comment':
            textNode = node.children[0];
            this._html.beginSpan('ps-comment');
            this._html.putText(this._options.commentDelimiter);
            this._buildTree(textNode);
            this._html.endSpan();
            break;
        case 'statement':
            // statements: \STATE, \ENSURE, \PRINT, \RETURN, etc.
            var stmtName = node.value;
            var displayStmtName = {
                'state': '',
                'ensure': 'Ensure: ',
                'require': 'Require: ',
                'input': 'Input: ',
                'output': 'Output: ',
                'print': 'print ',
                'return': 'return ',
            }[stmtName];

            this._newLine();
            if (displayStmtName) this._typeKeyword(displayStmtName);
            textNode = node.children[0];
            this._buildTree(textNode);
            break;
        // ------------------- Text -------------------
        case 'open-text':
            var openTextEnv = new TextEnvironment(node.children,
                                                  this._globalTextStyle);
            this._html.putHTML(openTextEnv.renderToHTML(this.backend));
            break;
        case 'close-text':
            var outerFontSize = this._globalTextStyle.fontSize();
            var newTextStyle = new TextStyle(outerFontSize);
            var closeTextEnv = new TextEnvironment(node.children, newTextStyle);
            this._html.putHTML(closeTextEnv.renderToHTML(this.backend));
            break;
        default:
            throw new ParseError(`Unexpected ParseNode of type ${node.type}`);
    }
};

module.exports = Renderer;


================================================
FILE: src/utils.js
================================================
module.exports = {
    isString: (str) => (typeof str === 'string') || (str instanceof String),

    isObject: (obj) => (typeof obj === 'object' && (obj instanceof Object)),

    toString: function (obj) {
        if (!this.isObject(obj))
            return `${obj}`;

        var parts = [];
        for (var member in obj)
            parts.push(`${member}: ${this.toString(obj[member])}`);

        return parts.join(', ');
    },
};


================================================
FILE: static/body.html.part
================================================
</head>

<body>
    <pre id="test-basics" data-line-number=true>
        \begin{algorithm}
        \caption{Test text-style}
        \begin{algorithmic}
        \REQUIRE some preconditions
        \ENSURE some postconditions
        \INPUT some inputs
        \OUTPUT some outputs
        \PROCEDURE{Test-Declarations}{}
            \STATE font families: {\sffamily sffamily, \ttfamily ttfamily, \normalfont normalfont, \rmfamily rmfamily.}
            \STATE font weights: {normal weight, \bfseries bold, \mdseries
            medium, \lfseries lighter. }
            \STATE font shapes: {\itshape itshape \scshape Small-Caps \slshape slshape \upshape upshape.}
            \STATE font sizes: \tiny tiny \scriptsize scriptsize \footnotesize
            footnotesize \small small \normalsize normal \large large \Large Large
            \LARGE LARGE \huge huge \Huge Huge \normalsize
        \ENDPROCEDURE
        \PROCEDURE{Test-Commands}{}
            \STATE \textnormal{textnormal,} \textrm{textrm,} \textsf{textsf,} \texttt{texttt.}
            \STATE \textbf{textbf,} \textmd{textmd,} \textlf{textlf.}
            \STATE \textup{textup,} \textit{textit,} \textsc{textsc,} \textsl{textsl.}
            \STATE \uppercase{uppercase,} \lowercase{LOWERCASE.}
        \ENDPROCEDURE
        \PROCEDURE{Test-Colors}{}
            \STATE colors: $\color{red}{red}$, $\color{green}{green}$, $\color{blue}{blue}$
            \STATE colors: $\color{yellow}{yellow}$, $\color{cyan}{cyan}$, $\color{magenta}{magenta}$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}

        \begin{algorithm}
        \caption{Test atoms}
        \begin{algorithmic}
        \STATE \textbf{Specials:} \{ \} \$ \& \# \% \_
        \STATE \textbf{Bools:} \AND \OR \NOT \TRUE \FALSE
        \STATE \textbf{Carriage return:} first line \\ second line
        \STATE \textbf{Text-symbols:} \textbackslash
        \STATE \textbf{Quote-symbols:} `single quotes', ``double quotes''
        \STATE \textbf{Math:} $(\mathcal{C}_m)$, $i \gets i + 1$, $E=mc^2$, \( x^n + y^n = z^n \), $\$$, \(\$\)
        \END{ALGORITHMIC}
        \END{ALGORITHM}
    </pre>
    <pre id="test-codes">
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELIF{&lt;cond&gt;}
                \STATE &lt;block&gt;
            \ELSE
                \STATE &lt;block&gt;
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Test-For}{$n$}
            \STATE $i \gets 0$
            \FOR{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-To}{$n$}
            \STATE $i \gets 0$
            \FOR{$i$ \TO $n$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-DownTo}{$n$}
            \FOR{$i \gets n$ \DOWNTO $0$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-For-All}{$n$}
            \FORALL{$i \in \{0, 1, \cdots, n\}$}
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \PROCEDURE{Test-While}{$n$}
            \STATE $i \gets 0$
            \WHILE{$i < n$}
                \PRINT $i$
                \STATE $i \gets i + 1$
            \ENDWHILE
        \ENDPROCEDURE
        \PROCEDURE{Test-Repeat}{$n$}
            \STATE $i \gets 0$
            \REPEAT
                \PRINT $i$
                \STATE $i \gets i + 1$
            \UNTIL{$i>n$}
        \ENDPROCEDURE
        \PROCEDURE{Test-Break-Continue}{$n$}
            \FOR{$i = 0$ \TO $2n$}
                \IF{$i < n/2$}
                    \CONTINUE
                \ELIF{$i > n$}
                    \BREAK
                \ENDIF
                \PRINT $i$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
        \begin{algorithm}
        \caption{Test statements and comments}
        \begin{algorithmic}
        \PROCEDURE{Test-Statements}{}
            \STATE This line is a normal statement
            \PRINT \texttt{`this is print statement'}
            \RETURN $retval$
        \ENDPROCEDURE

        \PROCEDURE{Test-Comments}{} \COMMENT{comment for procedure}
            \STATE a statement \COMMENT{inline comment}
            \STATE \COMMENT{line comment}
            \IF{some condition}\COMMENT{comment for if}
                \RETURN \TRUE \COMMENT{another inline comment}
            \ELSE \COMMENT{comment for else}
                \RETURN \FALSE \COMMENT{yet another inline comment}
            \ENDIF
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples" data-title-prefix="Procedure">
        % This quicksort algorithm is extracted from Chapter 7, Introduction
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$}
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre id="test-examples-custom-title">
        % This quicksort algorithm is extracted from Chapter 7, Introduction 
        % to Algorithms (3rd edition)
        \begin{algorithm}
        \caption{Quicksort}
        \begin{algorithmic}
        \PROCEDURE{Quicksort}{$A, p, r$}
            \IF{$p < r$} 
                \STATE $q = $ \CALL{Partition}{$A, p, r$}
                \STATE \CALL{Quicksort}{$A, p, q - 1$}
                \STATE \CALL{Quicksort}{$A, q + 1, r$}
            \ENDIF
        \ENDPROCEDURE
        \PROCEDURE{Partition}{$A, p, r$}
            \STATE $x = A[r]$
            \STATE $i = p - 1$
            \FOR{$j = p$ \TO $r - 1$}
                \IF{$A[j] < x$}
                    \STATE $i = i + 1$
                    \STATE exchange
                    $A[i]$ with $A[j]$
                \ENDIF
                \STATE exchange $A[i]$ with $A[r]$
            \ENDFOR
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-line-number=true>
        \begin{algorithm}
        \caption{Classical Euclidean Algorithm}
        \begin{algorithmic}
            \PROCEDURE{Euclid}{$a,b$}
                \WHILE{$a \neq b$}
                    \IF{$a > b$}
                        \STATE $a \gets a - b$
                    \ELSE
                        \STATE $b \gets b - a$
                    \ENDIF
                \ENDWHILE
                \RETURN $a$
            \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>
    <pre class="scopeline-pseudocode" data-no-end=true>
        \begin{algorithm}
        \caption{DBSCAN}
        \begin{algorithmic}
        \INPUT A dataset $D$, the $\varepsilon$ distance threshold, and the minimum number of points $minPts$
        \OUTPUT A set of clusters $K$
        \PROCEDURE{DBSCAN}{$D, \varepsilon, minPts$}
            \STATE $K \gets \emptyset$
            \FORALL{$p \in D$}
                \IF{$p$ has not been visited}
                    \STATE mark $p$ as visited
                    \STATE $N_{\varepsilon}(p) \gets $ \textsc{RangeQuery}$(p, \varepsilon, D)$
                    \IF{$N_{\varepsilon}(p) < minPts$}
                        \STATE mark $p$ as noise
                    \ELSE
                        \COMMENT{p is a core object}
                        \STATE $C \gets $ \textsc{ExpandCluster}$(p, N_{\varepsilon}(p))$
                        \STATE $K \gets K \cup \{C\}$
                    \ENDIF
                \ENDIF
            \ENDFOR
            \RETURN $K$
        \ENDPROCEDURE
        \end{algorithmic}
        \end{algorithm}
    </pre>


================================================
FILE: static/footer.html.part
================================================
    <script>
        pseudocode.renderElement(document.getElementById("test-basics"),
                                 {
                                     lineNumber: false,
                                     noEnd: true
                                 });
        pseudocode.renderElement(document.getElementById("test-codes"));
        pseudocode.renderElement(document.getElementById("test-examples"),
                                 {
                                     lineNumber: true,
                                     noEnd: false
                                 });
        pseudocode.renderElement(document.getElementById("test-examples-custom-title"),
                                 {
                                     lineNumber: true,
                                     noEnd: false,
                                     titlePrefix: "My pretty Algorithm"
                                 });                                   
        pseudocode.renderClass("scopeline-pseudocode", { scopeLines: true });
    </script>
</body>
</html>


================================================
FILE: static/katex.html.part
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with KaTeX</title>

    <!-- Setup KaTeX -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"
            integrity="sha512-EKW5YvKU3hpyyOcN6jQnAxO/L8gts+YdYV6Yymtl8pk9YlYFtqJgihORuRoBXK8/cOIlappdU6Ms8KdK6yBCgA=="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>


================================================
FILE: static/mathjax-v2.html.part
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.9/MathJax.js?config=TeX-AMS_CHTML-full"
            integrity="sha256-DViIOMYdwlM/axqoGDPeUyf0urLoHMN4QACBKyB58Uw="
            crossorigin="anonymous" referrerpolicy="no-referrer">
    </script>
    <script type="text/x-mathjax-config">
        MathJax.Hub.Config({
            tex2jax: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
                skipTags: ['code', 'script', 'noscript', 'style', 'textarea', 'pre'],
            }
        });
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>


================================================
FILE: static/mathjax-v3.html.part
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script>
        MathJax = {
            tex: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
            }
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-chtml-full.js"
            integrity="sha256-kbAFUDxdHwlYv01zraGjvjNZayxKtdoiJ38bDTFJtaQ="
            crossorigin="anonymous">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>


================================================
FILE: static/mathjax-v4.html.part
================================================
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>pseudocode.js Samples with MathJax</title>

    <!-- Setup MathJax -->
    <script>
        MathJax = {
            tex: {
                inlineMath: [['$','$'], ['\\(','\\)']],
                displayMath: [['$$','$$'], ['\\[','\\]']],
                processEscapes: true,
                processEnvironments: true,
            }
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/mathjax@4.0.0-beta.3/tex-chtml.js"
            integrity="sha256-ljPODBK7Jf/VfUrVqec63xzZbysEmwB9Ab20TWRMQRU="
            crossorigin="anonymous">
    </script>

    <!-- Pseudocode -->
    <link rel="stylesheet" href="pseudocode.css" type="text/css">
    <script src="pseudocode.js" type="text/javascript"></script>


================================================
FILE: static/pseudocode.css
================================================
@import url('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css');

.ps-root {
    font-family: KaTeX_Main, 'Times New Roman', Times, serif;
    font-size: 1em;
    font-weight: 100;
    -webkit-font-smoothing: antialiased !important;
}
.ps-root .ps-algorithm {
    margin: 0.8em 0;
    /* algorithm environment has borders; algorithmic has not */
    border-top: 3px solid;
    border-bottom: 2px solid;
}
.ps-root .ps-algorithm.with-caption > .ps-line:first-child {
    border-bottom: 2px solid;
}

.ps-root .katex {
    text-indent: 0;
    font-size: 1em;
}
.ps-root .MathJax_CHTML, .ps-root .MathJax {
    text-indent: 0;
    font-size: 1em !important;
}

.ps-root .ps-line {
    margin: 0; padding: 0;
    line-height: 1.2;
}
.ps-root .ps-funcname {
    font-family: KaTeX_Main, 'Times New Roman', Times, serif;
    font-weight: normal;
    font-variant: small-caps;
    font-style: normal;
    text-transform: none;
}
.ps-root .ps-keyword {
    font-family: KaTeX_Main, 'Times New Roman', Times, serif;
    font-weight: bold;
    font-variant: normal;
    font-style: normal;
    text-transform: none;
}
.ps-root .ps-comment {
    font-family: KaTeX_Main, 'Times New Roman', Times, serif;
    font-weight: normal;
    font-variant: normal;
    font-style: normal;
    text-transform: none;
}

/* line number support */
.ps-root .ps-linenum {
    font-size: 0.8em;
    line-height: 1em;
    width: 1.6em;
    text-align: right;
    display: inline-block;
    position: relative;
    padding-right: 0.3em;
}
.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code {
    text-indent: -1.6em;
}
.ps-root .ps-algorithmic.with-linenum .ps-line.ps-code > span {
    text-indent: 0em;
}

/* scope lines support */
.ps-root .ps-algorithmic.with-scopelines div.ps-block {
    border-left-style: solid;
    border-left-width: 0.1em;
    padding-left: 0.6em;
}

.ps-root .ps-algorithmic.with-scopelines > div.ps-block {
    border-left: none;
}
Download .txt
gitextract_lpmuwsmr/

├── .eslintrc
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── docs/
│   ├── index.html
│   ├── javascripts/
│   │   └── scale.fix.js
│   ├── katex-samples.html
│   ├── mathjax-v2-samples.html
│   ├── mathjax-v3-samples.html
│   ├── mathjax-v4-samples.html
│   ├── params.json
│   ├── pseudocode.css
│   ├── pseudocode.js
│   └── stylesheets/
│       └── styles.css
├── package.json
├── pseudocode.js
├── src/
│   ├── Lexer.js
│   ├── ParseError.js
│   ├── Parser.js
│   ├── Renderer.js
│   └── utils.js
└── static/
    ├── body.html.part
    ├── footer.html.part
    ├── katex.html.part
    ├── mathjax-v2.html.part
    ├── mathjax-v3.html.part
    ├── mathjax-v4.html.part
    └── pseudocode.css
Download .txt
SYMBOL INDEX (16 symbols across 5 files)

FILE: docs/javascripts/scale.fix.js
  function gestureStart (line 11) | function gestureStart() {

FILE: docs/pseudocode.js
  function p (line 1) | function p(o,s,a){function l(n,e){if(!s[n]){if(!o[n]){var t="function"==...
  function a (line 1) | function a(e,t){var n=new r(e);var i=new o(n);return new s(i,t)}
  function i (line 1) | function i(e,t,n){var i=`Error: ${e}`;if(t!==undefined&&n!==undefined){i...
  function A (line 1) | function A(e){this._css={};this._fontSize=this._outerFontSize=e!==undefi...
  function B (line 1) | function B(e,t){this._nodes=e;this._textStyle=t}
  function _ (line 1) | function _(){this._body=[];this._textBuf=[]}
  function r (line 1) | function r(e){e=e||{};this.indentSize=e.indentSize?this._parseEmVal(e.in...
  function F (line 1) | function F(e,t){this._root=e.parse();this._options=new r(t);this._openLi...

FILE: pseudocode.js
  function makeRenderer (line 10) | function makeRenderer (data, options) {

FILE: src/ParseError.js
  function ParseError (line 1) | function ParseError (message, pos, input) {

FILE: src/Renderer.js
  function TextStyle (line 24) | function TextStyle (outerFontSize) {
  function TextEnvironment (line 135) | function TextEnvironment (nodes, textStyle) {
  function HTMLBuilder (line 284) | function HTMLBuilder () {
  function RendererOptions (line 429) | function RendererOptions (options) {
  function Renderer (line 457) | function Renderer (parser, options) {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (185K chars).
[
  {
    "path": ".eslintrc",
    "chars": 2545,
    "preview": "{\n    \"rules\": {\n        \"arrow-spacing\": \"error\",\n        \"brace-style\": [\"error\", \"stroustrup\"],\n        \"camelcase\": "
  },
  {
    "path": ".gitignore",
    "chars": 74,
    "preview": "_site\ntex/\nbuild/\nnode_modules/\nstatic/katex/\nstatic/fonts/\nnpm-debug.log\n"
  },
  {
    "path": "LICENSE",
    "chars": 1165,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2020-2023 Saswat Padhi (saswat.sourav@gmail.com)\nCopyright (c) 2015-2019 Tate Tian "
  },
  {
    "path": "Makefile",
    "chars": 2264,
    "preview": ".PHONY: all build clean docs default lint release\n\nVERSION=2.4.1\n\n# Building tools\nBROWSERIFY = $(realpath ./node_module"
  },
  {
    "path": "README.md",
    "chars": 10910,
    "preview": "# pseudocode.js\n\n<img align=\"right\" width=\"40%\" src=\"docs/screenshot.png\">\n\n**pseudocode.js** is a JavaScript library th"
  },
  {
    "path": "docs/index.html",
    "chars": 11946,
    "preview": "<!doctype html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\">\n    "
  },
  {
    "path": "docs/javascripts/scale.fix.js",
    "chars": 536,
    "preview": "var metas = document.getElementsByTagName('meta');\nvar i;\nif (navigator.userAgent.match(/iPhone/i)) {\n  for (i=0; i<meta"
  },
  {
    "path": "docs/katex-samples.html",
    "chars": 10016,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with KaTeX</title>\n\n    <!-- "
  },
  {
    "path": "docs/mathjax-v2-samples.html",
    "chars": 10407,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "docs/mathjax-v3-samples.html",
    "chars": 10233,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "docs/mathjax-v4-samples.html",
    "chars": 10231,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "docs/params.json",
    "chars": 2098,
    "preview": "{\n    \"name\": \"pseudocode.js\",\n    \"tagline\": \"Beautiful TeX-style pseudocode for the Web\",\n    \"body\": \"### Welcome to "
  },
  {
    "path": "docs/pseudocode.css",
    "chars": 1497,
    "preview": "@import url(https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css);.ps-root{font-family:KaTeX_Main,'Times Ne"
  },
  {
    "path": "docs/pseudocode.js",
    "chars": 25084,
    "preview": "(function(e){if(typeof exports===\"object\"&&typeof module!==\"undefined\"){module.exports=e()}else if(typeof define===\"func"
  },
  {
    "path": "docs/stylesheets/styles.css",
    "chars": 3491,
    "preview": "body {\n  padding:50px;\n  font:14px/1.5 \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  color:#777;\n  font-weight:300;\n"
  },
  {
    "path": "package.json",
    "chars": 891,
    "preview": "{\n    \"name\": \"pseudocode\",\n    \"version\": \"2.4.1\",\n    \"author\": {\n        \"name\": \"Saswat Padhi\",\n        \"email\": \"sa"
  },
  {
    "path": "pseudocode.js",
    "chars": 2589,
    "preview": "/*\n * The entry points of pseudocode-js\n **/\n\nvar ParseError = require('./src/ParseError');\nvar Lexer = require('./src/L"
  },
  {
    "path": "src/Lexer.js",
    "chars": 5450,
    "preview": "/**\n * The Lexer class tokenizes the input sequentially, looking ahead only one\n * token.\n */\nvar utils = require('./uti"
  },
  {
    "path": "src/ParseError.js",
    "chars": 742,
    "preview": "function ParseError (message, pos, input) {\n    var error = `Error: ${message}`;\n    // If we have the input and a posit"
  },
  {
    "path": "src/Parser.js",
    "chars": 17003,
    "preview": "/**\n * The Parser class parses the token stream from Lexer into an abstract syntax\n * tree, represented by ParseNode.\n *"
  },
  {
    "path": "src/Renderer.js",
    "chars": 31082,
    "preview": "/*\n* */\nvar utils = require('./utils');\n\n/*\n * TextStyle - used by TextEnvironment class to handle LaTeX text-style\n * c"
  },
  {
    "path": "src/utils.js",
    "chars": 437,
    "preview": "module.exports = {\n    isString: (str) => (typeof str === 'string') || (str instanceof String),\n\n    isObject: (obj) => "
  },
  {
    "path": "static/body.html.part",
    "chars": 8372,
    "preview": "</head>\n\n<body>\n    <pre id=\"test-basics\" data-line-number=true>\n        \\begin{algorithm}\n        \\caption{Test text-st"
  },
  {
    "path": "static/footer.html.part",
    "chars": 1069,
    "preview": "    <script>\n        pseudocode.renderElement(document.getElementById(\"test-basics\"),\n                                 {"
  },
  {
    "path": "static/katex.html.part",
    "chars": 575,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with KaTeX</title>\n\n    <!-- "
  },
  {
    "path": "static/mathjax-v2.html.part",
    "chars": 966,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "static/mathjax-v3.html.part",
    "chars": 792,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "static/mathjax-v4.html.part",
    "chars": 790,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>pseudocode.js Samples with MathJax</title>\n\n    <!-"
  },
  {
    "path": "static/pseudocode.css",
    "chars": 1953,
    "preview": "@import url('https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css');\n\n.ps-root {\n    font-family: KaTeX_Mai"
  }
]

About this extraction

This page contains the full source code of the SaswatPadhi/pseudocode.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (171.1 KB), approximately 47.6k tokens, and a symbol index with 16 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!