def doxygen(variable_name)
    command_grammars = []
    # these commands are broken up by what part of the text they capture
    # - standalone: nothing
    # - word: The next word
    # - line: to the end of the line
    # - paragraph: until a blank line of another paragraph command is reached
    # - block: until the corrosponding endfoo command

    # if a command takes multiple arguments (not include {} arguments) it is treated as
    # at least line

    standalone_command = [
        "callergraph",
        "callgraph",
        "else",
        "endif",
        "f$",
        "f[",
        "f]",
        "hidecallergraph",
        "hidecallgraph",
        "hiderefby",
        "hiderefs",
        "hideinitializer",
        "htmlinclude",
        "n",
        "nosubgrouping",
        "private",
        "privatesection",
        "protected",
        "protectedsection",
        "public",
        "publicsection",
        "pure",
        "showinitializer",
        "showrefby",
        "showrefs",
        "tableofcontents",
        "$",
        "#",
        "<",
        ">",
        "%",
        "\"",
        ".",
        "=",
        "::",
        "|",
        "--",
        "---",
    ]

    word_commands = [
        "a",
        "anchor",
        "b",
        "c",
        "cite",
        "copybrief",
        "copydetail",
        "copydoc",
        "def",
        "dir",
        "dontinclude",
        "e",
        "em",
        "emoji",
        "enum",
        "example",
        "extends",
        "file",
        "idlexcept",
        "implements",
        "include",
        "includedoc",
        "includelineno",
        "latexinclude",
        "link",
        "memberof",
        "namespace",
        "p",
        "package",
        "ref",
        "refitem",
        "related",
        "relates",
        "relatedalso",
        "relatesalso",
        "verbinclude",
    ]
    
    line_commands = [
        "addindex",
        "addtogroup",
        "category",
        "class",
        "defgroup",
        "diafile",
        "dotfile",
        "elseif",
        "fn",
        "headerfile",
        "if",
        "ifnot",
        "image",
        "ingroup",
        "interface",
        "line",
        "mainpage",
        "mscfile",
        "name",
        "overload",
        "page",
        "property",
        "protocol",
        "section",
        "skip",
        "skipline",
        "snippet",
        "snippetdoc",
        "snippetlineno",
        "struct",
        "subpage",
        "subsection",
        "subsubsection",
        "typedef",
        "union",
        "until",
        "vhdlflow",
        "weakgroup",
    ]
    
    block_commands = [
        "code",
        "cond",
        "docbookonly",
        "dot",
        "htmlonly",
        "internal",
        "latexonly",
        "link",
        "manonly",
        "msc",
        "parblock",
        "rtfonly",
        "secreflist",
        "startuml",
        "verbatim",
        "xmlonly",
    ]
    
    paragraph_commands = [
        "arg",
        "attention",
        "author",
        "authors",
        "brief",
        "bug",
        "copyright",
        "date",
        "deprecated",
        "details",
        "exception",
        "invariant",
        "li",
        "note",
        "par",
        "paragraph",
        "param",
        "post",
        "pre",
        "remark",
        "remarks",
        "result",
        "return",
        "returns",
        "retval",
        "sa",
        "see",
        "short",
        "since",
        "test",
        "throw",
        "throws",
        "todo",
        "tparam",
        "version",
        "warning",
        "xrefitem",
    ]
    
    command_grammars << Pattern.new(
        match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(
            Pattern.new(/(?:#{standalone_command.map{|pat| Regexp.escape pat}.join("|")})/)
        ).then(/\b/).maybe(/\{[^}]*\}/),
        # this tag_as makes no sense but it is what jsdoc commands are tagged as
        # (storage.type.class)
        tag_as: "storage.type.class.doxygen",
    )
    
    # italics
    command_grammars << Pattern.new(
        match: Pattern.new(
            match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(/(?:a|em|e)/),
            tag_as: "storage.type.class.doxygen",
        ).then(@spaces).then(
            match: /\S+/,
            tag_as: "markup.italic.doxygen",
        ),
    )

    # bold
    command_grammars << Pattern.new(
        match: Pattern.new(
            match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(/b/),
            tag_as: "storage.type.class.doxygen",
        ).then(@spaces).then(
            match: /\S+/,
            tag_as: "markup.bold.doxygen",
        ),
    )

    # inline code
    command_grammars << Pattern.new(
        match: Pattern.new(
            match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(/(?:c|p)/),
            tag_as: "storage.type.class.doxygen",
        ).then(@spaces).then(
            match: /\S+/,
            tag_as: "markup.inline.raw.string",
        ),
    )

    # TODO: make this consume and possibly reprocess the next word
    command_grammars << Pattern.new(
        match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(
            Pattern.new(/(?:#{word_commands.join("|")})/)
        ).then(/\b/).maybe(/\{[^}]*\}/),
        # this tag_as makes no sense but it is what jsdoc commands are tagged as
        # (storage.type.class)
        tag_as: "storage.type.class.doxygen",
    )


    # TODO: make this consume and possibly reprocess the next line
    command_grammars << Pattern.new(
        match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(
            Pattern.new(/(?:#{line_commands.join("|")})/)
        ).then(/\b/).maybe(/\{[^}]*\}/),
        # this tag_as makes no sense but it is what jsdoc commands are tagged as
        # (storage.type.class)
        tag_as: "storage.type.class.doxygen",
    )

    parameter = Pattern.new(
        match: variable_name,
        tag_as:"variable.parameter"
    )
    comma = Pattern.new(
        match:",",
        tag_as: "punctuation",
    )
    command_grammars << Pattern.new(
        Pattern.new(
            match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(/param/),
            tag_as: "storage.type.class.doxygen",
        ).maybe(
            Pattern.new(/\s*\[/).oneOrMoreOf(
                match: maybe(/,/).maybe(@spaces).then(/(?:in|out)/).maybe(@spaces),
                includes: [
                    Pattern.new(
                        match: /in|out/,
                        tag_as: "keyword.other.parameter.direction.$match"
                    )
                ]
            ).then(/\]/)
        ).then(
            match: Pattern.new(
                @spaces.then(
                    parameter
                ).zeroOrMoreOf(
                    comma.maybe(@spaces).then(parameter)
                ),
            ),
            includes: [
                parameter,
                comma,
            ]
        ),
    )
    
    # TODO: make this consume and possibly reprocess the next paragraph
    command_grammars << Pattern.new(
        match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(
            Pattern.new(/(?:#{paragraph_commands.join("|")})/)
        ).then(/\b/).maybe(/\{[^}]*\}/),
        # this tag_as makes no sense but it is what jsdoc commands are tagged as
        # (storage.type.class)
        tag_as: "storage.type.class.doxygen",
    )

    with_end_blocks = [
        *block_commands,
        *block_commands.map do |block|
            "end" + block.gsub("start", "")
        end,
    ]

    # TODO: make this consume and possibly reprocess the next block
    command_grammars << Pattern.new(
        match: lookBehindFor(/[\s*!\/]/).then(/[\\@]/).then(
            Pattern.new(/(?:#{with_end_blocks.join("|")})/)
        ).then(/\b/).maybe(/\{[^}]*\}/),
        # this tag_as makes no sense but it is what jsdoc commands are tagged as
        # (storage.type.class)
        tag_as: "storage.type.class.doxygen",
    )

    # gtk doc
    command_grammars << Pattern.new(
        match: Pattern.new(/\b[A-Z]+:/).or(/@[a-z_]+:/),
        tag_as: "storage.type.class.gtkdoc",
    )

    line_comment = PatternRange.new(
        tag_as: "comment.line.double-slash.documentation",
        start_pattern: Pattern.new(
            @start_of_line.maybe(match: @spaces, dont_back_track?: true).then(
                match: Pattern.new(/\/\//).oneOrMoreOf(/[!\/]/),
                tag_as: "punctuation.definition.comment.documentation",
            )
        ),
        # a newline that doesnt have a line continuation
        end_pattern: lookBehindFor(/\n/).lookBehindToAvoid(/\\\n/),
        includes: [ :line_continuation_character, *command_grammars ]
    )

    single_line_block_comment = Pattern.new(
        tag_as: "comment.block.documentation",
        match: Pattern.new(
            match: Pattern.new(/\/\*/).oneOrMoreOf(/[!*]/).lookAheadFor(/\s/),
            tag_as: "punctuation.definition.comment.begin.documentation",
        ).then(
            match: oneOrMoreOf(/./),
            includes: command_grammars,
        ).then(
            match: zeroOrMoreOf(/[!*]/).then(/\*\//),
            tag_as: "punctuation.definition.comment.end.documentation",
        ),
    )

    block_comment = PatternRange.new(
        tag_as: "comment.block.documentation",
        start_pattern: Pattern.new(
            match: maybe(match: @spaces, dont_back_track?: true).then(/\/\*/).oneOrMoreOf(/[!*]/).then(@end_of_line.or(lookAheadFor(/\s/))),
            tag_as: "punctuation.definition.comment.begin.documentation",
        ),
        end_pattern: Pattern.new(
            match: zeroOrMoreOf(/[!*]/).then(/\*\//),
            tag_as: "punctuation.definition.comment.end.documentation",
        ),
        includes: [
            *command_grammars,
        ],
    )

    return [line_comment, single_line_block_comment, block_comment]
end