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 **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 · For KaTeX users Include the following in the `` of your page: ```html ``` #### Step 1B · For MathJax 2.x users Include the following in the `` of your page: ```html ``` > **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 · For MathJax 3.x users Include the following in the `` of your page: ```html ``` > **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 · Grab pseudocode.js Include the following in the `` of your page: ```html ``` You may also use the `latest` tag for pseudocode instead, but jsDelivr might be delayed in updating the pointer for this tag. ```html ``` #### Step 3 · Write your pseudocode inside a `
`
We assume the pseudocode to be rendered is in a `
` DOM element.
Here is an example that illustrates a quicksort algorithm:

```html
    % 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}
``` #### Step 4A · Render the element using pseudocode.js Insert the following Javascript snippet at the end of your document: ```html ``` #### Step 4B · Render all elements of the class using pseudocode.js Insert the following Javascript snippet at the end of your document: ```html ``` ### 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 # A postcondition is optional \ENSURE # An input is optional \INPUT # An output is optional \OUTPUT # The body of your code is a \STATE ... \end{algorithmic} ``` `` can include zero or more ``, ``, `` and ``: ```tex # A can be: \STATE \RETURN \PRINT # A can be: # A conditional \IF{} \ELIF{} \ELSE \ENDIF # Or a loop: \WHILE, \FOR or \FORALL \WHILE{} \ENDWHILE # Or a repeat: \REPEAT \UNTIL{} \REPEAT \UNTIL{} # A can by defined by either \FUNCTION or \PROCEDURE # Both are exactly the same \FUNCTION{}{} \ENDFUNCTION # A is: \COMMENT{} ``` A ``, ``, or `` may include the following: ```tex % Normal characters Hello world % Escaped characters \\, \{, \}, \$, \&, \#, \% and \_ % Math formula $i \gets i + 1$ % Function call \CALL{}{} % 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{}, \textrm{}, \textsf{}, \texttt{} \textup{}, \textit{}, \textsl{}, \textsc{} \uppercase{}, \lowercase{} \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 `
` DOM element.

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

```html
   ...
