changeset 164:a4317957148b

Move all lexers into a subpackage pygments_lexer_pseudocode2.lexers. This is to prepare for a new subpackage with filters.
author Franz Glasner <fzglas.hg@dom66.de>
date Fri, 08 May 2026 21:19:54 +0200
parents 315cfe0a836f
children 88f872c50aae
files docs/conf.py docs/details-algpseudocode.rst docs/intro.rst pygments_lexer_pseudocode2/algpseudocode.py pygments_lexer_pseudocode2/bases.py pygments_lexer_pseudocode2/fr_pseudocode.py pygments_lexer_pseudocode2/lexers/__init__.py pygments_lexer_pseudocode2/lexers/algpseudocode.py pygments_lexer_pseudocode2/lexers/bases.py pygments_lexer_pseudocode2/lexers/fr_pseudocode.py pyproject.toml tests/_tsetup.py tox.ini
diffstat 13 files changed, 1030 insertions(+), 1015 deletions(-) [+]
line wrap: on
line diff
--- a/docs/conf.py	Fri May 08 19:49:08 2026 +0200
+++ b/docs/conf.py	Fri May 08 21:19:54 2026 +0200
@@ -12,7 +12,7 @@
 
 sys.path.insert(0, os.path.dirname(os.path.abspath('.')))
 
-from pygments_lexer_pseudocode2.algpseudocode import AlgPseudocodeLexer
+from pygments_lexer_pseudocode2.lexers.algpseudocode import AlgPseudocodeLexer
 
 
 needs_sphinx = '2.1'
--- a/docs/details-algpseudocode.rst	Fri May 08 19:49:08 2026 +0200
+++ b/docs/details-algpseudocode.rst	Fri May 08 21:19:54 2026 +0200
@@ -421,7 +421,7 @@
 .. code-block:: python
 
    from functools import partial
-   from pygments_lexer_pseudocode2.algpseudocode import AlgPseudocodeLexer
+   from pygments_lexer_pseudocode2.lexers.algpseudocode import AlgPseudocodeLexer
 
    def setup(app):
 
--- a/docs/intro.rst	Fri May 08 19:49:08 2026 +0200
+++ b/docs/intro.rst	Fri May 08 21:19:54 2026 +0200
@@ -125,4 +125,10 @@
    :language: algpseudocode-fr
    :lines: 2-
 
+And again the *Edmonds–Karp Algorithm* with german keywords:
+
+.. literalinclude:: examples/algorithm-edmonds-karp.pseudocode
+   :language: algpseudocode-de
+   :lines: 2-           
+
 More details you will find :ref:`here <details-algpseudocode>`.          
