diff pygments_lexer_pseudocode2/lexers/algpseudocode.py @ 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 pygments_lexer_pseudocode2/algpseudocode.py@11ce0903ff8b
children 33a722c8ae17
line wrap: on
line diff
--- /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 "