``` ## 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 ================================================ pseudocode.js

pseudocode.js

Beautiful pseudocode for the Web

View the Project on GitHub SaswatPadhi/pseudocode.js

pseudocode.js enables JavaScript to typeset algorithms as beautifully as LaTeX does:

The demo above is editable. Feel free to experiment with it by clicking on the edit button.

Features

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, Opera, and Edge.

Usage

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 KaTeX or MathJax is properly setup in your document.

Download pseudocode.js, and host the files on your server. And then include the js and css files in your HTML files:

<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>

Place the pseudocode to be rendered in a <pre> element:

<pre id="hello-world-code">
    \begin{algorithmic}
    \PRINT \texttt{'hello world'}
    \end{algorithmic}
</pre>

Finally, call pseudocode.render by placing the following Javascript snippet at the end of your document body:

<script>
    pseudocode.renderElement(document.getElementById("hello-world-code"));
</script>

For more details on available options and backends, please see the usage section of README.

Author

pseudocode.js was originally written by Tate Tian (@tatetian). Together with @ZJUGuoShuai, I (@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 and relies on it to render math formulas. Thanks Emily Eisenberg(@xymostech) and other contributers for building such a wonderful project.

================================================ FILE: docs/javascripts/scale.fix.js ================================================ var metas = document.getElementsByTagName('meta'); var i; if (navigator.userAgent.match(/iPhone/i)) { for (i=0; i pseudocode.js Samples with KaTeX
        \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}
    
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{<cond>}
                \STATE <block>
            \ELIF{<cond>}
                \STATE <block>
            \ELSE
                \STATE <block>
            \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}
    
        % 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}
    
        % 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}
    
        \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}
    
        \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}
    
================================================ FILE: docs/mathjax-v2-samples.html ================================================ pseudocode.js Samples with MathJax
        \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}
    
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{<cond>}
                \STATE <block>
            \ELIF{<cond>}
                \STATE <block>
            \ELSE
                \STATE <block>
            \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}
    
        % 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}
    
        % 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}
    
        \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}
    
        \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}
    
================================================ FILE: docs/mathjax-v3-samples.html ================================================ pseudocode.js Samples with MathJax
        \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}
    
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{<cond>}
                \STATE <block>
            \ELIF{<cond>}
                \STATE <block>
            \ELSE
                \STATE <block>
            \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}
    
        % 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}
    
        % 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}
    
        \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}
    
        \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}
    
================================================ FILE: docs/mathjax-v4-samples.html ================================================ pseudocode.js Samples with MathJax
        \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}
    
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{<cond>}
                \STATE <block>
            \ELIF{<cond>}
                \STATE <block>
            \ELSE
                \STATE <block>
            \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}
    
        % 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}
    
        % 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}
    
        \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}
    
        \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}
    
================================================ 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 `` 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;e3)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;i0&&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`;if(this.value)i+=` (${s.toString(this.value)})`;i+="\n";if(this.children){for(var r=0;r0){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("
");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(``);return this};var i={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};_.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;n0&&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;ttypeof 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: * * :== ( | )[0..n] * * :== \begin{algorithm} * ( | )[0..n] * \end{algorithm} * :== \caption{ } * * :== \begin{algorithmic} * ( | | )[0..n] * \end{algorithmic} * :== \REQUIRE * :== \ENSURE * * :== ( | | | | * )[0..n] * * :== | | | | * :== \IF{} * ( \ELIF{} )[0..n] * ( \ELSE )[0..1] * \ENDIF * * :== \FOR{} \ENDFOR * :== \WHILE{} \ENDWHILE * :== \REPEAT \UNTIL{} * :== \UPON{} \EDNUPON * * :== \FUNCTION{}{} \ENDFUNCTION * (same for ) * * :== | | * :== \STATE * :== \RETURN * :== \PRINT * * :== | * :== \BREAK * :== \CONTINUE * * :== \COMMENT{} * * :== * :== ( | ) | * { } | * :== ( | ) | * { } | * * :== [1..n] | | * | | | | * :== * * :== \CALL{}({}) * :== \\ | \{ | \} | \$ | \& | \# | \% | \_ * :== \AND | \OR | \NOT | \TRUE | \FALSE | \TO | \DOWNTO * :== \textbackslash * :== ` | `` | ' | '' * (More LaTeX symbols can be added if necessary. See * http://tug.ctan.org/info/symbols/comprehensive/symbols-a4.pdf) * :== \( ... \) | $ ... $ * (Math is handled by a backend, KaTeX or MathJax) * :== \tiny | \scriptsize | \footnotesize | \small * | \normalsize | \large | \Large | \LARGE | \huge * | \HUGE * :== \rmfamily | \sffamily | \ttfamily * | \upshape | \itshape | \slshape | \scshape * :== not any of \ { } $ & # % _ * :== * * 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'); // 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'); // { } this._lexer.expect('open'); ifNode.addChild(this._parseCond()); this._lexer.expect('close'); ifNode.addChild(this._parseBlock()); // ( \ELIF { } )[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 )[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); // { } 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); // repeatNode.addChild(this._parseBlock()); // \UNTIL this._lexer.expect('func', 'until'); // {} 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'); // { } 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 { } ({ })[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('
'); 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(``); return this; }; var entityMap = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''', "/": '/', }; 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,
,

, and , 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: // ==> // HTML:

...
this._beginBlock(); this._buildTreeForAllChildren(node); this._endBlock(); break; // ----------------- Mixture (Groups + Lines) ------------------- case 'function': // \FUNCTION{}{} \ENDFUNCTION // ==> // function () // ... // 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 { } // ==> //

// if // ... // then //

this._newLine(); this._typeKeyword('if '); ifCond = node.children[0]; this._buildTree(ifCond); this._typeKeyword(' then'); // var ifBlock = node.children[1]; this._buildCommentsFromBlock(ifBlock); this._buildTree(ifBlock); // ( \ELIF {} )[0..n] var numElif = node.value.numElif; for (var ei = 0 ; ei < numElif; ei++) { // \ELIF {} // ==> //

// elif // ... // then //

this._newLine(); this._typeKeyword('else if '); var elifCond = node.children[2 + 2 * ei]; this._buildTree(elifCond); this._typeKeyword(' then'); // var elifBlock = node.children[2 + 2 * ei + 1]; this._buildCommentsFromBlock(elifBlock); this._buildTree(elifBlock); } // ( \ELSE )[0..1] var hasElse = node.value.hasElse; if (hasElse) { // \ELSE // ==> //

// else //

this._newLine(); this._typeKeyword('else'); // 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{} or \WHILE{} // ==> //

// for // ... // do //

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'); // var block = node.children[1]; this._buildCommentsFromBlock(block); this._buildTree(block); if (!this._options.noEnd) { // \ENDFOR or \ENDWHILE // ==> //

// end for //

this._newLine(); var endLoopName = loopType === 'while' ? 'end while' : 'end for'; this._typeKeyword(endLoopName); } break; case 'repeat': // \REPEAT // ==> //

// repeat //

this._newLine(); this._typeKeyword('repeat'); // block var repeatBlock = node.children[0]; this._buildCommentsFromBlock(repeatBlock); this._buildTree(repeatBlock); // \UNTIL{} // ==> //

// until //

this._newLine(); this._typeKeyword('until '); var repeatCond = node.children[1]; this._buildTree(repeatCond); break; case 'upon': // \UPON { } // ==> //

// upon //

this._newLine(); this._typeKeyword('upon '); uponCond = node.children[0]; this._buildTree(uponCond); // 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 ================================================
        \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}
    
        \begin{algorithm}
        \caption{Test control blocks}
        \begin{algorithmic}
        \PROCEDURE{Test-If}{}
            \IF{<cond>}
                \STATE <block>
            \ELIF{<cond>}
                \STATE <block>
            \ELSE
                \STATE <block>
            \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}
    
        % 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}
    
        % 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}
    
        \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}
    
        \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}
    
================================================ FILE: static/footer.html.part ================================================ ================================================ FILE: static/katex.html.part ================================================ pseudocode.js Samples with KaTeX ================================================ FILE: static/mathjax-v2.html.part ================================================ pseudocode.js Samples with MathJax ================================================ FILE: static/mathjax-v3.html.part ================================================ pseudocode.js Samples with MathJax ================================================ FILE: static/mathjax-v4.html.part ================================================ pseudocode.js Samples with MathJax ================================================ 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; }