--- a/pygments_lexer_pseudocode2/algpseudocode.py	Fri May 08 19:49:08 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,691 +0,0 @@
-# -*- coding: utf-8 -*-
-# :-
-# SPDX-FileCopyrightText: © 2026 Franz Glasner
-# SPDX-License-Identifier: MIT
-# :-
-r"""A pseudocode lexer along the lines of CTAN's algpseudocode or
-algpseudocodex.
-
-"""
-
-__all__ = ["AlgPseudocodeLexer",
-           "AlgPseudocodeLexer_DE", "AlgPseudocodeLexer_FR"]
-
-
-import logging
-import re
-
-import pygments.util
-from pygments.lexer import bygroups, include, words
-from pygments.token import (Comment, Error, Generic, Keyword, Name, Operator,
-                            Punctuation, Text, Whitespace)
-
-#
-# Relative imports do not work with pygments.lexers.load_lexer_from_file()
-# in all of our supported Python releases.
-#
-from pygments_lexer_pseudocode2.bases import LexBase
-from pygments_lexer_pseudocode2.utils import REVERSED_STANDARD_TYPES
-from pygments_lexer_pseudocode2 import uniprops
-
-#
-# As in the local imports: use an explicit name because __name__ is
-# __builtins__
-#
-_logger = logging.getLogger("pygments_lexer_pseudocode2.algpseudocode")
-
-
-class AlgPseudocodeLexer(LexBase):
-
-    """A pseudocode lexer along the lines of CTAN's algpseudocode or
-    algpseudocodex.
-
-    Some ideas (e.g. strings) are borrowed from Pygment's Python lexer.
-
-    """
-
-    name = "AlgPseudocode"
-    aliases = ["algpseudocode", "algpseudo"]
-    filenames = ["*.algpseudo", "*.algpseudocode"]
-    mimetypes = []
-    flags = re.MULTILINE
-
-    LANG = "en"
-    TRANSLATIONS = {
-        "PROG": "PROGRAM",
-        "PROGRAM": "PROGRAM",
-        "ALGO": "ALGORITHM",
-        "ALGORITHM": "ALGORITHM",
-        "PROC": "PROCEDURE",
-        "PROCEDURE": "PROCEDURE",
-        "FUNC": "FUNCTION",
-        "FUNCTION": "FUNCTION",
-        "FN": "FUNCTION",
-        "CLASS": "CLASS",
-        "INPUT": "Input:",
-        "INPUTS": "Inputs:",
-        "OUTPUT": "Output:",
-        "OUTPUTS": "Outputs:",
-        "RETURNS": "Returns:",
-        "ENSURE": "Ensure:",
-        "REQUIRE": "Require:",
-        "IS": "IS",
-        "WITH": "WITH",
-        "IF": "IF",
-        "THEN": "THEN",
-        "ELSE": "ELSE",
-        "ELSEIF": "ELSE IF",
-        "ELSIF": "ELSE IF",
-        "ELIF": "ELSE IF",
-        "DO": "DO",           # in WHILE ... DO
-        "WHILE": "WHILE",
-        "FOR": "FOR",
-        "FORALL": "FOR ALL",
-        "FROM": "FROM",
-        "TO": "TO",
-        "IN": "IN",           # as in FOR ... IN
-        "STEP": "STEP",
-        "LOOP": "LOOP",
-        "REPEAT": "REPEAT",
-        "UNTIL": "UNTIL",
-        "RETURN": "RETURN",
-        "BEGIN": "BEGIN",
-        "END": "END",         # not in END_TRANSLATIONS
-    }
-    END_TRANSLATIONS = {
-        "PROG": "END OF PROGRAM",
-        "PROGRAM": "END OF PROGRAM",
-        "ALGO": "END OF ALGORITHM",
-        "ALGORITHM": "END OF ALGORITHM",
-        "PROC": "END OF PROCEDURE",
-        "PROCEDURE": "END OF PROCEDURE",
-        "FUNC": "END OF FUNCTION",
-        "FUNCTION": "END OF FUNCTION",
-        "FN": "END OF FUNCTION",
-        "CLASS": "END OF CLASS",
-        "IF": "END IF",
-        "WHILE": "END WHILE",
-        "FOR": "END FOR",
-        "FORALL": "END FOR ALL",
-        "LOOP": "END LOOP",
-    }
-    DEFAULT_END_PREFIX = "END OF "
-    SYMBOL_REMARK = u"▷"           # U+25B7: Unicode 1.0 (Geometric Shapes)
-    # SYMBOL_REMARK = u"▻"         # U+25BB: Unicode 1.0 (Geometric Shapes)
-    # SYMBOL_REMARK = u"⍝"         # U+235D: Unicode 1.1 (Misc. Technical, APL)
-    SYMBOL_BLOCK = u"◆"            # U+25C6: Unicode 1.0 (Geometric Shapes)
-    # SYMBOL_BLOCK = u"┃"          # U+2503: Unicode 1.0 (Bow Drawing)
-    # SYMBOL_BLOCK = u"●"          # U+25CF: Unicode 1.0 (Geometric Shapes)
-    SYMBOL_TEXTSTATEMENT = u"▪"    # U+25AA: Unicode 1.0 (Geometric Shapes)
-    # SYMBOL_TEXTSTATEMENT = u"■"  # U+25A0: Unicode 1.0 (Geometric Shapes)
-    SYMBOLS = {
-        # Group REMARK
-        "REMARK": SYMBOL_REMARK,
-        "REM": SYMBOL_REMARK,
-        # Group STATEMENT
-        "STATEMENT": SYMBOL_BLOCK,
-        "STATE": SYMBOL_BLOCK,
-        "BLOCK": SYMBOL_BLOCK,
-        # Group TEXTSTATEMENT
-        "TEXTSTATEMENT": SYMBOL_TEXTSTATEMENT,
-        "TEXTSTATE": SYMBOL_TEXTSTATEMENT,
-        "TSTATEMENT": SYMBOL_TEXTSTATEMENT,
-        "TSTATE": SYMBOL_TEXTSTATEMENT,
-        "TEXTBLOCK": SYMBOL_TEXTSTATEMENT,
-        "TBLOCK": SYMBOL_TEXTSTATEMENT,
-        "<-": u"⟵",          # U+27F5: Unicode 3.2 (Supplemental Arrows-A)
-        "->": u"⟶",          # U+27F6: Unicode 3.2 (Supplemental Arrows-A)
-        "<->": u"⟷",         # U+27F7: Unicode 3.2 (Supplemental Arrows-A)
-        # "=>": u"⟹",       # U+27F9: Unicode 3.2 (Supplemental Arrows-A)
-        # "<=>": u"⟺",      # U+27FA: Unicode 3.2 (Supplemental Arrows-A)
-        # "<-": u"←",        # U+2190: Unicode 1.0 (Arrows)
-        # "->": u"→",        # U+2192: Unicode 1.0 (Arrows)
-        # "<->": u"↔",       # U+2194: Unicode 1.0 (Arrows)
-        "=>": u"⇒",          # U+21D2: Unicode 1.0 (Arrows)
-        "<=>": u"⇔",         # U+21D4: Unicode 1.0 (Arrows)
-        "<=": u"≤",          # U+2264: Unicode 1.0 (Mathematical Operators)
-        ">=": u"≥",          # U+2265: Unicode 1.0 (Mathematical Operators)
-        "<>": u"≠",          # U+2260: Unicode 1.0 (Mathematical Operators)
-        "!=": u"≠",          # U+2260: Unicode 1.0 (Mathematical Operators)
-        ":=": u"∶=",    # "≔" U+2254  not recognizable in my (small) mono font
-        "=:": u"=∶",    # "≕" U+2255  not recognizable in my (small) mono font
-        "?=": u"≟",          # U+225F: Unicode 1.0 (Mathematical Operators)
-    }
-
-    def op_translate(toktype):
-
-        def _op_translate(lexer, match, ctx=None):
-            kw = match.group().upper()
-            yield match.start(), toktype, lexer.TRANSLATIONS.get(kw, kw)
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_translate
-
-    def op_opt_end_translate(toktype):
-
-        def _op_end_translate(lexer, match, ctx=None):
-            if not lexer.no_end:
-                kw = match.group().upper()
-                yield (match.start(),
-                       toktype,
-                       lexer.END_TRANSLATIONS.get(
-                           kw,
-                           lexer.DEFAULT_END_PREFIX + kw))
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_end_translate
-
-    def op_opt_ignore(toktype):
-
-        def _op_opt_ignore(lexer, match, ctx=None):
-            if not lexer.no_end:
-                yield match.start(), toktype, match.group()
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_opt_ignore
-
-    def op_opt_ignore_or_fixed(toktype, value):
-        """Yield a fixed given token type and value or -- if the lexer's
-        `no_end` setting evals to ``True`` nothing.
-
-        """
-
-        def _op_opt_ignore_or_fixed(lexer, match, ctx=None):
-            if not lexer.no_end:
-                yield match.start(), toktype, value
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_opt_ignore_or_fixed
-
-    def op_gets(lexer, match, ctx=None):
-        yield match.start(), Operator, lexer.symbol_gets
-
-    def op_remark(lexer, match, ctx=None):
-        yield match.start(), Comment.Single, lexer.symbol_remark
-
-    def op_symbol(toktype):
-
-        def _op_symbol(lexer, match, ctx=None):
-            kw = match.group().upper()
-            yield match.start(), toktype, lexer.SYMBOLS.get(kw, kw)
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_symbol
-
-    def op_explicit_tokentype(lexer, match, ctx=None):
-        needed_css = match.group("type")
-        toktype = REVERSED_STANDARD_TYPES.get(needed_css, None)
-        if toktype is None:
-            # Be more error friendly
-            toktype = Generic.Error
-            val = match.group()
-            _logger.warning("Unhandled explicit token type: %s", val)
-        else:
-            val = match.group("character")
-        yield match.start(), toktype, val
-        if ctx:
-            ctx.pos = match.end()
-
-    tokens = {
-        "root": [
-            (r"\n", Whitespace),
-            (r"/\*", Comment.Multiline, "multiline-nested-comment"),
-            (r"\(\*", Comment.Multiline, "multiline-nested-comment-alt"),
-            (r"(//|#).*$", Comment.Single),
-            include("remark"),
-            (r"(?i)\\(block|state(?:ment)?)[ \t]*(\{)",
-             bygroups(op_symbol(Text), LexBase.op_fixed(Whitespace, " ")),
-             "block-expr"),
-            (r"(?i)\\("
-             r"(?:textstate(?:ment)?)"
-             r"|(?:tstate(?:ment)?)"
-             r"|(?:textblock)"
-             r"|(?:tblock)"
-             r")[ \t]*(\{)",
-             bygroups(op_symbol(Text), LexBase.op_fixed(Whitespace, " ")),
-             "text-statement"),
-            (r"(?i)\\("
-             r"(?:input(?:s)?)"
-             r"|(?:output(?:s)?)"
-             r"|(?:ensure)"
-             r"|(?:require)"
-             r"|(?:returns)"
-             r")[ \t]*(\{)",
-             bygroups(op_translate(Keyword),
-                      LexBase.op_fixed(Whitespace, " ")),
-             "text-statement"),
-            (r"(?i)\\("
-             r"(?:if)"
-             r"|(?:then)"
-             r"|(?:else)"
-             r"|(?:el(?:s(?:e)?)?if)"
-             r"|(?:do)"               # as in WHILE ... DO not DO ... UNTIL
-             r"|(?:while)"
-             r"|(?:forall)"
-             r"|(?:for)"
-             r"|(?:from)"
-             r"|(?:to)"
-             r"|(?:step)"
-             r"|(?:in)"
-             r"|(?:loop)"
-             r"|(?:repeat)"
-             r"|(?:until)"
-             r"|(?:return)"
-             r")\b",
-             bygroups(op_translate(Keyword))),
-            (r"\\\n", Text),
-            (r"(?i)\\("
-             r"(?:prog(?:ram)?)"
-             r"|(?:algo(?:rithm)?)"
-             r"|(?:proc(?:edure)?)"
-             r"|(?:func(?:tion)?|(?:fn))"
-             r"|(?:class)"
-             r")[ \t]*(\{)",
-             bygroups(op_translate(Keyword),
-                      LexBase.op_fixed(Whitespace, " ")),
-             "entity-name"),
-            # ENDxxx keywords with optional entity name in two parts:
-            #   1. with name
-            (r"(?i)\\end(?:[_\-]|(?:[ \t]+))?("
-             r"(?:prog(?:ram)?)"
-             r"|(?:algo(?:rithm)?)"
-             r"|(?:proc(?:edure)?)"
-             r"|(?:func(?:tion)?)"
-             r"|(?:fn)"
-             r"|(?:class)"
-             r")(?:[_\-]|(?:[\t ]+))?(\{)",
-             bygroups(op_opt_end_translate(Keyword),
-                      op_opt_ignore_or_fixed(Whitespace, " ")),
-             "entity-name-end"),
-            #   2. without name
-            #   3. AND keywords that do not allow a param (e.g. endif)
-            (r"(?i)\\end(?:[_\-]|(?:[ \t]+))?("
-             r"(?:prog(?:ram)?)"
-             r"|(?:algo(?:rithm)?)"
-             r"|(?:proc(?:edure)?)"
-             r"|(?:func(?:tion)?)"
-             r"|(?:fn)"
-             r"|(?:class)"
-             r"|(?:if)"
-             r"|(?:while)"
-             r"|(?:for)"
-             r"|(?:forall)"
-             r"|(?:loop)"
-             r")\b",
-             bygroups(op_opt_end_translate(Keyword))),
-            #
-            # A single begin or end that is never suppressed because
-            # it is supposed to be paired with begin
-            #
-            (r"(?i)\\(begin|end)\b",
-             bygroups(op_translate(Keyword))),
-            # Keywords
-            (r"(?i)\\("
-             r"(?:is)"
-             r"|(?:with)"
-             r")\b",
-             bygroups(op_translate(Keyword))),
-            include("expr"),
-            include("unicode-separators"),
-            include("unicode-other"),
-            (r"[^\S\n]+", Text),
-            # (r".", Generic.Error),     # tolerance for errors
-            (r".", Error),
-        ],
-        "remark": [
-            (r"(?i)\\(remark|rem)\b(.*)$",
-             bygroups(op_remark, Comment.Single)),
-        ],
-        "entity-name": [      # may be multiline
-            (r"[^\\}]+", Name.Entity),
-            (r"\}", LexBase.op_ignore, "#pop"),
-            (r"\\\}", LexBase.op_fixed(Name.Entity, "}")),
-            (r"\\\\", LexBase.op_fixed(Name.Entity, "\\")),
-            (r"\\", LexBase.op_fixed(Generic.Error, "\\")),
-        ],
-        "entity-name-end": [  # may be multiline -- suppressed if no_end
-            (r"[^\\}]+", op_opt_ignore(Name.Entity)),
-            (r"\}", LexBase.op_ignore, "#pop"),
-            (r"\\\}", op_opt_ignore_or_fixed(Name.Entity, "}")),
-            (r"\\\\", op_opt_ignore_or_fixed(Name.Entity, "\\")),
-            (r"\\", op_opt_ignore_or_fixed(Generic.Error, "\\")),
-        ],
-        "expr": [
-            include("math-symbols"),          # must be before punctuation
-            include("ascii-punctuation"),
-            include("unicode-punctuation"),
-            include("escaped-string-start"),
-            include("py-strings"),
-            include("py-numbers"),
-            (r"(?i)\\(call|name)[ \t]*(\{)", LexBase.op_ignore, "entity-name"),
-            (r"(?i)\\gets\b", op_gets),
-            (r"(?i)\\text[ \t]*\{", LexBase.op_ignore, "text-in-expr"),
-            include("explicit-tokentype"),
-            include("remark"),
-            include("keyword-constants"),
-            include("word-operators"),
-            include("math-builtins"),
-            include("py-name"),
-        ],
-        "expr-in-braces": [
-            include("math-symbols"),          # must be before punctuation
-            include("ascii-punctuation-in-braces"),
-            include("unicode-punctuation"),
-            include("escaped-string-start"),
-            include("py-strings"),
-            include("py-numbers"),
-            (r"(?i)\\(call|name)[ \t]*(\{)", LexBase.op_ignore, "entity-name"),
-            (r"(?i)\\gets\b", op_gets),
-            (r"(?i)\\text[ \t]*\{", LexBase.op_ignore, "text-in-expr"),
-            include("explicit-tokentype"),
-            include("remark"),
-            include("keyword-constants"),
-            include("word-operators"),
-            include("math-builtins"),
-            include("py-name"),
-        ],
-        "block-expr": [      # somewhat similar to "root"
-            (r"\}", LexBase.op_ignore, "#pop"),
-            (r"\n", Whitespace),
-            include("expr-in-braces"),
-            (r"\\\\", LexBase.op_fixed(Text, "\\")),
-            (r"\\", LexBase.op_fixed(Generic.Error, "\\")),
-            include("unicode-separators"),
-            include("unicode-other"),
-            (r"[^\S\n]+", Text),
-            (r".", Error),
-        ],
-        "text-statement": [  # like block but default to text-mode
-            (r"[^\\}\n]+", Text),
-            (r"\}", LexBase.op_ignore, "#pop"),
-            (r"\n", Whitespace),
-            (r"\\\}", LexBase.op_fixed(Text, "}")),
-            (r"(?i)\\expr(?:ession)?[ \t]*\{",
-             LexBase.op_ignore,
-             "block-expr"),
-            include("explicit-tokentype"),
-            include("remark"),
-            (r"\\\\", LexBase.op_fixed(Text, "\\")),
-            (r"\\", LexBase.op_fixed(Text, "\\")),  # in text-mode: leave Text
-            (r".", Error),
-        ],
-        "text-in-expr": [
-            (r"[^\\}\n]+", Text),
-            (r"\}", LexBase.op_ignore, "#pop"),
-            (r"\n", Whitespace),
-            (r"\\\}", LexBase.op_fixed(Text, "}")),
-            (r"(?i)\\expr(?:ession)?[ \t]*\{",
-             LexBase.op_ignore,
-             "block-expr"),
-            include("explicit-tokentype"),
-            include("remark"),
-            (r"\\\\", LexBase.op_fixed(Text, "\\")),
-            (r"\\", LexBase.op_fixed(Text, "\\")),  # in text-mode: leave Text
-            (r".", Error),
-        ],
-        "math-builtins": [
-            (words(("sqrt", "pow", "cos", "sin", "tan", "arcos", "arcsin",
-                    "arctan", "arctan2", "mod", "exp", "ln", "log",
-                    "min", "max"),
-                   prefix=r"(?<!\.)",
-                   suffix=r"\b"),
-             Name.Builtin),
-        ],
-        "math-symbols": [
-            (r"<=>|<->|<-|->|=>|<=|>=|<>|!=|:=|=:|\?=", op_symbol(Operator)),
-            (r"[!&<>=+\-*/%|~]", Operator),         # ASCII
-            (u"[%s]" % (uniprops.Sm,), Operator),   # other Unicode
-        ],
-        "word-operators": [
-            (words(("IN", "In", "in",
-                    "IS", "Is", "is",
-                    "AND", "And", "and",
-                    "OR", "Or", "or",
-                    "XOR", "Xor", "xor",
-                    "NOT", "Not", "not"),
-                   prefix=r"(?<!\.)",
-                   suffix=r"\b"),
-             Operator.Word),
-        ],
-        "keyword-constants": [
-            (words(("True", "TRUE", "true", "False", "FALSE", "false",
-                    "None", "NONE", "none", "Nil", "NIL", "nil",
-                    "Null", "NULL", "null",
-                    "Empty", "EMPTY", "empty"),
-                   prefix=r"(?<!\.)",
-                   suffix=r"\b"),
-             Keyword.Constant),
-        ],
-        "ascii-punctuation": [
-            (r"\.+", Punctuation),
-            (r"[{}:(),;[\]?@]", Punctuation),
-        ],
-        "ascii-punctuation-in-braces": [
-            #
-            # Like "punctuation" but needs an escaped curly brace for } because
-            # a single closing curly brace pops the current state here.
-            #
-            (r"\\\}", LexBase.op_fixed(Punctuation, "}")),
-            (r"[{:(),;[\]?@]", Punctuation),
-        ],
-        "unicode-separators": [
-            (u"[%s]" % (uniprops.Zl,), Whitespace),
-            (u"[%s]" % (uniprops.Zp,), Whitespace),
-            (u"[%s]" % (uniprops.Zs,), Whitespace),
-        ],
-        "unicode-punctuation": [
-            (u"[%s]" % (uniprops.Pc,), Punctuation),
-            (u"[%s]" % (uniprops.Pd,), Punctuation),
-            (u"[%s]" % (uniprops.Ps,), Punctuation),
-            (u"[%s]" % (uniprops.Pe,), Punctuation),
-            (u"[%s]" % (uniprops.Pi,), Punctuation),
-            (u"[%s]" % (uniprops.Pf,), Punctuation),
-            (u"[%s]" % (uniprops.Po,), Punctuation),
-        ],
-        "unicode-other": [
-            (u"[%s]" % (uniprops.Sc,), Text),    # Currency
-            (u"[%s]" % (uniprops.So,), Text),    # Other symbols
-        ],
-        "escaped-string-start": [
-            (r"""\\(['"])""", bygroups(Punctuation)),
-        ],
-        "explicit-tokentype": [
-            # All these REs are CASE-SENSITIVE!
-
-            # Multiple characters possible, but no escaping!
-            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)(?P<sep>[/:|=*+!\$~])"
-             r"(?P<character>(.|\n)+?)(?P=sep)",
-             op_explicit_tokentype),
-            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\{(?P<character>[^}]+?)\}",
-             op_explicit_tokentype),
-            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\((?P<character>[^)]+?)\)",
-             op_explicit_tokentype),
-            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)<(?P<character>[^>]+?)>",
-             op_explicit_tokentype),
-            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\[(?P<character>[^\]]+?)\]",
-             op_explicit_tokentype),
-
-            # Every character is possible: no escaping needed!
-            (r"\\tt-(?P<type>[^/]+?)/(?P<character>(?:.|\n))",
-             op_explicit_tokentype),
-        ],
-    }
-
-    def __init__(self, **options):
-        self.no_end = pygments.util.get_bool_opt(
-            options, "no_end", default=False)
-        self.symbol_gets = options.get("gets", None)
-        if self.symbol_gets is None:
-            self.symbol_gets = self.SYMBOLS["<-"]   # Default: "⟵"  # U+27F5
-        self.symbol_remark = options.get("remark", None)
-        if self.symbol_remark is None:
-            self.symbol_remark = self.SYMBOLS["REM"]  # Default: "▷"  # U+25B7
-        LexBase.__init__(self, **options)
-        #
-        # Do this after calling the base because a "filters" option should be
-        # allowed to set **any** filter.
-        #
-        self.prohibit_raiseonerror_filter = pygments.util.get_bool_opt(
-            options, "prohibit_raiseonerror_filter", default=False)
-
-    def add_filter(self, filter_, **options):
-        #
-        # May be called by Lexer.__init__ when
-        # self.prohibit_raiseonerror_filter is not yet set.
-        # This is intentionally so.
-        #
-        if getattr(self, "prohibit_raiseonerror_filter", False):
-            if filter_ in ("raiseonerror",):
-                return
-        LexBase.add_filter(self, filter_, **options)
-
-
-class AlgPseudocodeLexer_DE(AlgPseudocodeLexer):
-
-    """
-
-    .. seealso::
-       - https://de.wikipedia.org/wiki/Pseudocode
-
-    .. todo:: Complete german translations
-
-    """
-
-    name = "AlgPseudocodeDE"
-    aliases = ["algpseudocode-de", "algpseudo-de"]
-    filenames = ["*.algpseudo-de", "*.algpseudocode-de"]
-
-    LANG = "de"
-    TRANSLATIONS = AlgPseudocodeLexer.TRANSLATIONS.copy()
-    TRANSLATIONS.update({
-        "PROG": "PROGRAMM",
-        "PROGRAM": "PROGRAMM",
-        "ALGO": "ALGORITHMUS",
-        "ALGORITHM": "ALGORITHMUS",
-        "PROC": "PROZEDUR",
-        "PROCEDURE": "PROZEDUR",
-        "FUNC": "FUNKTION",
-        "FUNCTION": "FUNKTION",
-        "FN": "FUNKTION",
-        "CLASS": "KLASSE",
-        "IS": "IST",
-        "WITH": "MIT",
-        "IF": "WENN",
-        "THEN": "DANN",
-        "ELSE": "ANDERNFALLS",
-        "ELSEIF": "ANDERNFALLS WENN",
-        "ELSIF": "ANDERNFALLS WENN",
-        "ELIF": "ANDERNFALLS WENN",
-#       "DO": # XXX TBD  # in WHILE WHILE ... DO  # noqa  # in WHILE ... DO
-        "WHILE": "SOLANGE",
-        "FOR": "FÜR",
-        "FORALL": "FÜR ALLE",
-        "FROM": "VON",
-        "TO": "BIS",
-        "IN": "IN",
-        "STEP": "SCHRITTWEITE",
-#       "LOOP": XXX TBD        # noqa
-        "REPEAT": "WIEDERHOLE",
-        "UNTIL": "BIS",
-#       "RETURN": XXX TBD      # noqa
-        "BEGIN": "START",
-        "END": "ENDE",
-    })
-    END_TRANSLATIONS = AlgPseudocodeLexer.END_TRANSLATIONS.copy()
-    END_TRANSLATIONS.update({
-        "PROG": "ENDE DES PROGRAMMS",
-        "PROGRAM": "ENDE DES PROGRAMMS",
-        "ALGO": "ENDE DES ALGORITHMUS",
-        "ALGORITHM": "ENDE DES ALGORITHMUS",
-        "PROC": "ENDE DER PROZEDUR",
-        "PROCEDURE": "ENDE DER PROZEDUR",
-        "FUNC": "ENDE DER FUNKTION",
-        "FUNCTION": "ENDE DER FUNKTION",
-        "FN": "ENDE DER FUNKTION",
-        "CLASS": "ENDE DER KLASSE",
-        "IF": "ENDE WENN",
-        "WHILE": "ENDE SOLANGE",
-        "FOR": "ENDE FÜR",
-        "FORALL": "ENDE FÜR ALLE",
-#       "LOOP": "ENDE XXX",            # XXX TBD   # noqa
-    })
-    DEFAULT_END_PREFIX = "ENDE VON "
-
-
-class AlgPseudocodeLexer_FR(AlgPseudocodeLexer):
-
-    """
-
-    .. seealso::
-      - https://info.blaisepascal.fr/pseudo-code/
-      - https://fr.wikipedia.org/wiki/Pseudo-code
-      - https://fr.wikipedia.org/wiki/Structure_de_contr%C3%B4le
-
-    .. todo:: Complete french translations
-
-    """
-
-    name = "AlgPseudocodeFR"
-    aliases = ["algpseudocode-fr", "algpseudo-fr"]
-    filenames = ["*.algpseudo-fr", "*.algpseudocode-fr"]
-
-    LANG = "fr"
-    TRANSLATIONS = AlgPseudocodeLexer.TRANSLATIONS.copy()
-    TRANSLATIONS.update({
-        "PROG": "PROGRAMME",
-        "PROGRAM": "PROGRAMME",
-        "ALGO": "ALGORITHME",
-        "ALGORITHM": "ALGORITHME",
-        "PROC": "PROCÉDURE",
-        "PROCEDURE": "PROCÉDURE",
-        "FUNC": "FONCTION",
-        "FUNCTION": "FOUNCTION",
-        "FN": "FONCTION",
-        "CLASS": "CLASSE",
-        "IS": "EST",
-        "WITH": "AVEC",
-        "IF": "SI",
-        "THEN": "ALORS",
-        "ELSE": "SINON",
-        "ELSEIF": "SINONSI",
-        "ELSIF": "SINONSI",
-        "ELIF": "SINONSI",
-        "DO": "FAIRE",             # as in in WHILE ... DO (not DO ... UNTIL)
-        "WHILE": "TANTQUE",
-        "FOR": "POUR",
-        "FORALL": "POUR CHAQUE",
-        "FROM": "DE",
-        "TO": "JUSQU'À",           # or just "À",
-        "IN": "DANS",              # as in FOR ... IN
-        "STEP": "PAR PAS DE",
-        "LOOP": "BOUCLE",          # XXX FIXME???
-        "REPEAT": "RÉPÉTER",
-        "UNTIL": "JUSQUACEQUE",
-        "RETURN": "RENVOYER",
-        "BEGIN": "DÉBUT",
-        "END": "FIN",
-    })
-    END_TRANSLATIONS = AlgPseudocodeLexer.END_TRANSLATIONS.copy()
-    END_TRANSLATIONS.update({
-        "PROG": "FIN DE PROGRAMME",
-        "PROGRAM": "FIN DE PROGRAMME",
-        "ALGO": "FIN D'ALGORITHME",
-        "ALGORITHM": "FIN D'ALGORITHME",
-        "PROC": "FIN DE PROCÉDURE",
-        "PROCEDURE": "FIN DE PROCÉDURE",
-        "FUNC": "FIN DE FONCTION",
-        "FUNCTION": "FIN DE FOUNCTION",
-        "FN": "FIN DE FONCTION",
-        "CLASS": "FIN DE CLASSE",
-        "SI": "FIN SI",
-        "FOR": "FIN POUR",
-        "FORALL": "FIN POUR CHAQUE",
-        "WHILE": "FIN TANTQUE",
-        "LOOP": "FIN BOUCLE",
-    })
-    DEFAULT_END_PREFIX = "FIN DE "
--- a/pygments_lexer_pseudocode2/bases.py	Fri May 08 19:49:08 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,188 +0,0 @@
-# -*- coding: utf-8 -*-
-# :-
-# SPDX-FileCopyrightText: © 2026 Franz Glasner
-# SPDX-License-Identifier: MIT
-# :-
-r"""Some common bases for the lexers."""
-
-__all__ = ["LexBase", "uni_name", "py_innerstring_rules", "py_name_rules"]
-
-
-import sys
-
-from pygments import unistring
-from pygments.lexer import RegexLexer, combined, bygroups, include
-from pygments.token import (Comment, Error, Name, Number, Other, String)
-
-
-PY2 = sys.version_info[0] <= 2
-
-
-#
-# SPDX-SnippetBegin
-# SPDX-License-Identifier: BSD-2-Clause
-# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
-# SPDX-SnippetCopyrightText: Copyright 2026 by Franz Glasner
-#
-
-uni_name = "[%s][%s]*" % (unistring.xid_start, unistring.xid_continue)
-
-
-"""PY3 allows no @staticmethod but PY2 needs it."""
-if PY2:
-    _staticmethod = staticmethod
-else:
-    def _staticmethod(fn):
-        return fn
-
-
-def py_innerstring_rules(ttype):
-    return [
-        # the old style '%s' % (...) string formatting (still valid in Py3)
-        (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
-         '[hlL]?[E-GXc-giorsaux%]', String.Interpol),
-        # the new style '{}'.format(...) string formatting
-        (r'\{'
-         r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?'  # field name
-         r'(\![sra])?'                       # conversion
-         r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?'
-         r'\}', String.Interpol),
-        #
-        # backslashes, quotes and formatting signs must be parsed
-        # one at a time
-        #
-        (r'[^\\\'"%{\n]+', ttype),
-        (r'[\'"\\]', ttype),
-        # unhandled string formatting sign
-        (r'%|(\{{1,2})', ttype)
-        # newlines are an error (use "nl" state)
-    ]
-
-
-def py_name_rules(ttype, deco_ttype=Name.Decorator):
-    return [
-        # We recognize decorator syntax here
-        (r'@' + uni_name, deco_ttype),
-        #
-        # Python's new matrix multiplication operator:
-        # not used here in pseudocode
-        # (r'@', Operator),
-        (uni_name, ttype),
-    ]
-
-# SPDX-SnippetEnd
-
-
-class LexBase(RegexLexer):
-
-    """A base that defines some common lexer states.
-
-    Default flags are not important.
-
-    """
-
-    def op_ignore(lexer, match, ctx=None):
-        """Unconditionally ignore the match."""
-        if False:
-            yield match.start(), Other, ""
-        if ctx:
-            ctx.pos = match.end()
-
-    @_staticmethod
-    def op_fixed(toktype, value):
-        """Unconditionally yield a given token type and value."""
-
-        def _op_fixed(lexer, match, ctx=None):
-            yield match.start(), toktype, value
-            if ctx:
-                ctx.pos = match.end()
-
-        return _op_fixed
-
-    tokens = {
-#
-# These states are borrowed from Pygment's Python lexer.
-# Their names have been prefixed with `py-'.
-#
-# SPDX-SnippetBegin
-# SPDX-License-Identifier: BSD-2-Clause
-# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
-# SPDX-SnippetCopyrightText: Copyright 2026 by Franz Glasner
-#
-        'py-numbers': [
-            (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
-             r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
-            (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
-            (r'0[oO](?:_?[0-7])+', Number.Oct),
-            (r'0[bB](?:_?[01])+', Number.Bin),
-            (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
-            (r'\d(?:_?\d)*', Number.Integer),
-        ],
-        'py-strings': [
-            # non-raw strings
-            ('([uU]?)(""")', bygroups(String.Affix, String.Double),
-             combined('py-stringescape', 'py-tdqs')),
-            ("([uU]?)(''')", bygroups(String.Affix, String.Single),
-             combined('py-stringescape', 'py-tsqs')),
-            ('([uU]?)(")', bygroups(String.Affix, String.Double),
-             combined('py-stringescape', 'py-dqs')),
-            ("([uU]?)(')", bygroups(String.Affix, String.Single),
-             combined('py-stringescape', 'py-sqs')),
-            # non-raw bytes
-            ('([bB])(""")', bygroups(String.Affix, String.Double),
-             combined('py-bytesescape', 'py-tdqs')),
-            ("([bB])(''')", bygroups(String.Affix, String.Single),
-             combined('py-bytesescape', 'py-tsqs')),
-            ('([bB])(")', bygroups(String.Affix, String.Double),
-             combined('py-bytesescape', 'py-dqs')),
-            ("([bB])(')", bygroups(String.Affix, String.Single),
-             combined('py-bytesescape', 'py-sqs')),
-        ],
-        'py-stringescape': [
-            (r'\\(N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8})', String.Escape),
-            include('py-bytesescape')
-        ],
-        'py-bytesescape': [
-            (r'\\([\\abfnrtv"\']|\n|x[a-fA-F0-9]{2}|[0-7]{1,3})',
-             String.Escape)
-        ],
-        'py-dqs': [
-            (r'"', String.Double, '#pop'),
-            (r'\\\\|\\"|\\\n', String.Escape),  # included here for raw strings
-            include('py-strings-double'),
-            (r'\n', Error),    # added by fag
-        ],
-        'py-sqs': [
-            (r"'", String.Single, '#pop'),
-            (r"\\\\|\\'|\\\n", String.Escape),  # included here for raw strings
-            include('py-strings-single'),
-            (r'\n', Error),    # added by fag
-        ],
-        'py-tdqs': [
-            (r'"""', String.Double, '#pop'),
-            include('py-strings-double'),
-            (r'\n', String.Double)
-        ],
-        'py-tsqs': [
-            (r"'''", String.Single, '#pop'),
-            include('py-strings-single'),
-            (r'\n', String.Single)
-        ],
-        'py-strings-single': py_innerstring_rules(String.Single),
-        'py-strings-double': py_innerstring_rules(String.Double),
-        'py-name': py_name_rules(Name.Entity),
-# SPDX-SnippetEnd
-    # This snippet is from the Pygments' documentation "Write your own lexer"
-    'multiline-nested-comment': [
-            (r'[^*/]+', Comment.Multiline),
-            (r'/\*', Comment.Multiline, '#push'),
-            (r'\*/', Comment.Multiline, '#pop'),
-            (r'[*/]', Comment.Multiline),
-        ],
-    'multiline-nested-comment-alt': [
-            (r'[^*()]+', Comment.Multiline),
-            (r'\(\*', Comment.Multiline, '#push'),
-            (r'\*\)', Comment.Multiline, '#pop'),
-            (r'[*()]', Comment.Multiline),
-        ]
-    }
--- a/pygments_lexer_pseudocode2/fr_pseudocode.py	Fri May 08 19:49:08 2026 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-# :-
-# SPDX-FileCopyrightText: © 2015 Simon Wachter
-# SPDX-FileCopyrightText: © 2026 Franz Glasner
-# SPDX-License-Identifier: MIT
-# :-
-r"""
-:Author:    Simon Wachter
-:Author:    Franz Glasner
-:Copyright: © 2015 Simon Wachter
-:Copyright: © 2026 Franz Glasner
-:License:   MIT License.
-            See :file:`MIT.txt` for details.
-            If you cannot find MIT.txt see
-            <http://opensource.org/licenses/MIT>.
-"""
-
-__all__ = ["FrPseudocodeLexer"]
-
-
-import re
-
-from pygments.lexer import RegexLexer, include
-from pygments.token import Punctuation, Whitespace, Comment, Operator, Keyword, Name, String, Number
-
-
-class FrPseudocodeLexer(RegexLexer):
-    '''
-    A Pseudo code (fr) lexer
-    '''
-    name = 'FrPseudocode'
-    aliases = ['fr-pseudocode', 'fr-pseudo', 'fr-algorithm', 'fr-algo']
-    filenames = ['*.fr-algo', '*.fr-pseudocode']
-    mimetypes = []
-    flags = re.MULTILINE
-
-    REPLACEMENTS = {
-        '<=': '≤',
-        '>=': '≥',
-        '<>': '≠',
-        '!=': '≠',
-        '<-': '←',
-        '->': '→',
-        '=>': '⇒',
-        '<->': '↔',
-        '<=>': '⇔',
-        '^': '↑',
-    }
-
-    def op_replace(lexer, match):
-        op = match.group(0)
-        yield match.start(), Operator, lexer.REPLACEMENTS.get(op, op)
-
-    def scomment(lexer, match):
-        s = match.group(1).lower().strip()
-        c = Comment
-
-        directives = ['passage par copie', 'passage par valeur', 'passage par référence', 'passage par reference', 'passage par adresse', 've', 'vs', 've/s']
-
-        if s in directives:
-            c = Comment.Special
-
-        yield match.start(), c, match.group(0)
-
-    tokens = {
-        'root': [
-                 (r'\/\*.*\*\/', Comment),
-                 (r'(\/\/|#).*\n', Comment),
-                 (r'\|', Comment),
-                 (r'\{(.*)\}', scomment),
-                 include('strings'),
-                 include('core'),
-                 (r'(?i)[a-zéàùçèÉÀÙÇÈ][a-z0-9éàùçèÉÀÙÇÈ_]*', Name.Variable),
-                 include('numbers'),
-                 (r'[\s]+', Whitespace)
-        ],
-        'core': [ # Statements
-                 (r'(?i)\b(debut|début|fin|si|alors|sinon|fin[_ ]si|tant[ _]que|tantque|fin[ _]tantque|faire|répéter|'
-                  r'repeter|type|structure|fin[ _]structure|fonction|procédure|procedure|retourner|renvoyer|'
-                  r'pour|fin[ _]pour|à|déclarations?|juqsque|spécialise|specialise|comporte|super|public|privé|protégé|'
-                  r'classe'
-                  r')\s*\b', Keyword),
-
-                 # Data Types
-                 (r'(?i)\b(entiers?|chaines?|chaînes?|réels?|reels?|caractères?|caracteres?|booléens?|'
-                  r'booleens?|tableaux?|rien)\s*\b',
-                  Keyword.Type),
-
-                  (r'(?i)\b(vrai|faux|nil)\s*\b',
-                   Name.Constant),
-
-                 # Operators
-                 (r'(?i)(<->|<=>|<=|>=|<>|!=|<-|->|=>|\^|\*|\+|-|\/|<|>|=|\\\\|mod|←|↑|≤|≥|≠|÷|×|\.\.|\[|\]|\.|non|xou|et|ou)',
-                  op_replace),
-
-                 (r'(\(|\)|\,|\;|:)',
-                  Punctuation),
-
-                 #(r'\b(\[(VE|VS|VE/S)\])\s*\b',
-                 # Keyword.Declaration),
-
-                  # Intrinsics
-                 (r'(?i)\b(sqrt|pow|cos|sin|tan|arccos|arcsin|arctan|arctan2|lire|ecrire|écrire|'
-                  r'exp|ln|log|détruire|detruire'
-                  r')\s*\b', Name.Builtin)
-                ],
-
-        'strings': [
-                 (r'"([^"])*"', String.Double),
-                 (r"'([^'])*'", String.Single),
-                ],
-#
-# This is stolen from the Pygment's Python lexer.
-#
-# SPDX-SnippetBegin
-# SPDX-License-Identifier: BSD-2-Clause
-# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
-        'numbers': [
-            (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
-             r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
-            (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
-            (r'0[oO](?:_?[0-7])+', Number.Oct),
-            (r'0[bB](?:_?[01])+', Number.Bin),
-            (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
-            (r'\d(?:_?\d)*', Number.Integer),
-        ],
-# SPDX-SnippetEnd
-        }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pygments_lexer_pseudocode2/lexers/__init__.py	Fri May 08 21:19:54 2026 +0200
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+# :-
+# SPDX-FileCopyrightText: © 2026 Franz Glasner
+# SPDX-License-Identifier: MIT
+# :-
+r"Sub-package for all lexers."""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pygments_lexer_pseudocode2/lexers/algpseudocode.py	Fri May 08 21:19:54 2026 +0200
@@ -0,0 +1,691 @@
+# -*- coding: utf-8 -*-
+# :-
+# SPDX-FileCopyrightText: © 2026 Franz Glasner
+# SPDX-License-Identifier: MIT
+# :-
+r"""A pseudocode lexer along the lines of CTAN's algpseudocode or
+algpseudocodex.
+
+"""
+
+__all__ = ["AlgPseudocodeLexer",
+           "AlgPseudocodeLexer_DE", "AlgPseudocodeLexer_FR"]
+
+
+import logging
+import re
+
+import pygments.util
+from pygments.lexer import bygroups, include, words
+from pygments.token import (Comment, Error, Generic, Keyword, Name, Operator,
+                            Punctuation, Text, Whitespace)
+
+#
+# Relative imports do not work with pygments.lexers.load_lexer_from_file()
+# in all of our supported Python releases.
+#
+from pygments_lexer_pseudocode2.lexers.bases import LexBase
+from pygments_lexer_pseudocode2.utils import REVERSED_STANDARD_TYPES
+from pygments_lexer_pseudocode2 import uniprops
+
+#
+# As in the local imports: use an explicit name because __name__ is
+# __builtins__
+#
+_logger = logging.getLogger("pygments_lexer_pseudocode2.algpseudocode")
+
+
+class AlgPseudocodeLexer(LexBase):
+
+    """A pseudocode lexer along the lines of CTAN's algpseudocode or
+    algpseudocodex.
+
+    Some ideas (e.g. strings) are borrowed from Pygment's Python lexer.
+
+    """
+
+    name = "AlgPseudocode"
+    aliases = ["algpseudocode", "algpseudo"]
+    filenames = ["*.algpseudo", "*.algpseudocode"]
+    mimetypes = []
+    flags = re.MULTILINE
+
+    LANG = "en"
+    TRANSLATIONS = {
+        "PROG": "PROGRAM",
+        "PROGRAM": "PROGRAM",
+        "ALGO": "ALGORITHM",
+        "ALGORITHM": "ALGORITHM",
+        "PROC": "PROCEDURE",
+        "PROCEDURE": "PROCEDURE",
+        "FUNC": "FUNCTION",
+        "FUNCTION": "FUNCTION",
+        "FN": "FUNCTION",
+        "CLASS": "CLASS",
+        "INPUT": "Input:",
+        "INPUTS": "Inputs:",
+        "OUTPUT": "Output:",
+        "OUTPUTS": "Outputs:",
+        "RETURNS": "Returns:",
+        "ENSURE": "Ensure:",
+        "REQUIRE": "Require:",
+        "IS": "IS",
+        "WITH": "WITH",
+        "IF": "IF",
+        "THEN": "THEN",
+        "ELSE": "ELSE",
+        "ELSEIF": "ELSE IF",
+        "ELSIF": "ELSE IF",
+        "ELIF": "ELSE IF",
+        "DO": "DO",           # in WHILE ... DO
+        "WHILE": "WHILE",
+        "FOR": "FOR",
+        "FORALL": "FOR ALL",
+        "FROM": "FROM",
+        "TO": "TO",
+        "IN": "IN",           # as in FOR ... IN
+        "STEP": "STEP",
+        "LOOP": "LOOP",
+        "REPEAT": "REPEAT",
+        "UNTIL": "UNTIL",
+        "RETURN": "RETURN",
+        "BEGIN": "BEGIN",
+        "END": "END",         # not in END_TRANSLATIONS
+    }
+    END_TRANSLATIONS = {
+        "PROG": "END OF PROGRAM",
+        "PROGRAM": "END OF PROGRAM",
+        "ALGO": "END OF ALGORITHM",
+        "ALGORITHM": "END OF ALGORITHM",
+        "PROC": "END OF PROCEDURE",
+        "PROCEDURE": "END OF PROCEDURE",
+        "FUNC": "END OF FUNCTION",
+        "FUNCTION": "END OF FUNCTION",
+        "FN": "END OF FUNCTION",
+        "CLASS": "END OF CLASS",
+        "IF": "END IF",
+        "WHILE": "END WHILE",
+        "FOR": "END FOR",
+        "FORALL": "END FOR ALL",
+        "LOOP": "END LOOP",
+    }
+    DEFAULT_END_PREFIX = "END OF "
+    SYMBOL_REMARK = u"▷"           # U+25B7: Unicode 1.0 (Geometric Shapes)
+    # SYMBOL_REMARK = u"▻"         # U+25BB: Unicode 1.0 (Geometric Shapes)
+    # SYMBOL_REMARK = u"⍝"         # U+235D: Unicode 1.1 (Misc. Technical, APL)
+    SYMBOL_BLOCK = u"◆"            # U+25C6: Unicode 1.0 (Geometric Shapes)
+    # SYMBOL_BLOCK = u"┃"          # U+2503: Unicode 1.0 (Bow Drawing)
+    # SYMBOL_BLOCK = u"●"          # U+25CF: Unicode 1.0 (Geometric Shapes)
+    SYMBOL_TEXTSTATEMENT = u"▪"    # U+25AA: Unicode 1.0 (Geometric Shapes)
+    # SYMBOL_TEXTSTATEMENT = u"■"  # U+25A0: Unicode 1.0 (Geometric Shapes)
+    SYMBOLS = {
+        # Group REMARK
+        "REMARK": SYMBOL_REMARK,
+        "REM": SYMBOL_REMARK,
+        # Group STATEMENT
+        "STATEMENT": SYMBOL_BLOCK,
+        "STATE": SYMBOL_BLOCK,
+        "BLOCK": SYMBOL_BLOCK,
+        # Group TEXTSTATEMENT
+        "TEXTSTATEMENT": SYMBOL_TEXTSTATEMENT,
+        "TEXTSTATE": SYMBOL_TEXTSTATEMENT,
+        "TSTATEMENT": SYMBOL_TEXTSTATEMENT,
+        "TSTATE": SYMBOL_TEXTSTATEMENT,
+        "TEXTBLOCK": SYMBOL_TEXTSTATEMENT,
+        "TBLOCK": SYMBOL_TEXTSTATEMENT,
+        "<-": u"⟵",          # U+27F5: Unicode 3.2 (Supplemental Arrows-A)
+        "->": u"⟶",          # U+27F6: Unicode 3.2 (Supplemental Arrows-A)
+        "<->": u"⟷",         # U+27F7: Unicode 3.2 (Supplemental Arrows-A)
+        # "=>": u"⟹",       # U+27F9: Unicode 3.2 (Supplemental Arrows-A)
+        # "<=>": u"⟺",      # U+27FA: Unicode 3.2 (Supplemental Arrows-A)
+        # "<-": u"←",        # U+2190: Unicode 1.0 (Arrows)
+        # "->": u"→",        # U+2192: Unicode 1.0 (Arrows)
+        # "<->": u"↔",       # U+2194: Unicode 1.0 (Arrows)
+        "=>": u"⇒",          # U+21D2: Unicode 1.0 (Arrows)
+        "<=>": u"⇔",         # U+21D4: Unicode 1.0 (Arrows)
+        "<=": u"≤",          # U+2264: Unicode 1.0 (Mathematical Operators)
+        ">=": u"≥",          # U+2265: Unicode 1.0 (Mathematical Operators)
+        "<>": u"≠",          # U+2260: Unicode 1.0 (Mathematical Operators)
+        "!=": u"≠",          # U+2260: Unicode 1.0 (Mathematical Operators)
+        ":=": u"∶=",    # "≔" U+2254  not recognizable in my (small) mono font
+        "=:": u"=∶",    # "≕" U+2255  not recognizable in my (small) mono font
+        "?=": u"≟",          # U+225F: Unicode 1.0 (Mathematical Operators)
+    }
+
+    def op_translate(toktype):
+
+        def _op_translate(lexer, match, ctx=None):
+            kw = match.group().upper()
+            yield match.start(), toktype, lexer.TRANSLATIONS.get(kw, kw)
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_translate
+
+    def op_opt_end_translate(toktype):
+
+        def _op_end_translate(lexer, match, ctx=None):
+            if not lexer.no_end:
+                kw = match.group().upper()
+                yield (match.start(),
+                       toktype,
+                       lexer.END_TRANSLATIONS.get(
+                           kw,
+                           lexer.DEFAULT_END_PREFIX + kw))
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_end_translate
+
+    def op_opt_ignore(toktype):
+
+        def _op_opt_ignore(lexer, match, ctx=None):
+            if not lexer.no_end:
+                yield match.start(), toktype, match.group()
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_opt_ignore
+
+    def op_opt_ignore_or_fixed(toktype, value):
+        """Yield a fixed given token type and value or -- if the lexer's
+        `no_end` setting evals to ``True`` nothing.
+
+        """
+
+        def _op_opt_ignore_or_fixed(lexer, match, ctx=None):
+            if not lexer.no_end:
+                yield match.start(), toktype, value
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_opt_ignore_or_fixed
+
+    def op_gets(lexer, match, ctx=None):
+        yield match.start(), Operator, lexer.symbol_gets
+
+    def op_remark(lexer, match, ctx=None):
+        yield match.start(), Comment.Single, lexer.symbol_remark
+
+    def op_symbol(toktype):
+
+        def _op_symbol(lexer, match, ctx=None):
+            kw = match.group().upper()
+            yield match.start(), toktype, lexer.SYMBOLS.get(kw, kw)
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_symbol
+
+    def op_explicit_tokentype(lexer, match, ctx=None):
+        needed_css = match.group("type")
+        toktype = REVERSED_STANDARD_TYPES.get(needed_css, None)
+        if toktype is None:
+            # Be more error friendly
+            toktype = Generic.Error
+            val = match.group()
+            _logger.warning("Unhandled explicit token type: %s", val)
+        else:
+            val = match.group("character")
+        yield match.start(), toktype, val
+        if ctx:
+            ctx.pos = match.end()
+
+    tokens = {
+        "root": [
+            (r"\n", Whitespace),
+            (r"/\*", Comment.Multiline, "multiline-nested-comment"),
+            (r"\(\*", Comment.Multiline, "multiline-nested-comment-alt"),
+            (r"(//|#).*$", Comment.Single),
+            include("remark"),
+            (r"(?i)\\(block|state(?:ment)?)[ \t]*(\{)",
+             bygroups(op_symbol(Text), LexBase.op_fixed(Whitespace, " ")),
+             "block-expr"),
+            (r"(?i)\\("
+             r"(?:textstate(?:ment)?)"
+             r"|(?:tstate(?:ment)?)"
+             r"|(?:textblock)"
+             r"|(?:tblock)"
+             r")[ \t]*(\{)",
+             bygroups(op_symbol(Text), LexBase.op_fixed(Whitespace, " ")),
+             "text-statement"),
+            (r"(?i)\\("
+             r"(?:input(?:s)?)"
+             r"|(?:output(?:s)?)"
+             r"|(?:ensure)"
+             r"|(?:require)"
+             r"|(?:returns)"
+             r")[ \t]*(\{)",
+             bygroups(op_translate(Keyword),
+                      LexBase.op_fixed(Whitespace, " ")),
+             "text-statement"),
+            (r"(?i)\\("
+             r"(?:if)"
+             r"|(?:then)"
+             r"|(?:else)"
+             r"|(?:el(?:s(?:e)?)?if)"
+             r"|(?:do)"               # as in WHILE ... DO not DO ... UNTIL
+             r"|(?:while)"
+             r"|(?:forall)"
+             r"|(?:for)"
+             r"|(?:from)"
+             r"|(?:to)"
+             r"|(?:step)"
+             r"|(?:in)"
+             r"|(?:loop)"
+             r"|(?:repeat)"
+             r"|(?:until)"
+             r"|(?:return)"
+             r")\b",
+             bygroups(op_translate(Keyword))),
+            (r"\\\n", Text),
+            (r"(?i)\\("
+             r"(?:prog(?:ram)?)"
+             r"|(?:algo(?:rithm)?)"
+             r"|(?:proc(?:edure)?)"
+             r"|(?:func(?:tion)?|(?:fn))"
+             r"|(?:class)"
+             r")[ \t]*(\{)",
+             bygroups(op_translate(Keyword),
+                      LexBase.op_fixed(Whitespace, " ")),
+             "entity-name"),
+            # ENDxxx keywords with optional entity name in two parts:
+            #   1. with name
+            (r"(?i)\\end(?:[_\-]|(?:[ \t]+))?("
+             r"(?:prog(?:ram)?)"
+             r"|(?:algo(?:rithm)?)"
+             r"|(?:proc(?:edure)?)"
+             r"|(?:func(?:tion)?)"
+             r"|(?:fn)"
+             r"|(?:class)"
+             r")(?:[_\-]|(?:[\t ]+))?(\{)",
+             bygroups(op_opt_end_translate(Keyword),
+                      op_opt_ignore_or_fixed(Whitespace, " ")),
+             "entity-name-end"),
+            #   2. without name
+            #   3. AND keywords that do not allow a param (e.g. endif)
+            (r"(?i)\\end(?:[_\-]|(?:[ \t]+))?("
+             r"(?:prog(?:ram)?)"
+             r"|(?:algo(?:rithm)?)"
+             r"|(?:proc(?:edure)?)"
+             r"|(?:func(?:tion)?)"
+             r"|(?:fn)"
+             r"|(?:class)"
+             r"|(?:if)"
+             r"|(?:while)"
+             r"|(?:for)"
+             r"|(?:forall)"
+             r"|(?:loop)"
+             r")\b",
+             bygroups(op_opt_end_translate(Keyword))),
+            #
+            # A single begin or end that is never suppressed because
+            # it is supposed to be paired with begin
+            #
+            (r"(?i)\\(begin|end)\b",
+             bygroups(op_translate(Keyword))),
+            # Keywords
+            (r"(?i)\\("
+             r"(?:is)"
+             r"|(?:with)"
+             r")\b",
+             bygroups(op_translate(Keyword))),
+            include("expr"),
+            include("unicode-separators"),
+            include("unicode-other"),
+            (r"[^\S\n]+", Text),
+            # (r".", Generic.Error),     # tolerance for errors
+            (r".", Error),
+        ],
+        "remark": [
+            (r"(?i)\\(remark|rem)\b(.*)$",
+             bygroups(op_remark, Comment.Single)),
+        ],
+        "entity-name": [      # may be multiline
+            (r"[^\\}]+", Name.Entity),
+            (r"\}", LexBase.op_ignore, "#pop"),
+            (r"\\\}", LexBase.op_fixed(Name.Entity, "}")),
+            (r"\\\\", LexBase.op_fixed(Name.Entity, "\\")),
+            (r"\\", LexBase.op_fixed(Generic.Error, "\\")),
+        ],
+        "entity-name-end": [  # may be multiline -- suppressed if no_end
+            (r"[^\\}]+", op_opt_ignore(Name.Entity)),
+            (r"\}", LexBase.op_ignore, "#pop"),
+            (r"\\\}", op_opt_ignore_or_fixed(Name.Entity, "}")),
+            (r"\\\\", op_opt_ignore_or_fixed(Name.Entity, "\\")),
+            (r"\\", op_opt_ignore_or_fixed(Generic.Error, "\\")),
+        ],
+        "expr": [
+            include("math-symbols"),          # must be before punctuation
+            include("ascii-punctuation"),
+            include("unicode-punctuation"),
+            include("escaped-string-start"),
+            include("py-strings"),
+            include("py-numbers"),
+            (r"(?i)\\(call|name)[ \t]*(\{)", LexBase.op_ignore, "entity-name"),
+            (r"(?i)\\gets\b", op_gets),
+            (r"(?i)\\text[ \t]*\{", LexBase.op_ignore, "text-in-expr"),
+            include("explicit-tokentype"),
+            include("remark"),
+            include("keyword-constants"),
+            include("word-operators"),
+            include("math-builtins"),
+            include("py-name"),
+        ],
+        "expr-in-braces": [
+            include("math-symbols"),          # must be before punctuation
+            include("ascii-punctuation-in-braces"),
+            include("unicode-punctuation"),
+            include("escaped-string-start"),
+            include("py-strings"),
+            include("py-numbers"),
+            (r"(?i)\\(call|name)[ \t]*(\{)", LexBase.op_ignore, "entity-name"),
+            (r"(?i)\\gets\b", op_gets),
+            (r"(?i)\\text[ \t]*\{", LexBase.op_ignore, "text-in-expr"),
+            include("explicit-tokentype"),
+            include("remark"),
+            include("keyword-constants"),
+            include("word-operators"),
+            include("math-builtins"),
+            include("py-name"),
+        ],
+        "block-expr": [      # somewhat similar to "root"
+            (r"\}", LexBase.op_ignore, "#pop"),
+            (r"\n", Whitespace),
+            include("expr-in-braces"),
+            (r"\\\\", LexBase.op_fixed(Text, "\\")),
+            (r"\\", LexBase.op_fixed(Generic.Error, "\\")),
+            include("unicode-separators"),
+            include("unicode-other"),
+            (r"[^\S\n]+", Text),
+            (r".", Error),
+        ],
+        "text-statement": [  # like block but default to text-mode
+            (r"[^\\}\n]+", Text),
+            (r"\}", LexBase.op_ignore, "#pop"),
+            (r"\n", Whitespace),
+            (r"\\\}", LexBase.op_fixed(Text, "}")),
+            (r"(?i)\\expr(?:ession)?[ \t]*\{",
+             LexBase.op_ignore,
+             "block-expr"),
+            include("explicit-tokentype"),
+            include("remark"),
+            (r"\\\\", LexBase.op_fixed(Text, "\\")),
+            (r"\\", LexBase.op_fixed(Text, "\\")),  # in text-mode: leave Text
+            (r".", Error),
+        ],
+        "text-in-expr": [
+            (r"[^\\}\n]+", Text),
+            (r"\}", LexBase.op_ignore, "#pop"),
+            (r"\n", Whitespace),
+            (r"\\\}", LexBase.op_fixed(Text, "}")),
+            (r"(?i)\\expr(?:ession)?[ \t]*\{",
+             LexBase.op_ignore,
+             "block-expr"),
+            include("explicit-tokentype"),
+            include("remark"),
+            (r"\\\\", LexBase.op_fixed(Text, "\\")),
+            (r"\\", LexBase.op_fixed(Text, "\\")),  # in text-mode: leave Text
+            (r".", Error),
+        ],
+        "math-builtins": [
+            (words(("sqrt", "pow", "cos", "sin", "tan", "arcos", "arcsin",
+                    "arctan", "arctan2", "mod", "exp", "ln", "log",
+                    "min", "max"),
+                   prefix=r"(?<!\.)",
+                   suffix=r"\b"),
+             Name.Builtin),
+        ],
+        "math-symbols": [
+            (r"<=>|<->|<-|->|=>|<=|>=|<>|!=|:=|=:|\?=", op_symbol(Operator)),
+            (r"[!&<>=+\-*/%|~]", Operator),         # ASCII
+            (u"[%s]" % (uniprops.Sm,), Operator),   # other Unicode
+        ],
+        "word-operators": [
+            (words(("IN", "In", "in",
+                    "IS", "Is", "is",
+                    "AND", "And", "and",
+                    "OR", "Or", "or",
+                    "XOR", "Xor", "xor",
+                    "NOT", "Not", "not"),
+                   prefix=r"(?<!\.)",
+                   suffix=r"\b"),
+             Operator.Word),
+        ],
+        "keyword-constants": [
+            (words(("True", "TRUE", "true", "False", "FALSE", "false",
+                    "None", "NONE", "none", "Nil", "NIL", "nil",
+                    "Null", "NULL", "null",
+                    "Empty", "EMPTY", "empty"),
+                   prefix=r"(?<!\.)",
+                   suffix=r"\b"),
+             Keyword.Constant),
+        ],
+        "ascii-punctuation": [
+            (r"\.+", Punctuation),
+            (r"[{}:(),;[\]?@]", Punctuation),
+        ],
+        "ascii-punctuation-in-braces": [
+            #
+            # Like "punctuation" but needs an escaped curly brace for } because
+            # a single closing curly brace pops the current state here.
+            #
+            (r"\\\}", LexBase.op_fixed(Punctuation, "}")),
+            (r"[{:(),;[\]?@]", Punctuation),
+        ],
+        "unicode-separators": [
+            (u"[%s]" % (uniprops.Zl,), Whitespace),
+            (u"[%s]" % (uniprops.Zp,), Whitespace),
+            (u"[%s]" % (uniprops.Zs,), Whitespace),
+        ],
+        "unicode-punctuation": [
+            (u"[%s]" % (uniprops.Pc,), Punctuation),
+            (u"[%s]" % (uniprops.Pd,), Punctuation),
+            (u"[%s]" % (uniprops.Ps,), Punctuation),
+            (u"[%s]" % (uniprops.Pe,), Punctuation),
+            (u"[%s]" % (uniprops.Pi,), Punctuation),
+            (u"[%s]" % (uniprops.Pf,), Punctuation),
+            (u"[%s]" % (uniprops.Po,), Punctuation),
+        ],
+        "unicode-other": [
+            (u"[%s]" % (uniprops.Sc,), Text),    # Currency
+            (u"[%s]" % (uniprops.So,), Text),    # Other symbols
+        ],
+        "escaped-string-start": [
+            (r"""\\(['"])""", bygroups(Punctuation)),
+        ],
+        "explicit-tokentype": [
+            # All these REs are CASE-SENSITIVE!
+
+            # Multiple characters possible, but no escaping!
+            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)(?P<sep>[/:|=*+!\$~])"
+             r"(?P<character>(.|\n)+?)(?P=sep)",
+             op_explicit_tokentype),
+            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\{(?P<character>[^}]+?)\}",
+             op_explicit_tokentype),
+            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\((?P<character>[^)]+?)\)",
+             op_explicit_tokentype),
+            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)<(?P<character>[^>]+?)>",
+             op_explicit_tokentype),
+            (r"\\ttx\-(?P<type>[a-zA-Z0-9_-]+?)\[(?P<character>[^\]]+?)\]",
+             op_explicit_tokentype),
+
+            # Every character is possible: no escaping needed!
+            (r"\\tt-(?P<type>[^/]+?)/(?P<character>(?:.|\n))",
+             op_explicit_tokentype),
+        ],
+    }
+
+    def __init__(self, **options):
+        self.no_end = pygments.util.get_bool_opt(
+            options, "no_end", default=False)
+        self.symbol_gets = options.get("gets", None)
+        if self.symbol_gets is None:
+            self.symbol_gets = self.SYMBOLS["<-"]   # Default: "⟵"  # U+27F5
+        self.symbol_remark = options.get("remark", None)
+        if self.symbol_remark is None:
+            self.symbol_remark = self.SYMBOLS["REM"]  # Default: "▷"  # U+25B7
+        LexBase.__init__(self, **options)
+        #
+        # Do this after calling the base because a "filters" option should be
+        # allowed to set **any** filter.
+        #
+        self.prohibit_raiseonerror_filter = pygments.util.get_bool_opt(
+            options, "prohibit_raiseonerror_filter", default=False)
+
+    def add_filter(self, filter_, **options):
+        #
+        # May be called by Lexer.__init__ when
+        # self.prohibit_raiseonerror_filter is not yet set.
+        # This is intentionally so.
+        #
+        if getattr(self, "prohibit_raiseonerror_filter", False):
+            if filter_ in ("raiseonerror",):
+                return
+        LexBase.add_filter(self, filter_, **options)
+
+
+class AlgPseudocodeLexer_DE(AlgPseudocodeLexer):
+
+    """
+
+    .. seealso::
+       - https://de.wikipedia.org/wiki/Pseudocode
+
+    .. todo:: Complete german translations
+
+    """
+
+    name = "AlgPseudocodeDE"
+    aliases = ["algpseudocode-de", "algpseudo-de"]
+    filenames = ["*.algpseudo-de", "*.algpseudocode-de"]
+
+    LANG = "de"
+    TRANSLATIONS = AlgPseudocodeLexer.TRANSLATIONS.copy()
+    TRANSLATIONS.update({
+        "PROG": "PROGRAMM",
+        "PROGRAM": "PROGRAMM",
+        "ALGO": "ALGORITHMUS",
+        "ALGORITHM": "ALGORITHMUS",
+        "PROC": "PROZEDUR",
+        "PROCEDURE": "PROZEDUR",
+        "FUNC": "FUNKTION",
+        "FUNCTION": "FUNKTION",
+        "FN": "FUNKTION",
+        "CLASS": "KLASSE",
+        "IS": "IST",
+        "WITH": "MIT",
+        "IF": "WENN",
+        "THEN": "DANN",
+        "ELSE": "ANDERNFALLS",
+        "ELSEIF": "ANDERNFALLS WENN",
+        "ELSIF": "ANDERNFALLS WENN",
+        "ELIF": "ANDERNFALLS WENN",
+#       "DO": # XXX TBD  # in WHILE WHILE ... DO  # noqa  # in WHILE ... DO
+        "WHILE": "SOLANGE",
+        "FOR": "FÜR",
+        "FORALL": "FÜR ALLE",
+        "FROM": "VON",
+        "TO": "BIS",
+        "IN": "IN",
+        "STEP": "SCHRITTWEITE",
+#       "LOOP": XXX TBD        # noqa
+        "REPEAT": "WIEDERHOLE",
+        "UNTIL": "BIS",
+#       "RETURN": XXX TBD      # noqa
+        "BEGIN": "START",
+        "END": "ENDE",
+    })
+    END_TRANSLATIONS = AlgPseudocodeLexer.END_TRANSLATIONS.copy()
+    END_TRANSLATIONS.update({
+        "PROG": "ENDE DES PROGRAMMS",
+        "PROGRAM": "ENDE DES PROGRAMMS",
+        "ALGO": "ENDE DES ALGORITHMUS",
+        "ALGORITHM": "ENDE DES ALGORITHMUS",
+        "PROC": "ENDE DER PROZEDUR",
+        "PROCEDURE": "ENDE DER PROZEDUR",
+        "FUNC": "ENDE DER FUNKTION",
+        "FUNCTION": "ENDE DER FUNKTION",
+        "FN": "ENDE DER FUNKTION",
+        "CLASS": "ENDE DER KLASSE",
+        "IF": "ENDE WENN",
+        "WHILE": "ENDE SOLANGE",
+        "FOR": "ENDE FÜR",
+        "FORALL": "ENDE FÜR ALLE",
+#       "LOOP": "ENDE XXX",            # XXX TBD   # noqa
+    })
+    DEFAULT_END_PREFIX = "ENDE VON "
+
+
+class AlgPseudocodeLexer_FR(AlgPseudocodeLexer):
+
+    """
+
+    .. seealso::
+      - https://info.blaisepascal.fr/pseudo-code/
+      - https://fr.wikipedia.org/wiki/Pseudo-code
+      - https://fr.wikipedia.org/wiki/Structure_de_contr%C3%B4le
+
+    .. todo:: Complete french translations
+
+    """
+
+    name = "AlgPseudocodeFR"
+    aliases = ["algpseudocode-fr", "algpseudo-fr"]
+    filenames = ["*.algpseudo-fr", "*.algpseudocode-fr"]
+
+    LANG = "fr"
+    TRANSLATIONS = AlgPseudocodeLexer.TRANSLATIONS.copy()
+    TRANSLATIONS.update({
+        "PROG": "PROGRAMME",
+        "PROGRAM": "PROGRAMME",
+        "ALGO": "ALGORITHME",
+        "ALGORITHM": "ALGORITHME",
+        "PROC": "PROCÉDURE",
+        "PROCEDURE": "PROCÉDURE",
+        "FUNC": "FONCTION",
+        "FUNCTION": "FOUNCTION",
+        "FN": "FONCTION",
+        "CLASS": "CLASSE",
+        "IS": "EST",
+        "WITH": "AVEC",
+        "IF": "SI",
+        "THEN": "ALORS",
+        "ELSE": "SINON",
+        "ELSEIF": "SINONSI",
+        "ELSIF": "SINONSI",
+        "ELIF": "SINONSI",
+        "DO": "FAIRE",             # as in in WHILE ... DO (not DO ... UNTIL)
+        "WHILE": "TANTQUE",
+        "FOR": "POUR",
+        "FORALL": "POUR CHAQUE",
+        "FROM": "DE",
+        "TO": "JUSQU'À",           # or just "À",
+        "IN": "DANS",              # as in FOR ... IN
+        "STEP": "PAR PAS DE",
+        "LOOP": "BOUCLE",          # XXX FIXME???
+        "REPEAT": "RÉPÉTER",
+        "UNTIL": "JUSQUACEQUE",
+        "RETURN": "RENVOYER",
+        "BEGIN": "DÉBUT",
+        "END": "FIN",
+    })
+    END_TRANSLATIONS = AlgPseudocodeLexer.END_TRANSLATIONS.copy()
+    END_TRANSLATIONS.update({
+        "PROG": "FIN DE PROGRAMME",
+        "PROGRAM": "FIN DE PROGRAMME",
+        "ALGO": "FIN D'ALGORITHME",
+        "ALGORITHM": "FIN D'ALGORITHME",
+        "PROC": "FIN DE PROCÉDURE",
+        "PROCEDURE": "FIN DE PROCÉDURE",
+        "FUNC": "FIN DE FONCTION",
+        "FUNCTION": "FIN DE FOUNCTION",
+        "FN": "FIN DE FONCTION",
+        "CLASS": "FIN DE CLASSE",
+        "SI": "FIN SI",
+        "FOR": "FIN POUR",
+        "FORALL": "FIN POUR CHAQUE",
+        "WHILE": "FIN TANTQUE",
+        "LOOP": "FIN BOUCLE",
+    })
+    DEFAULT_END_PREFIX = "FIN DE "
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pygments_lexer_pseudocode2/lexers/bases.py	Fri May 08 21:19:54 2026 +0200
@@ -0,0 +1,188 @@
+# -*- coding: utf-8 -*-
+# :-
+# SPDX-FileCopyrightText: © 2026 Franz Glasner
+# SPDX-License-Identifier: MIT
+# :-
+r"""Some common bases for the lexers."""
+
+__all__ = ["LexBase", "uni_name", "py_innerstring_rules", "py_name_rules"]
+
+
+import sys
+
+from pygments import unistring
+from pygments.lexer import RegexLexer, combined, bygroups, include
+from pygments.token import (Comment, Error, Name, Number, Other, String)
+
+
+PY2 = sys.version_info[0] <= 2
+
+
+#
+# SPDX-SnippetBegin
+# SPDX-License-Identifier: BSD-2-Clause
+# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
+# SPDX-SnippetCopyrightText: Copyright 2026 by Franz Glasner
+#
+
+uni_name = "[%s][%s]*" % (unistring.xid_start, unistring.xid_continue)
+
+
+"""PY3 allows no @staticmethod but PY2 needs it."""
+if PY2:
+    _staticmethod = staticmethod
+else:
+    def _staticmethod(fn):
+        return fn
+
+
+def py_innerstring_rules(ttype):
+    return [
+        # the old style '%s' % (...) string formatting (still valid in Py3)
+        (r'%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?'
+         '[hlL]?[E-GXc-giorsaux%]', String.Interpol),
+        # the new style '{}'.format(...) string formatting
+        (r'\{'
+         r'((\w+)((\.\w+)|(\[[^\]]+\]))*)?'  # field name
+         r'(\![sra])?'                       # conversion
+         r'(\:(.?[<>=\^])?[-+ ]?#?0?(\d+)?,?(\.\d+)?[E-GXb-gnosx%]?)?'
+         r'\}', String.Interpol),
+        #
+        # backslashes, quotes and formatting signs must be parsed
+        # one at a time
+        #
+        (r'[^\\\'"%{\n]+', ttype),
+        (r'[\'"\\]', ttype),
+        # unhandled string formatting sign
+        (r'%|(\{{1,2})', ttype)
+        # newlines are an error (use "nl" state)
+    ]
+
+
+def py_name_rules(ttype, deco_ttype=Name.Decorator):
+    return [
+        # We recognize decorator syntax here
+        (r'@' + uni_name, deco_ttype),
+        #
+        # Python's new matrix multiplication operator:
+        # not used here in pseudocode
+        # (r'@', Operator),
+        (uni_name, ttype),
+    ]
+
+# SPDX-SnippetEnd
+
+
+class LexBase(RegexLexer):
+
+    """A base that defines some common lexer states.
+
+    Default flags are not important.
+
+    """
+
+    def op_ignore(lexer, match, ctx=None):
+        """Unconditionally ignore the match."""
+        if False:
+            yield match.start(), Other, ""
+        if ctx:
+            ctx.pos = match.end()
+
+    @_staticmethod
+    def op_fixed(toktype, value):
+        """Unconditionally yield a given token type and value."""
+
+        def _op_fixed(lexer, match, ctx=None):
+            yield match.start(), toktype, value
+            if ctx:
+                ctx.pos = match.end()
+
+        return _op_fixed
+
+    tokens = {
+#
+# These states are borrowed from Pygment's Python lexer.
+# Their names have been prefixed with `py-'.
+#
+# SPDX-SnippetBegin
+# SPDX-License-Identifier: BSD-2-Clause
+# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
+# SPDX-SnippetCopyrightText: Copyright 2026 by Franz Glasner
+#
+        'py-numbers': [
+            (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
+             r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
+            (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
+            (r'0[oO](?:_?[0-7])+', Number.Oct),
+            (r'0[bB](?:_?[01])+', Number.Bin),
+            (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
+            (r'\d(?:_?\d)*', Number.Integer),
+        ],
+        'py-strings': [
+            # non-raw strings
+            ('([uU]?)(""")', bygroups(String.Affix, String.Double),
+             combined('py-stringescape', 'py-tdqs')),
+            ("([uU]?)(''')", bygroups(String.Affix, String.Single),
+             combined('py-stringescape', 'py-tsqs')),
+            ('([uU]?)(")', bygroups(String.Affix, String.Double),
+             combined('py-stringescape', 'py-dqs')),
+            ("([uU]?)(')", bygroups(String.Affix, String.Single),
+             combined('py-stringescape', 'py-sqs')),
+            # non-raw bytes
+            ('([bB])(""")', bygroups(String.Affix, String.Double),
+             combined('py-bytesescape', 'py-tdqs')),
+            ("([bB])(''')", bygroups(String.Affix, String.Single),
+             combined('py-bytesescape', 'py-tsqs')),
+            ('([bB])(")', bygroups(String.Affix, String.Double),
+             combined('py-bytesescape', 'py-dqs')),
+            ("([bB])(')", bygroups(String.Affix, String.Single),
+             combined('py-bytesescape', 'py-sqs')),
+        ],
+        'py-stringescape': [
+            (r'\\(N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8})', String.Escape),
+            include('py-bytesescape')
+        ],
+        'py-bytesescape': [
+            (r'\\([\\abfnrtv"\']|\n|x[a-fA-F0-9]{2}|[0-7]{1,3})',
+             String.Escape)
+        ],
+        'py-dqs': [
+            (r'"', String.Double, '#pop'),
+            (r'\\\\|\\"|\\\n', String.Escape),  # included here for raw strings
+            include('py-strings-double'),
+            (r'\n', Error),    # added by fag
+        ],
+        'py-sqs': [
+            (r"'", String.Single, '#pop'),
+            (r"\\\\|\\'|\\\n", String.Escape),  # included here for raw strings
+            include('py-strings-single'),
+            (r'\n', Error),    # added by fag
+        ],
+        'py-tdqs': [
+            (r'"""', String.Double, '#pop'),
+            include('py-strings-double'),
+            (r'\n', String.Double)
+        ],
+        'py-tsqs': [
+            (r"'''", String.Single, '#pop'),
+            include('py-strings-single'),
+            (r'\n', String.Single)
+        ],
+        'py-strings-single': py_innerstring_rules(String.Single),
+        'py-strings-double': py_innerstring_rules(String.Double),
+        'py-name': py_name_rules(Name.Entity),
+# SPDX-SnippetEnd
+    # This snippet is from the Pygments' documentation "Write your own lexer"
+    'multiline-nested-comment': [
+            (r'[^*/]+', Comment.Multiline),
+            (r'/\*', Comment.Multiline, '#push'),
+            (r'\*/', Comment.Multiline, '#pop'),
+            (r'[*/]', Comment.Multiline),
+        ],
+    'multiline-nested-comment-alt': [
+            (r'[^*()]+', Comment.Multiline),
+            (r'\(\*', Comment.Multiline, '#push'),
+            (r'\*\)', Comment.Multiline, '#pop'),
+            (r'[*()]', Comment.Multiline),
+        ]
+    }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pygments_lexer_pseudocode2/lexers/fr_pseudocode.py	Fri May 08 21:19:54 2026 +0200
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+# :-
+# SPDX-FileCopyrightText: © 2015 Simon Wachter
+# SPDX-FileCopyrightText: © 2026 Franz Glasner
+# SPDX-License-Identifier: MIT
+# :-
+r"""
+:Author:    Simon Wachter
+:Author:    Franz Glasner
+:Copyright: © 2015 Simon Wachter
+:Copyright: © 2026 Franz Glasner
+:License:   MIT License.
+            See :file:`MIT.txt` for details.
+            If you cannot find MIT.txt see
+            <http://opensource.org/licenses/MIT>.
+"""
+
+__all__ = ["FrPseudocodeLexer"]
+
+
+import re
+
+from pygments.lexer import RegexLexer, include
+from pygments.token import Punctuation, Whitespace, Comment, Operator, Keyword, Name, String, Number
+
+
+class FrPseudocodeLexer(RegexLexer):
+    '''
+    A Pseudo code (fr) lexer
+    '''
+    name = 'FrPseudocode'
+    aliases = ['fr-pseudocode', 'fr-pseudo', 'fr-algorithm', 'fr-algo']
+    filenames = ['*.fr-algo', '*.fr-pseudocode']
+    mimetypes = []
+    flags = re.MULTILINE
+
+    REPLACEMENTS = {
+        '<=': '≤',
+        '>=': '≥',
+        '<>': '≠',
+        '!=': '≠',
+        '<-': '←',
+        '->': '→',
+        '=>': '⇒',
+        '<->': '↔',
+        '<=>': '⇔',
+        '^': '↑',
+    }
+
+    def op_replace(lexer, match):
+        op = match.group(0)
+        yield match.start(), Operator, lexer.REPLACEMENTS.get(op, op)
+
+    def scomment(lexer, match):
+        s = match.group(1).lower().strip()
+        c = Comment
+
+        directives = ['passage par copie', 'passage par valeur', 'passage par référence', 'passage par reference', 'passage par adresse', 've', 'vs', 've/s']
+
+        if s in directives:
+            c = Comment.Special
+
+        yield match.start(), c, match.group(0)
+
+    tokens = {
+        'root': [
+                 (r'\/\*.*\*\/', Comment),
+                 (r'(\/\/|#).*\n', Comment),
+                 (r'\|', Comment),
+                 (r'\{(.*)\}', scomment),
+                 include('strings'),
+                 include('core'),
+                 (r'(?i)[a-zéàùçèÉÀÙÇÈ][a-z0-9éàùçèÉÀÙÇÈ_]*', Name.Variable),
+                 include('numbers'),
+                 (r'[\s]+', Whitespace)
+        ],
+        'core': [ # Statements
+                 (r'(?i)\b(debut|début|fin|si|alors|sinon|fin[_ ]si|tant[ _]que|tantque|fin[ _]tantque|faire|répéter|'
+                  r'repeter|type|structure|fin[ _]structure|fonction|procédure|procedure|retourner|renvoyer|'
+                  r'pour|fin[ _]pour|à|déclarations?|juqsque|spécialise|specialise|comporte|super|public|privé|protégé|'
+                  r'classe'
+                  r')\s*\b', Keyword),
+
+                 # Data Types
+                 (r'(?i)\b(entiers?|chaines?|chaînes?|réels?|reels?|caractères?|caracteres?|booléens?|'
+                  r'booleens?|tableaux?|rien)\s*\b',
+                  Keyword.Type),
+
+                  (r'(?i)\b(vrai|faux|nil)\s*\b',
+                   Name.Constant),
+
+                 # Operators
+                 (r'(?i)(<->|<=>|<=|>=|<>|!=|<-|->|=>|\^|\*|\+|-|\/|<|>|=|\\\\|mod|←|↑|≤|≥|≠|÷|×|\.\.|\[|\]|\.|non|xou|et|ou)',
+                  op_replace),
+
+                 (r'(\(|\)|\,|\;|:)',
+                  Punctuation),
+
+                 #(r'\b(\[(VE|VS|VE/S)\])\s*\b',
+                 # Keyword.Declaration),
+
+                  # Intrinsics
+                 (r'(?i)\b(sqrt|pow|cos|sin|tan|arccos|arcsin|arctan|arctan2|lire|ecrire|écrire|'
+                  r'exp|ln|log|détruire|detruire'
+                  r')\s*\b', Name.Builtin)
+                ],
+
+        'strings': [
+                 (r'"([^"])*"', String.Double),
+                 (r"'([^'])*'", String.Single),
+                ],
+#
+# This is stolen from the Pygment's Python lexer.
+#
+# SPDX-SnippetBegin
+# SPDX-License-Identifier: BSD-2-Clause
+# SPDX-SnippetCopyrightText: Copyright 2006-2023 by the Pygments team
+        'numbers': [
+            (r'(\d(?:_?\d)*\.(?:\d(?:_?\d)*)?|(?:\d(?:_?\d)*)?\.\d(?:_?\d)*)'
+             r'([eE][+-]?\d(?:_?\d)*)?', Number.Float),
+            (r'\d(?:_?\d)*[eE][+-]?\d(?:_?\d)*j?', Number.Float),
+            (r'0[oO](?:_?[0-7])+', Number.Oct),
+            (r'0[bB](?:_?[01])+', Number.Bin),
+            (r'0[xX](?:_?[a-fA-F0-9])+', Number.Hex),
+            (r'\d(?:_?\d)*', Number.Integer),
+        ],
+# SPDX-SnippetEnd
+        }
--- a/pyproject.toml	Fri May 08 19:49:08 2026 +0200
+++ b/pyproject.toml	Fri May 08 21:19:54 2026 +0200
@@ -45,14 +45,15 @@
 
 [project.entry-points.'pygments.lexers']
 # The mostly original and somewhat extended Pseudocode lexer (fr)
-fr_pseudocodelexer = "pygments_lexer_pseudocode2.fr_pseudocode:FrPseudocodeLexer"
-algpseudocodelexer = "pygments_lexer_pseudocode2.algpseudocode:AlgPseudocodeLexer"
-algpseudocodelexer_de = "pygments_lexer_pseudocode2.algpseudocode:AlgPseudocodeLexer_DE"
-algpseudocodelexer_fr = "pygments_lexer_pseudocode2.algpseudocode:AlgPseudocodeLexer_FR"
+fr_pseudocodelexer = "pygments_lexer_pseudocode2.lexers.fr_pseudocode:FrPseudocodeLexer"
+algpseudocodelexer = "pygments_lexer_pseudocode2.lexers.algpseudocode:AlgPseudocodeLexer"
+algpseudocodelexer_de = "pygments_lexer_pseudocode2.lexers.algpseudocode:AlgPseudocodeLexer_DE"
+algpseudocodelexer_fr = "pygments_lexer_pseudocode2.lexers.algpseudocode:AlgPseudocodeLexer_FR"
 
 [tool.setuptools]
 packages = [
     "pygments_lexer_pseudocode2",
+    "pygments_lexer_pseudocode2.lexers",    
 ]
 platforms = ["any"]
 zip-safe = true
--- a/tests/_tsetup.py	Fri May 08 19:49:08 2026 +0200
+++ b/tests/_tsetup.py	Fri May 08 21:19:54 2026 +0200
@@ -28,10 +28,12 @@
 sys.path.insert(0, PROJECTDIR)
 FRLEXERFILENAME = os.path.join(PROJECTDIR,
                                "pygments_lexer_pseudocode2",
+                               "lexers",
                                "fr_pseudocode.py")
 FRLEXERCLASS = "FrPseudocodeLexer"
 ALGLEXERFILENAME = os.path.join(PROJECTDIR,
                                 "pygments_lexer_pseudocode2",
+                                "lexers",
                                 "algpseudocode.py")
 ALGLEXERCLASS = "AlgPseudocodeLexer"
 TESTSNIPPETSDIR = os.path.join(
--- a/tox.ini	Fri May 08 19:49:08 2026 +0200
+++ b/tox.ini	Fri May 08 21:19:54 2026 +0200
@@ -3,10 +3,10 @@
 [flake8]
 per-file-ignores =
     # E122: continuation line missing indentation or outdented
-    pygments_lexer_pseudocode2/bases.py: E122
+    pygments_lexer_pseudocode2/lexers/bases.py: E122
     # E122: continuation line missing indentation or outdented
     # E131: continuation line unaligned for hanging indent
     # E261: at least two spaces before inline comment
     # E265: block comment should start with '# '
     # E501: line too long
-    pygments_lexer_pseudocode2/fr_pseudocode.py:E122,E131,E261,E265,E501
+    pygments_lexer_pseudocode2/lexers/fr_pseudocode.py:E122,E131,E261,E265,E501