text:
@enum(Kind::UInt16,
    NONE,      # Placeholder; never emitted by lexer
    ENDMARKER, # EOF
    COMMENT, # aadsdsa, #= fdsf #=
    WHITESPACE, # '\n   \t'
    IDENTIFIER, # foo, Σxx
    AT_SIGN, # @
    COMMA, #,
    SEMICOLON, # ;

    begin_errors,
        EOF_MULTICOMMENT,
        EOF_CHAR,
        INVALID_NUMERIC_CONSTANT,
        INVALID_OPERATOR,
        INVALID_INTERPOLATION_TERMINATOR,
        ERROR,
    end_errors,

    begin_keywords,
        KEYWORD, # general
        BAREMODULE,
        BEGIN,
        BREAK,
        CATCH,
        CONST,
        CONTINUE,
        DO,
        ELSE,
        ELSEIF,
        END,
        EXPORT,
        FINALLY,
        FOR,
        FUNCTION,
        GLOBAL,
        IF,
        IMPORT,
        LET,
        LOCAL,
        MACRO,
        MODULE,
        QUOTE,
        RETURN,
        STRUCT,
        TRY,
        USING,
        WHILE,
        begin_contextual_keywords,
            ABSTRACT,
            AS,
            DOC,
            MUTABLE,
            OUTER,
            PRIMITIVE,
            TYPE,
            VAR,
        end_contextual_keywords,
    end_keywords,

    begin_cstparser,
        INVISIBLE_BRACKETS,
        NOTHING,  # A literal `nothing`
        WS,
        SEMICOLON_WS,
        NEWLINE_WS,
        EMPTY_WS,
    end_cstparser,

    begin_literal,
        LITERAL, # general
        INTEGER, # 4
        BIN_INT, # 0b1
        HEX_INT, # 0x0
        OCT_INT, # 0o0
        FLOAT, # 3.5, 3.7e+3
        STRING, # "foo" (without the " delimiters)
        CHAR, # 'a'
        CMD, # `cmd ...` (without delimiters)
        TRUE, FALSE,
    end_literal,

    begin_delimiters,
        LSQUARE, # [
        RSQUARE, # [
        LBRACE, # {
        RBRACE, # }
        LPAREN, # (
        RPAREN,  # )
        DQUOTE,  # " (double quote)
        TRIPLE_DQUOTE, # """
        BACKTICK,  # `
        TRIPLE_BACKTICK, # ```
    end_delimiters,

    begin_ops,
        OP, # general
        DDDOT, # ...

        # Level 1
        begin_assignments,
            EQ, # =
            PLUS_EQ, # +=
            MINUS_EQ, # -=
            STAR_EQ, # *=
            FWD_SLASH_EQ, # /=
            FWDFWD_SLASH_EQ, # //=
            OR_EQ, # |=
            CIRCUMFLEX_EQ, # ^=
            DIVISION_EQ, # ÷=
            REM_EQ, # %=
            LBITSHIFT_EQ, # <<=
            RBITSHIFT_EQ, # >>=
            UNSIGNED_BITSHIFT_EQ, # >>>=
            BACKSLASH_EQ, # \=
            AND_EQ, # &=
            COLON_EQ, # :=
            APPROX, # ~
            EX_OR_EQ, # $=
            XOR_EQ, # ⊻=
        end_assignments,

        begin_pairarrow,
            PAIR_ARROW, # =>
        end_pairarrow,

        # Level 2
        begin_conditional,
            CONDITIONAL, # ?
        end_conditional,

        # Level 3
        begin_arrow,
            RIGHT_ARROW, # -->
            LEFT_ARROW, # <--
            DOUBLE_ARROW, # <-->
            LEFTWARDS_ARROW, # ←
            RIGHTWARDS_ARROW, # →
            LEFT_RIGHT_ARROW, # ↔
            LEFTWARDS_ARROW_WITH_STROKE, # ↚
            RIGHTWARDS_ARROW_WITH_STROKE, # ↛
            LEFTWARDS_TWO_HEADED_ARROW,# ↞
            RIGHTWARDS_TWO_HEADED_ARROW, # ↠
            LEFTWARDS_ARROW_WITH_TAIL, # ↢
            RIGHTWARDS_ARROW_WITH_TAIL, # ↣
            LEFTWARDS_ARROW_FROM_BAR,# ↤
            RIGHTWARDS_ARROW_FROM_BAR, # ↦
            LEFT_RIGHT_ARROW_WITH_STROKE, # ↮
            LEFT_RIGHT_DOUBLE_ARROW_WITH_STROKE, # ⇎
            LEFTWARDS_DOUBLE_ARROW_WITH_STROKE, # ⇍
            RIGHTWARDS_DOUBLE_ARROW_WITH_STROKE, # ⇏
            LEFTWARDS_DOUBLE_ARROW, # ⇐
            RIGHTWARDS_DOUBLE_ARROW, # ⇒
            LEFT_RIGHT_DOUBLE_ARROW, # ⇔
            RIGHT_ARROW_WITH_SMALL_CIRCLE, # ⇴
            THREE_RIGHTWARDS_ARROWS, # ⇶
            LEFTWARDS_ARROW_WITH_VERTICAL_STROKE, # ⇷
            RIGHTWARDS_ARROW_WITH_VERTICAL_STROKE, # ⇸
            LEFT_RIGHT_ARROW_WITH_VERTICAL_STROKE, # ⇹
            LEFTWARDS_ARROW_WITH_DOUBLE_VERTICAL_STROKE, # ⇺
            RIGHTWARDS_ARROW_WITH_DOUBLE_VERTICAL_STROKE, # ⇻
            LEFT_RIGHT_ARROW_WITH_DOUBLE_VERTICAL_STROKE, # ⇼
            LEFTWARDS_OPEN_HEADED_ARROW, # ⇽
            RIGHTWARDS_OPEN_HEADED_ARROW, # ⇾
            LEFT_RIGHT_OPEN_HEADED_ARROW, # ⇿
            LONG_LEFTWARDS_ARROW, # ⟵
            LONG_RIGHTWARDS_ARROW, # ⟶
            LONG_LEFT_RIGHT_ARROW, # ⟷
            LONG_RIGHTWARDS_DOUBLE_ARROW, # ⟹
            LONG_LEFT_RIGHT_DOUBLE_ARROW, # ⟺
            LONG_LEFTWARDS_ARROW_FROM_BAR, # ⟻
            LONG_RIGHTWARDS_ARROW_FROM_BAR, # ⟼
            LONG_LEFTWARDS_DOUBLE_ARROW_FROM_BAR, # ⟽
            LONG_RIGHTWARDS_DOUBLE_ARROW_FROM_BAR, # ⟾
            LONG_RIGHTWARDS_SQUIGGLE_ARROW, # ⟿
            RIGHTWARDS_TWO_HEADED_ARROW_WITH_VERTICAL_STROKE, # ⤀
            RIGHTWARDS_TWO_HEADED_ARROW_WITH_DOUBLE_VERTICAL_STROKE, # ⤁
            LEFTWARDS_DOUBLE_ARROW_WITH_VERTICAL_STROKE, # ⤂
            RIGHTWARDS_DOUBLE_ARROW_WITH_VERTICAL_STROKE, # ⤃
            LEFT_RIGHT_DOUBLE_ARROW_WITH_VERTICAL_STROKE, # ⤄
            RIGHTWARDS_TWO_HEADED_ARROW_FROM_BAR, # ⤅
            LEFTWARDS_DOUBLE_ARROW_FROM_BAR, # ⤆
            RIGHTWARDS_DOUBLE_ARROW_FROM_BAR, # ⤇
            LEFTWARDS_DOUBLE_DASH_ARROW, # ⤌
            RIGHTWARDS_DOUBLE_DASH_ARROW, # ⤍
            LEFTWARDS_TRIPLE_DASH_ARROW, # ⤎
            RIGHTWARDS_TRIPLE_DASH_ARROW, # ⤏
            RIGHTWARDS_TWO_HEADED_TRIPLE_DASH_ARROW, # ⤐
            RIGHTWARDS_ARROW_WITH_DOTTED_STEM, # ⤑
            RIGHTWARDS_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE, # ⤔
            RIGHTWARDS_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE, # ⤕
            RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL, # ⤖
            RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE, # ⤗
            RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE, # ⤘
            LEFTWARDS_ARROW_TO_BLACK_DIAMOND, # ⤝
            RIGHTWARDS_ARROW_TO_BLACK_DIAMOND, # ⤞
            LEFTWARDS_ARROW_FROM_BAR_TO_BLACK_DIAMOND, # ⤟
            RIGHTWARDS_ARROW_FROM_BAR_TO_BLACK_DIAMOND, # ⤠
            SHORT_RIGHTWARDS_ARROW_ABOVE_LEFTWARDS_ARROW, # ⥄
            RIGHTWARDS_ARROW_WITH_PLUS_BELOW, # ⥅
            LEFTWARDS_ARROW_WITH_PLUS_BELOW, # ⥆
            RIGHTWARDS_ARROW_THROUGH_X, # ⥇
            LEFT_RIGHT_ARROW_THROUGH_SMALL_CIRCLE, # ⥈
            LEFT_BARB_UP_RIGHT_BARB_DOWN_HARPOON, # ⥊
            LEFT_BARB_DOWN_RIGHT_BARB_UP_HARPOON, # ⥋
            LEFT_BARB_UP_RIGHT_BARB_UP_HARPOON, # ⥎
            LEFT_BARB_DOWN_RIGHT_BARB_DOWN_HARPOON, # ⥐
            LEFTWARDS_HARPOON_WITH_BARB_UP_TO_BAR, # ⥒
            RIGHTWARDS_HARPOON_WITH_BARB_UP_TO_BAR, # ⥓
            LEFTWARDS_HARPOON_WITH_BARB_DOWN_TO_BAR, # ⥖
            RIGHTWARDS_HARPOON_WITH_BARB_DOWN_TO_BAR, # ⥗
            LEFTWARDS_HARPOON_WITH_BARB_UP_FROM_BAR, # ⥚
            RIGHTWARDS_HARPOON_WITH_BARB_UP_FROM_BAR, # ⥛
            LEFTWARDS_HARPOON_WITH_BARB_DOWN_FROM_BAR, # ⥞
            RIGHTWARDS_HARPOON_WITH_BARB_DOWN_FROM_BAR, # ⥟
            LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_DOWN, # ⥢
            RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_DOWN, # ⥤
            LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_UP, # ⥦
            LEFTWARDS_HARPOON_WITH_BARB_DOWN_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_DOWN, # ⥧
            RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_UP, # ⥨
            RIGHTWARDS_HARPOON_WITH_BARB_DOWN_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_DOWN, # ⥩
            LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LONG_DASH, # ⥪
            LEFTWARDS_HARPOON_WITH_BARB_DOWN_BELOW_LONG_DASH, # ⥫
            RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LONG_DASH, # ⥬
            RIGHTWARDS_HARPOON_WITH_BARB_DOWN_BELOW_LONG_DASH, # ⥭
            RIGHT_DOUBLE_ARROW_WITH_ROUNDED_HEAD, # ⥰
            RULE_DELAYED, # ⧴
            THREE_LEFTWARDS_ARROWS, # ⬱
            LEFT_ARROW_WITH_SMALL_CIRCLE, # ⬰
            LEFT_ARROW_WITH_CIRCLED_PLUS, # ⬲
            LONG_LEFTWARDS_SQUIGGLE_ARROW, # ⬳
            LEFTWARDS_TWO_HEADED_ARROW_WITH_VERTICAL_STROKE, # ⬴
            LEFTWARDS_TWO_HEADED_ARROW_WITH_DOUBLE_VERTICAL_STROKE, # ⬵
            LEFTWARDS_TWO_HEADED_ARROW_FROM_BAR, # ⬶
            LEFTWARDS_TWO_HEADED_TRIPLE_DASH_ARROW, # ⬷
            LEFTWARDS_ARROW_WITH_DOTTED_STEM, # ⬸
            LEFTWARDS_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE, # ⬹
            LEFTWARDS_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE, # ⬺
            LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL, # ⬻
            LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE, # ⬼
            LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE, # ⬽
            LEFTWARDS_ARROW_THROUGH_X, # ⬾
            WAVE_ARROW_POINTING_DIRECTLY_LEFT, # ⬿
            EQUALS_SIGN_ABOVE_LEFTWARDS_ARROW, # ⭀
            REVERSE_TILDE_OPERATOR_ABOVE_LEFTWARDS_ARROW, # ⭁
            LEFTWARDS_ARROW_ABOVE_REVERSE_ALMOST_EQUAL_TO, # ⭂
            RIGHTWARDS_ARROW_THROUGH_GREATER_THAN, # ⭃
            RIGHTWARDS_ARROW_THROUGH_SUPERSET, # ⭄
            REVERSE_TILDE_OPERATOR_ABOVE_RIGHTWARDS_ARROW, # ⭇
            RIGHTWARDS_ARROW_ABOVE_REVERSE_ALMOST_EQUAL_TO, # ⭈
            TILDE_OPERATOR_ABOVE_LEFTWARDS_ARROW, # ⭉
            LEFTWARDS_ARROW_ABOVE_ALMOST_EQUAL_TO, # ⭊
            LEFTWARDS_ARROW_ABOVE_REVERSE_TILDE_OPERATOR, # ⭋
            RIGHTWARDS_ARROW_ABOVE_REVERSE_TILDE_OPERATOR, # ⭌
            HALFWIDTH_LEFTWARDS_ARROW, # ￩
            HALFWIDTH_RIGHTWARDS_ARROW, # ￫
            CIRCLE_ARROW_RIGHT,
            LEFT_SQUIGGLE_ARROW, # ⇜
            RIGHT_SQUIGGLE_ARROW, # ⇝
            LEFT_WAVE_ARROW, # ↜
            RIGHT_WAVE_ARROW, # ↝
            LEFTWARDS_ARROW_WITH_HOOK, # ↩
            RIGHTWARDS_ARROW_WITH_HOOK, # ↪
            LOOP_ARROW_LEFT, # ↫
            LOOP_ARROW_RIGHT, # ↬
            LEFT_HARPOON_UP, # ↼
            LEFT_HARPOON_DOWN, # ↽
            RIGHT_HARPOON_UP, # ⇀
            RIGHT_HARPOON_DOWN, # ⇁
            RIGHT_LEFT_ARROWS, # ⇄
            LEFT_RIGHT_ARROWS, # ⇆
            LEFT_LEFT_ARROWS, # ⇇
            RIGHT_RIGHT_ARROWS, # ⇉
            LEFT_RIGHT_HARPOONS, # ⇋
            RIGHT_LEFT_HARPOONS, # ⇌
            L_LEFT_ARROW, # ⇚
            R_RIGHT_ARROW, # ⇛
            LEFT_DASH_ARROW, # ⇠
            RIGHT_DASH_ARROW, # ⇢
            CURVE_ARROW_RIGHT, # ↷
            CURVE_ARROW_LEFT,# ↶
            CIRCLE_ARROW_LEFT,# ↺
        end_arrow,

        # Level 4
        begin_lazyor,
            LAZY_OR, # ||
        end_lazyor,

        # Level 5
        begin_lazyand,
            LAZY_AND, # &&
        end_lazyand,

        # Level 6
        begin_comparison,
            ISSUBTYPE, # <:
            ISSUPERTYPE, # >:
            GREATER, # >
            LESS, # <
            GREATER_EQ, # >=
            GREATER_THAN_OR_EQUAL_TO, # ≥
            LESS_EQ, # <=
            LESS_THAN_OR_EQUAL_TO, # ≤
            EQEQ, # ==
            EQEQEQ, # ===
            IDENTICAL_TO, # ≡
            NOT_EQ, # !=
            NOT_EQUAL_TO, # ≠
            NOT_IS, # !==
            NOT_IDENTICAL_TO, # ≢
            ELEMENT_OF, # ∈
            IN, # in
            ISA, # isa
            NOT_AN_ELEMENT_OF, # ∉
            CONTAINS_AS_MEMBER, # ∋
            DOES_NOT_CONTAIN_AS_MEMBER, # ∌
            SUBSET_OF_OR_EQUAL_TO, # ⊆
            NEITHER_A_SUBSET_OF_NOR_EQUAL_TO, # ⊈
            SUBSET_OF, # ⊂
            NOT_A_SUBSET_OF, # ⊄
            SUBSET_OF_WITH_NOT_EQUAL_TO, # ⊊
            PROPORTIONAL_TO, # ∝
            SMALL_ELEMENT_OF, # ∊
            SMALL_CONTAINS_AS_MEMBER, # ∍
            PARALLEL_TO, # ∥
            NOT_PARALLEL_TO, # ∦
            PROPORTION, # ∷
            GEOMETRIC_PROPORTION, # ∺
            HOMOTHETIC, # ∻
            REVERSED_TILDE, # ∽
            INVERTED_LAZY_S, # ∾
            NOT_TILDE, # ≁
            ASYMPTOTICALLY_EQUAL_TO, # ≃
            NOT_ASYMPTOTICALLY_EQUAL_TO, # ≄
            APPROXIMATELY_EQUAL_TO, # ≅
            APPROXIMATELY_BUT_NOT_ACTUALLY_EQUAL_TO, # ≆
            NEITHER_APPROXIMATELY_NOR_ACTUALLY_EQUAL_TO, # ≇
            ALMOST_EQUAL_TO, # ≈
            NOT_ALMOST_EQUAL_TO, # ≉
            ALMOST_EQUAL_OR_EQUAL_TO, # ≊
            TRIPLE_TILDE, # ≋
            ALL_EQUAL_TO, # ≌
            EQUIVALENT_TO, # ≍
            GEOMETRICALLY_EQUIVALENT_TO, # ≎
            APPROACHES_THE_LIMIT, # ≐
            GEOMETRICALLY_EQUAL_TO, # ≑
            APPROXIMATELY_EQUAL_TO_OR_THE_IMAGE_OF, # ≒
            IMAGE_OF_OR_APPROXIMATELY_EQUAL_TO, # ≓
            COLON_EQUALS, # ≔
            EQUALS_COLON, # ≕
            RING_IN_EQUAL_TO, # ≖
            RING_EQUAL_TO, # ≗
            CORRESPONDS_TO, # ≘
            ESTIMATES, # ≙
            EQUIANGULAR_TO, # ≚
            STAR_EQUALS, # ≛
            DELTA_EQUAL_TO, # ≜
            EQUAL_TO_BY_DEFINITION, # ≝
            MEASURED_BY, # ≞
            QUESTIONED_EQUAL_TO, # ≟
            STRICTLY_EQUIVALENT_TO, # ≣
            LESS_THAN_OVER_EQUAL_TO, # ≦
            GREATER_THAN_OVER_EQUAL_TO, # ≧
            LESS_THAN_BUT_NOT_EQUAL_TO, # ≨
            GREATER_THAN_BUT_NOT_EQUAL_TO, # ≩
            MUCH_LESS_THAN, # ≪
            MUCH_GREATER_THAN, # ≫
            BETWEEN, # ≬
            NOT_EQUIVALENT_TO, # ≭
            NOT_LESS_THAN, # ≮
            NOT_GREATER_THAN, # ≯
            NEITHER_LESS_THAN_NOR_EQUAL_TO, # ≰
            NEITHER_GREATER_THAN_NOR_EQUAL_TO, # ≱
            LESS_THAN_OR_EQUIVALENT_TO, # ≲
            GREATER_THAN_OR_EQUIVALENT_TO, # ≳
            NEITHER_LESS_THAN_NOR_EQUIVALENT_TO, # ≴
            NEITHER_GREATER_THAN_NOR_EQUIVALENT_TO, # ≵
            LESS_THAN_OR_GREATER_THAN, # ≶
            GREATER_THAN_OR_LESS_THAN, # ≷
            NEITHER_LESS_THAN_NOR_GREATER_THAN, # ≸
            NEITHER_GREATER_THAN_NOR_LESS_THAN, # ≹
            PRECEDES, # ≺
            SUCCEEDS, # ≻
            PRECEDES_OR_EQUAL_TO, # ≼
            SUCCEEDS_OR_EQUAL_TO, # ≽
            PRECEDES_OR_EQUIVALENT_TO, # ≾
            SUCCEEDS_OR_EQUIVALENT_TO, # ≿
            DOES_NOT_PRECEDE, # ⊀
            DOES_NOT_SUCCEED, # ⊁
            SUPERSET_OF, # ⊃
            NOT_A_SUPERSET_OF, # ⊅
            SUPERSET_OF_OR_EQUAL_TO, # ⊇
            NEITHER_A_SUPERSET_OF_NOR_EQUAL_TO, # ⊉
            SUPERSET_OF_WITH_NOT_EQUAL_TO, # ⊋
            SQUARE_IMAGE_OF, # ⊏
            SQUARE_ORIGINAL_OF, # ⊐
            SQUARE_IMAGE_OF_OR_EQUAL_TO, # ⊑
            SQUARE_ORIGINAL_OF_OR_EQUAL_TO, # ⊒
            CIRCLED_EQUALS, # ⊜
            FORCES, # ⊩
            DOES_NOT_PROVE, # ⊬
            DOES_NOT_FORCE, # ⊮
            PRECEDES_UNDER_RELATION, # ⊰
            SUCCEEDS_UNDER_RELATION, # ⊱
            NORMAL_SUBGROUP_OF, # ⊲
            CONTAINS_AS_NORMAL_SUBGROUP, # ⊳
            NORMAL_SUBGROUP_OF_OR_EQUAL_TO, # ⊴
            CONTAINS_AS_NORMAL_SUBGROUP_OR_EQUAL_TO, # ⊵
            ORIGINAL_OF, # ⊶
            IMAGE_OF, # ⊷
            REVERSED_TILDE_EQUALS, # ⋍
            DOUBLE_SUBSET, # ⋐
            DOUBLE_SUPERSET, # ⋑
            EQUAL_AND_PARALLEL_TO, # ⋕
            LESS_THAN_WITH_DOT, # ⋖
            GREATER_THAN_WITH_DOT, # ⋗
            VERY_MUCH_LESS_THAN, # ⋘
            VERY_MUCH_GREATER_THAN, # ⋙
            LESS_THAN_EQUAL_TO_OR_GREATER_THAN, # ⋚
            GREATER_THAN_EQUAL_TO_OR_LESS_THAN, # ⋛
            EQUAL_TO_OR_LESS_THAN, # ⋜
            EQUAL_TO_OR_GREATER_THAN, # ⋝
            EQUAL_TO_OR_PRECEDES, # ⋞
            EQUAL_TO_OR_SUCCEEDS, # ⋟
            DOES_NOT_PRECEDE_OR_EQUAL, # ⋠
            DOES_NOT_SUCCEED_OR_EQUAL, # ⋡
            NOT_SQUARE_IMAGE_OF_OR_EQUAL_TO, # ⋢
            NOT_SQUARE_ORIGINAL_OF_OR_EQUAL_TO, # ⋣
            SQUARE_IMAGE_OF_OR_NOT_EQUAL_TO, # ⋤
            SQUARE_ORIGINAL_OF_OR_NOT_EQUAL_TO, # ⋥
            LESS_THAN_BUT_NOT_EQUIVALENT_TO, # ⋦
            GREATER_THAN_BUT_NOT_EQUIVALENT_TO, # ⋧
            PRECEDES_BUT_NOT_EQUIVALENT_TO, # ⋨
            SUCCEEDS_BUT_NOT_EQUIVALENT_TO, # ⋩
            NOT_NORMAL_SUBGROUP_OF, # ⋪
            DOES_NOT_CONTAIN_AS_NORMAL_SUBGROUP, # ⋫
            NOT_NORMAL_SUBGROUP_OF_OR_EQUAL_TO, # ⋬
            DOES_NOT_CONTAIN_AS_NORMAL_SUBGROUP_OR_EQUAL, # ⋭
            ELEMENT_OF_WITH_LONG_HORIZONTAL_STROKE, # ⋲
            ELEMENT_OF_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE, # ⋳
            SMALL_ELEMENT_OF_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE, # ⋴
            ELEMENT_OF_WITH_DOT_ABOVE, # ⋵
            ELEMENT_OF_WITH_OVERBAR, # ⋶
            SMALL_ELEMENT_OF_WITH_OVERBAR, # ⋷
            ELEMENT_OF_WITH_UNDERBAR, # ⋸
            ELEMENT_OF_WITH_TWO_HORIZONTAL_STROKES, # ⋹
            CONTAINS_WITH_LONG_HORIZONTAL_STROKE, # ⋺
            CONTAINS_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE, # ⋻
            SMALL_CONTAINS_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE, # ⋼
            CONTAINS_WITH_OVERBAR, # ⋽
            SMALL_CONTAINS_WITH_OVERBAR, # ⋾
            Z_NOTATION_BAG_MEMBERSHIP, # ⋿
            REVERSE_SOLIDUS_PRECEDING_SUBSET, # ⟈
            SUPERSET_PRECEDING_SOLIDUS, # ⟉
            ELEMENT_OF_OPENING_UPWARDS, # ⟒
            CIRCLED_PARALLEL, # ⦷
            CIRCLED_LESS_THAN, # ⧀
            CIRCLED_GREATER_THAN, # ⧁
            INCREASES_AS, # ⧡
            EQUALS_SIGN_AND_SLANTED_PARALLEL, # ⧣
            EQUALS_SIGN_AND_SLANTED_PARALLEL_WITH_TILDE_ABOVE, # ⧤
            IDENTICAL_TO_AND_SLANTED_PARALLEL, # ⧥
            EQUALS_SIGN_WITH_DOT_BELOW, # ⩦
            IDENTICAL_WITH_DOT_ABOVE, # ⩧
            TILDE_OPERATOR_WITH_DOT_ABOVE, # ⩪
            TILDE_OPERATOR_WITH_RISING_DOTS, # ⩫
            SIMILAR_MINUS_SIMILAR, # ⩬
            CONGRUENT_WITH_DOT_ABOVE, # ⩭
            EQUALS_WITH_ASTERISK, # ⩮
            ALMOST_EQUAL_TO_WITH_CIRCUMFLEX_ACCENT, # ⩯
            APPROXIMATELY_EQUAL_OR_EQUAL_TO, # ⩰
            EQUALS_SIGN_ABOVE_PLUS_SIGN, # ⩱
            PLUS_SIGN_ABOVE_EQUALS_SIGN, # ⩲
            EQUALS_SIGN_ABOVE_TILDE_OPERATOR, # ⩳
            DOUBLE_COLON_EQUAL, # ⩴
            TWO_CONSECUTIVE_EQUALS_SIGNS, # ⩵
            THREE_CONSECUTIVE_EQUALS_SIGNS, # ⩶
            EQUALS_SIGN_WITH_TWO_DOTS_ABOVE_AND_TWO_DOTS_BELOW, # ⩷
            EQUIVALENT_WITH_FOUR_DOTS_ABOVE, # ⩸
            LESS_THAN_WITH_CIRCLE_INSIDE, # ⩹
            GREATER_THAN_WITH_CIRCLE_INSIDE, # ⩺
            LESS_THAN_WITH_QUESTION_MARK_ABOVE, # ⩻
            GREATER_THAN_WITH_QUESTION_MARK_ABOVE, # ⩼
            LESS_THAN_OR_SLANTED_EQUAL_TO, # ⩽
            GREATER_THAN_OR_SLANTED_EQUAL_TO, # ⩾
            LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_INSIDE, # ⩿
            GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_INSIDE, # ⪀
            LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE, # ⪁
            GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE, # ⪂
            LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE_RIGHT, # ⪃
            GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE_LEFT, # ⪄
            LESS_THAN_OR_APPROXIMATE, # ⪅
            GREATER_THAN_OR_APPROXIMATE, # ⪆
            LESS_THAN_AND_SINGLE_LINE_NOT_EQUAL_TO, # ⪇
            GREATER_THAN_AND_SINGLE_LINE_NOT_EQUAL_TO, # ⪈
            LESS_THAN_AND_NOT_APPROXIMATE, # ⪉
            GREATER_THAN_AND_NOT_APPROXIMATE, # ⪊
            LESS_THAN_ABOVE_DOUBLE_LINE_EQUAL_ABOVE_GREATER_THAN, # ⪋
            GREATER_THAN_ABOVE_DOUBLE_LINE_EQUAL_ABOVE_LESS_THAN, # ⪌
            LESS_THAN_ABOVE_SIMILAR_OR_EQUAL, # ⪍
            GREATER_THAN_ABOVE_SIMILAR_OR_EQUAL, # ⪎
            LESS_THAN_ABOVE_SIMILAR_ABOVE_GREATER_THAN, # ⪏
            GREATER_THAN_ABOVE_SIMILAR_ABOVE_LESS_THAN, # ⪐
            LESS_THAN_ABOVE_GREATER_THAN_ABOVE_DOUBLE_LINE_EQUAL, # ⪑
            GREATER_THAN_ABOVE_LESS_THAN_ABOVE_DOUBLE_LINE_EQUAL, # ⪒
            LESS_THAN_ABOVE_SLANTED_EQUAL_ABOVE_GREATER_THAN_ABOVE_SLANTED_EQUAL, # ⪓
            GREATER_THAN_ABOVE_SLANTED_EQUAL_ABOVE_LESS_THAN_ABOVE_SLANTED_EQUAL, # ⪔
            SLANTED_EQUAL_TO_OR_LESS_THAN, # ⪕
            SLANTED_EQUAL_TO_OR_GREATER_THAN, # ⪖
            SLANTED_EQUAL_TO_OR_LESS_THAN_WITH_DOT_INSIDE, # ⪗
            SLANTED_EQUAL_TO_OR_GREATER_THAN_WITH_DOT_INSIDE, # ⪘
            DOUBLE_LINE_EQUAL_TO_OR_LESS_THAN, # ⪙
            DOUBLE_LINE_EQUAL_TO_OR_GREATER_THAN, # ⪚
            DOUBLE_LINE_SLANTED_EQUAL_TO_OR_LESS_THAN, # ⪛
            DOUBLE_LINE_SLANTED_EQUAL_TO_OR_GREATER_THAN, # ⪜
            SIMILAR_OR_LESS_THAN, # ⪝
            SIMILAR_OR_GREATER_THAN, # ⪞
            SIMILAR_ABOVE_LESS_THAN_ABOVE_EQUALS_SIGN, # ⪟
            SIMILAR_ABOVE_GREATER_THAN_ABOVE_EQUALS_SIGN, # ⪠
            DOUBLE_NESTED_LESS_THAN, # ⪡
            DOUBLE_NESTED_GREATER_THAN, # ⪢
            DOUBLE_NESTED_LESS_THAN_WITH_UNDERBAR, # ⪣
            GREATER_THAN_OVERLAPPING_LESS_THAN, # ⪤
            GREATER_THAN_BESIDE_LESS_THAN, # ⪥
            LESS_THAN_CLOSED_BY_CURVE, # ⪦
            GREATER_THAN_CLOSED_BY_CURVE, # ⪧
            LESS_THAN_CLOSED_BY_CURVE_ABOVE_SLANTED_EQUAL, # ⪨
            GREATER_THAN_CLOSED_BY_CURVE_ABOVE_SLANTED_EQUAL, # ⪩
            SMALLER_THAN, # ⪪
            LARGER_THAN, # ⪫
            SMALLER_THAN_OR_EQUAL_TO, # ⪬
            LARGER_THAN_OR_EQUAL_TO, # ⪭
            EQUALS_SIGN_WITH_BUMPY_ABOVE, # ⪮
            PRECEDES_ABOVE_SINGLE_LINE_EQUALS_SIGN, # ⪯
            SUCCEEDS_ABOVE_SINGLE_LINE_EQUALS_SIGN, # ⪰
            PRECEDES_ABOVE_SINGLE_LINE_NOT_EQUAL_TO, # ⪱
            SUCCEEDS_ABOVE_SINGLE_LINE_NOT_EQUAL_TO, # ⪲
            PRECEDES_ABOVE_EQUALS_SIGN, # ⪳
            SUCCEEDS_ABOVE_EQUALS_SIGN, # ⪴
            PRECEDES_ABOVE_NOT_EQUAL_TO, # ⪵
            SUCCEEDS_ABOVE_NOT_EQUAL_TO, # ⪶
            PRECEDES_ABOVE_ALMOST_EQUAL_TO, # ⪷
            SUCCEEDS_ABOVE_ALMOST_EQUAL_TO, # ⪸
            PRECEDES_ABOVE_NOT_ALMOST_EQUAL_TO, # ⪹
            SUCCEEDS_ABOVE_NOT_ALMOST_EQUAL_TO, # ⪺
            DOUBLE_PRECEDES, # ⪻
            DOUBLE_SUCCEEDS, # ⪼
            SUBSET_WITH_DOT, # ⪽
            SUPERSET_WITH_DOT, # ⪾
            SUBSET_WITH_PLUS_SIGN_BELOW, # ⪿
            SUPERSET_WITH_PLUS_SIGN_BELOW, # ⫀
            SUBSET_WITH_MULTIPLICATION_SIGN_BELOW, # ⫁
            SUPERSET_WITH_MULTIPLICATION_SIGN_BELOW, # ⫂
            SUBSET_OF_OR_EQUAL_TO_WITH_DOT_ABOVE, # ⫃
            SUPERSET_OF_OR_EQUAL_TO_WITH_DOT_ABOVE, # ⫄
            SUBSET_OF_ABOVE_EQUALS_SIGN, # ⫅
            SUPERSET_OF_ABOVE_EQUALS_SIGN, # ⫆
            SUBSET_OF_ABOVE_TILDE_OPERATOR, # ⫇
            SUPERSET_OF_ABOVE_TILDE_OPERATOR, # ⫈
            SUBSET_OF_ABOVE_ALMOST_EQUAL_TO, # ⫉
            SUPERSET_OF_ABOVE_ALMOST_EQUAL_TO, # ⫊
            SUBSET_OF_ABOVE_NOT_EQUAL_TO, # ⫋
            SUPERSET_OF_ABOVE_NOT_EQUAL_TO, # ⫌
            SQUARE_LEFT_OPEN_BOX_OPERATOR, # ⫍
            SQUARE_RIGHT_OPEN_BOX_OPERATOR, # ⫎
            CLOSED_SUBSET, # ⫏
            CLOSED_SUPERSET, # ⫐
            CLOSED_SUBSET_OR_EQUAL_TO, # ⫑
            CLOSED_SUPERSET_OR_EQUAL_TO, # ⫒
            SUBSET_ABOVE_SUPERSET, # ⫓
            SUPERSET_ABOVE_SUBSET, # ⫔
            SUBSET_ABOVE_SUBSET, # ⫕
            SUPERSET_ABOVE_SUPERSET, # ⫖
            SUPERSET_BESIDE_SUBSET, # ⫗
            SUPERSET_BESIDE_AND_JOINED_BY_DASH_WITH_SUBSET, # ⫘
            ELEMENT_OF_OPENING_DOWNWARDS, # ⫙
            TRIPLE_NESTED_LESS_THAN, # ⫷
            TRIPLE_NESTED_GREATER_THAN, # ⫸
            DOUBLE_LINE_SLANTED_LESS_THAN_OR_EQUAL_TO, # ⫹
            DOUBLE_LINE_SLANTED_GREATER_THAN_OR_EQUAL_TO, # ⫺
            RIGHT_TACK, # ⊢
            LEFT_TACK, # ⊣
            DOUBLE_DOWN_TACK, # ⫪
            DOUBLE_UP_TACK, # ⫫
            PERP, # ⟂
        end_comparison,

        # Level 7
        begin_pipe,
            LPIPE, # <|
            RPIPE, # |>
        end_pipe,

        # Level 8
        begin_colon,
            COLON, # :
            DDOT, # ..
            LDOTS, # …
            TRICOLON, # ⁝
            VDOTS, # ⋮
            DDOTS, # ⋱
            ADOTS, # ⋰
            CDOTS, # ⋯
        end_colon,

        # Level 9
        begin_plus,
            EX_OR, # $
            PLUS, # +
            MINUS, # -
            PLUSPLUS, # ++
            CIRCLED_PLUS, # ⊕
            CIRCLED_MINUS, # ⊖
            SQUARED_PLUS, # ⊞
            SQUARED_MINUS, # ⊟
            OR, # |
            UNION, # ∪
            LOGICAL_OR, # ∨
            SQUARE_CUP, # ⊔
            PLUS_MINUS_SIGN, # ±
            MINUS_OR_PLUS_SIGN, # ∓
            DOT_PLUS, # ∔
            DOT_MINUS, # ∸
            MINUS_TILDE, # ≂
            DIFFERENCE_BETWEEN, # ≏
            MULTISET_UNION, # ⊎
            XOR, # ⊻
            NOR, # ⊽
            CURLY_LOGICAL_OR, # ⋎
            DOUBLE_UNION, # ⋓
            DOUBLE_PLUS, # ⧺
            TRIPLE_PLUS, # ⧻
            TWO_LOGICAL_OR_OPERATOR, # ⨈
            PLUS_SIGN_WITH_SMALL_CIRCLE_ABOVE, # ⨢
            PLUS_SIGN_WITH_CIRCUMFLEX_ACCENT_ABOVE, # ⨣
            PLUS_SIGN_WITH_TILDE_ABOVE, # ⨤
            PLUS_SIGN_WITH_DOT_BELOW, # ⨥
            PLUS_SIGN_WITH_TILDE_BELOW, # ⨦
            PLUS_SIGN_WITH_SUBSCRIPT_TWO, # ⨧
            PLUS_SIGN_WITH_BLACK_TRIANGLE, # ⨨
            MINUS_SIGN_WITH_COMMA_ABOVE, # ⨩
            MINUS_SIGN_WITH_DOT_BELOW, # ⨪
            MINUS_SIGN_WITH_FALLING_DOTS, # ⨫
            MINUS_SIGN_WITH_RISING_DOTS, # ⨬
            PLUS_SIGN_IN_LEFT_HALF_CIRCLE, # ⨭
            PLUS_SIGN_IN_RIGHT_HALF_CIRCLE, # ⨮
            PLUS_SIGN_IN_TRIANGLE, # ⨹
            MINUS_SIGN_IN_TRIANGLE, # ⨺
            UNION_WITH_MINUS_SIGN, # ⩁
            UNION_WITH_OVERBAR, # ⩂
            UNION_WITH_LOGICAL_OR, # ⩅
            UNION_BESIDE_AND_JOINED_WITH_UNION, # ⩊
            CLOSED_UNION_WITH_SERIFS, # ⩌
            DOUBLE_SQUARE_UNION, # ⩏
            CLOSED_UNION_WITH_SERIFS_AND_SMASH_PRODUCT, # ⩐
            LOGICAL_OR_WITH_DOT_ABOVE, # ⩒
            DOUBLE_LOGICAL_OR, # ⩔
            TWO_INTERSECTING_LOGICAL_OR, # ⩖
            SLOPING_LARGE_OR, # ⩗
            LOGICAL_OR_WITH_MIDDLE_STEM, # ⩛
            LOGICAL_OR_WITH_HORIZONTAL_DASH, # ⩝
            SMALL_VEE_WITH_UNDERBAR, # ⩡
            LOGICAL_OR_WITH_DOUBLE_OVERBAR, # ⩢
            LOGICAL_OR_WITH_DOUBLE_UNDERBAR, # ⩣
            BROKEN_BAR, # ¦
        end_plus,

        # Level 10
        begin_bitshifts,
          LBITSHIFT, # <<
          RBITSHIFT, # >>
          UNSIGNED_BITSHIFT, # >>>
        end_bitshifts,

        # Level 11
        begin_times,
            STAR,  # *
            FWD_SLASH, # /
            DIVISION_SIGN, # ÷
            REM, # %
            UNICODE_DOT, # ⋅
            RING_OPERATOR, # ∘
            MULTIPLICATION_SIGN, # ×
            BACKSLASH, # \
            AND, # &
            INTERSECTION, # ∩
            LOGICAL_AND, # ∧
            CIRCLED_TIMES, # ⊗
            CIRCLED_DIVISION_SLASH, # ⊘
            CIRCLED_DOT_OPERATOR, # ⊙
            CIRCLED_RING_OPERATOR, # ⊚
            CIRCLED_ASTERISK_OPERATOR, # ⊛
            SQUARED_TIMES, # ⊠
            SQUARED_DOT_OPERATOR, # ⊡
            SQUARE_CAP, # ⊓
            ASTERISK_OPERATOR, # ∗
            BULLET_OPERATOR, # ∙
            DOES_NOT_DIVIDE, # ∤
            TURNED_AMPERSAND, # ⅋
            WREATH_PRODUCT, # ≀
            NAND, # ⊼
            DIAMOND_OPERATOR, # ⋄
            STAR_OPERATOR, # ⋆
            DIVISION_TIMES, # ⋇
            LEFT_NORMAL_FACTOR_SEMIDIRECT_PRODUCT, # ⋉
            RIGHT_NORMAL_FACTOR_SEMIDIRECT_PRODUCT, # ⋊
            LEFT_SEMIDIRECT_PRODUCT, # ⋋
            RIGHT_SEMIDIRECT_PRODUCT, # ⋌
            CURLY_LOGICAL_AND, # ⋏
            DOUBLE_INTERSECTION, # ⋒
            AND_WITH_DOT, # ⟑
            CIRCLED_REVERSE_SOLIDUS, # ⦸
            CIRCLED_ANTICLOCKWISE_ROTATED_DIVISION_SIGN, # ⦼
            CIRCLED_WHITE_BULLET, # ⦾
            CIRCLED_BULLET, # ⦿
            SOLIDUS_WITH_OVERBAR, # ⧶
            REVERSE_SOLIDUS_WITH_HORIZONTAL_STROKE, # ⧷
            TWO_LOGICAL_AND_OPERATOR, # ⨇
            MULTIPLICATION_SIGN_WITH_DOT_ABOVE, # ⨰
            MULTIPLICATION_SIGN_WITH_UNDERBAR, # ⨱
            SEMIDIRECT_PRODUCT_WITH_BOTTOM_CLOSED, # ⨲
            SMASH_PRODUCT, # ⨳
            MULTIPLICATION_SIGN_IN_LEFT_HALF_CIRCLE, # ⨴
            MULTIPLICATION_SIGN_IN_RIGHT_HALF_CIRCLE, # ⨵
            CIRCLED_MULTIPLICATION_SIGN_WITH_CIRCUMFLEX_ACCENT, # ⨶
            MULTIPLICATION_SIGN_IN_DOUBLE_CIRCLE, # ⨷
            CIRCLED_DIVISION_SIGN, # ⨸
            MULTIPLICATION_SIGN_IN_TRIANGLE, # ⨻
            INTERIOR_PRODUCT, # ⨼
            RIGHTHAND_INTERIOR_PRODUCT, # ⨽
            INTERSECTION_WITH_DOT, # ⩀
            INTERSECTION_WITH_OVERBAR, # ⩃
            INTERSECTION_WITH_LOGICAL_AND, # ⩄
            INTERSECTION_BESIDE_AND_JOINED_WITH_INTERSECTION, # ⩋
            CLOSED_INTERSECTION_WITH_SERIFS, # ⩍
            DOUBLE_SQUARE_INTERSECTION, # ⩎
            LOGICAL_AND_WITH_DOT_ABOVE, # ⩑
            DOUBLE_LOGICAL_AND, # ⩓
            TWO_INTERSECTING_LOGICAL_AND, # ⩕
            SLOPING_LARGE_AND, # ⩘
            LOGICAL_AND_WITH_MIDDLE_STEM, # ⩚
            LOGICAL_AND_WITH_HORIZONTAL_DASH, # ⩜
            LOGICAL_AND_WITH_DOUBLE_OVERBAR, # ⩞
            LOGICAL_AND_WITH_UNDERBAR, # ⩟
            LOGICAL_AND_WITH_DOUBLE_UNDERBAR, # ⩠
            TRANSVERSAL_INTERSECTION, # ⫛
            MULTISET_MULTIPLICATION, # ⊍
            WHITE_RIGHT_POINTING_TRIANGLE, # ▷
            JOIN, # ⨝
            LEFT_OUTER_JOIN, # ⟕
            RIGHT_OUTER_JOIN, # ⟖
            FULL_OUTER_JOIN, # ⟗
            NOT_SLASH, # ⌿
            BB_SEMI, # ⨟
        end_times,

        # Level 12
        begin_rational,
            FWDFWD_SLASH, # //
        end_rational,

        # Level 13
        begin_power,
            CIRCUMFLEX_ACCENT, # ^
            UPWARDS_ARROW, # ↑
            DOWNWARDS_ARROW, # ↓
            DOWNWARDS_ARROW_LEFTWARDS_OF_UPWARDS_ARROW, # ⇵
            UPWARDS_QUADRUPLE_ARROW, # ⟰
            DOWNWARDS_QUADRUPLE_ARROW, # ⟱
            DOWNWARDS_ARROW_WITH_HORIZONTAL_STROKE, # ⤈
            UPWARDS_ARROW_WITH_HORIZONTAL_STROKE, # ⤉
            UPWARDS_TRIPLE_ARROW, # ⤊
            DOWNWARDS_TRIPLE_ARROW, # ⤋
            UPWARDS_ARROW_TO_BAR, # ⤒
            DOWNWARDS_ARROW_TO_BAR, # ⤓
            UPWARDS_TWO_HEADED_ARROW_FROM_SMALL_CIRCLE, # ⥉
            UP_BARB_RIGHT_DOWN_BARB_LEFT_HARPOON, # ⥌
            UP_BARB_LEFT_DOWN_BARB_RIGHT_HARPOON, # ⥍
            UP_BARB_RIGHT_DOWN_BARB_RIGHT_HARPOON, # ⥏
            UP_BARB_LEFT_DOWN_BARB_LEFT_HARPOON, # ⥑
            UPWARDS_HARPOON_WITH_BARB_RIGHT_TO_BAR, # ⥔
            DOWNWARDS_HARPOON_WITH_BARB_RIGHT_TO_BAR, # ⥕
            UPWARDS_HARPOON_WITH_BARB_LEFT_TO_BAR, # ⥘
            DOWNWARDS_HARPOON_WITH_BARB_LEFT_TO_BAR, # ⥙
            UPWARDS_HARPOON_WITH_BARB_RIGHT_FROM_BAR, # ⥜
            DOWNWARDS_HARPOON_WITH_BARB_RIGHT_FROM_BAR, # ⥝
            UPWARDS_HARPOON_WITH_BARB_LEFT_FROM_BAR, # ⥠
            DOWNWARDS_HARPOON_WITH_BARB_LEFT_FROM_BAR, # ⥡
            UPWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_UPWARDS_HARPOON_WITH_BARB_RIGHT, # ⥣
            DOWNWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_DOWNWARDS_HARPOON_WITH_BARB_RIGHT, # ⥥
            UPWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_DOWNWARDS_HARPOON_WITH_BARB_RIGHT, # ⥮
            DOWNWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_UPWARDS_HARPOON_WITH_BARB_RIGHT, # ⥯
            HALFWIDTH_UPWARDS_ARROW, # ￪
            HALFWIDTH_DOWNWARDS_ARROW, # ￬
        end_power,

        # Level 14
        begin_decl,
            DECLARATION, # ::
        end_decl,

        # Level 15
        begin_where,
            WHERE,
        end_where,

        # Level 16
        begin_dot,
            DOT,# .
        end_dot,

        NOT, # !
        PRIME, # '
        TRANSPOSE, # .'
        ANON_FUNC, # ->

        begin_unicode_ops,
            NOT_SIGN, # ¬
            SQUARE_ROOT, # √
            CUBE_ROOT, # ∛
            QUAD_ROOT, # ∜
        end_unicode_ops,
    end_ops,

    # Kinds emitted by the parser. There's two types of these:
    # 1. Implied tokens which have a position but might have zero width in the
    #    source text.
    #
    # In some cases we want to generate parse tree nodes in a standard form,
    # but some of the leaf tokens are implied rather than existing in the
    # source text, or the lexed tokens need to be re-kinded to represent
    # special forms which only the parser can infer. These are "parser tokens".
    #
    # Some examples:
    #
    # Docstrings - the macro name is invisible
    #   "doc" foo() = 1   ==>  (macrocall (core @doc) . (= (call foo) 1))
    #
    # String macros - the macro name does not appear in the source text, so we
    # need a special kind of token to imply it.
    #
    # In these cases, we use some special kinds which can be emitted as zero
    # width tokens to keep the parse tree more uniform.
    begin_parser_tokens,
        TOMBSTONE,           # Empty placeholder for kind to be filled later

        # Macro names are modelled as a special kind of identifier because the
        # @ may not be attached to the macro name in the source (or may not be
        # associated with a token at all in the case of implied macro calls
        # like CORE_DOC_MACRO_NAME)
        begin_macro_names,
            MACRO_NAME,                  # A macro name identifier
            STRING_MACRO_NAME,           # macname"some_str"
            CMD_MACRO_NAME,              # macname`some_str`
            DOT_MACRO_NAME,              # The macro name of @.
            CORE_DOC_MACRO_NAME,         # Core.@doc
            CORE_CMD_MACRO_NAME,         # Core.@cmd
            CORE_INT128_STR_MACRO_NAME,  # Core.@int128_str
            CORE_UINT128_STR_MACRO_NAME, # Core.@uint128_str
            CORE_BIG_STR_MACRO_NAME,     # Core.@big_str
        end_macro_names,
    end_parser_tokens,

    # 2. Nonterminals which are exposed in the AST, but where the surface
    #    syntax doesn't have a token corresponding to the node type.
    begin_syntax_kinds,
        BLOCK,
        CALL,
        COMPARISON,
        CURLY,
        INERT,           # QuoteNode; not quasiquote
        STRING_INTERP,   # "a $x"
        TOPLEVEL,
        TUPLE,
        REF,
        VECT,
        MACROCALL,
        KW,             # the = in f(a=1)
        PARAMETERS,     # the list after ; in f(; a=1)
        # Concatenation syntax
        BRACES,
        BRACESCAT,
        HCAT,
        VCAT,
        NCAT,
        TYPED_HCAT,
        TYPED_VCAT,
        TYPED_NCAT,
        ROW,
        NROW,
        # Comprehensions
        GENERATOR,
        FILTER,
        FLATTEN,
        COMPREHENSION,
        TYPED_COMPREHENSION,
    end_syntax_kinds,
)

const UNICODE_OPS = Dict{Char, Kind}(
'−' => MINUS,
'÷' => DIVISION_SIGN,
'¬' => NOT_SIGN,
'√' => SQUARE_ROOT,
'∛' => CUBE_ROOT,
'∜' => QUAD_ROOT,
'←' => LEFTWARDS_ARROW,
'→' => RIGHTWARDS_ARROW,
'↔' => LEFT_RIGHT_ARROW,
'↚' => LEFTWARDS_ARROW_WITH_STROKE,
'↛' => RIGHTWARDS_ARROW_WITH_STROKE,
'↞' => LEFTWARDS_TWO_HEADED_ARROW,
'↠' => RIGHTWARDS_TWO_HEADED_ARROW,
'↢' => LEFTWARDS_ARROW_WITH_TAIL,
'↣' => RIGHTWARDS_ARROW_WITH_TAIL,
'↤' => LEFTWARDS_ARROW_FROM_BAR,
'↦' => RIGHTWARDS_ARROW_FROM_BAR,
'↮' => LEFT_RIGHT_ARROW_WITH_STROKE,
'⇎' => LEFT_RIGHT_DOUBLE_ARROW_WITH_STROKE,
'⇍' => LEFTWARDS_DOUBLE_ARROW_WITH_STROKE,
'⇏' => RIGHTWARDS_DOUBLE_ARROW_WITH_STROKE,
'⇐' => LEFTWARDS_DOUBLE_ARROW,
'⇒' => RIGHTWARDS_DOUBLE_ARROW,
'⇔' => LEFT_RIGHT_DOUBLE_ARROW,
'⇴' => RIGHT_ARROW_WITH_SMALL_CIRCLE,
'⇶' => THREE_RIGHTWARDS_ARROWS,
'⇷' => LEFTWARDS_ARROW_WITH_VERTICAL_STROKE,
'⇸' => RIGHTWARDS_ARROW_WITH_VERTICAL_STROKE,
'⇹' => LEFT_RIGHT_ARROW_WITH_VERTICAL_STROKE,
'⇺' => LEFTWARDS_ARROW_WITH_DOUBLE_VERTICAL_STROKE,
'⇻' => RIGHTWARDS_ARROW_WITH_DOUBLE_VERTICAL_STROKE,
'⇼' => LEFT_RIGHT_ARROW_WITH_DOUBLE_VERTICAL_STROKE,
'⇽' => LEFTWARDS_OPEN_HEADED_ARROW,
'⇾' => RIGHTWARDS_OPEN_HEADED_ARROW,
'⇿' => LEFT_RIGHT_OPEN_HEADED_ARROW,
'⟵' => LONG_LEFTWARDS_ARROW,
'⟶' => LONG_RIGHTWARDS_ARROW,
'⟷' => LONG_LEFT_RIGHT_ARROW,
'⟹' => LONG_RIGHTWARDS_DOUBLE_ARROW,
'⟺' => LONG_LEFT_RIGHT_DOUBLE_ARROW,
'⟻' => LONG_LEFTWARDS_ARROW_FROM_BAR,
'⟼' => LONG_RIGHTWARDS_ARROW_FROM_BAR,
'⟽' => LONG_LEFTWARDS_DOUBLE_ARROW_FROM_BAR,
'⟾' => LONG_RIGHTWARDS_DOUBLE_ARROW_FROM_BAR,
'⟿' => LONG_RIGHTWARDS_SQUIGGLE_ARROW,
'⤀' => RIGHTWARDS_TWO_HEADED_ARROW_WITH_VERTICAL_STROKE,
'⤁' => RIGHTWARDS_TWO_HEADED_ARROW_WITH_DOUBLE_VERTICAL_STROKE,
'⤂' => LEFTWARDS_DOUBLE_ARROW_WITH_VERTICAL_STROKE,
'⤃' => RIGHTWARDS_DOUBLE_ARROW_WITH_VERTICAL_STROKE,
'⤄' => LEFT_RIGHT_DOUBLE_ARROW_WITH_VERTICAL_STROKE,
'⤅' => RIGHTWARDS_TWO_HEADED_ARROW_FROM_BAR,
'⤆' => LEFTWARDS_DOUBLE_ARROW_FROM_BAR,
'⤇' => RIGHTWARDS_DOUBLE_ARROW_FROM_BAR,
'⤌' => LEFTWARDS_DOUBLE_DASH_ARROW,
'⤍' => RIGHTWARDS_DOUBLE_DASH_ARROW,
'⤎' => LEFTWARDS_TRIPLE_DASH_ARROW,
'⤏' => RIGHTWARDS_TRIPLE_DASH_ARROW,
'⤐' => RIGHTWARDS_TWO_HEADED_TRIPLE_DASH_ARROW,
'⤑' => RIGHTWARDS_ARROW_WITH_DOTTED_STEM,
'⤔' => RIGHTWARDS_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE,
'⤕' => RIGHTWARDS_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE,
'⤖' => RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL,
'⤗' => RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE,
'⤘' => RIGHTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE,
'⤝' => LEFTWARDS_ARROW_TO_BLACK_DIAMOND,
'⤞' => RIGHTWARDS_ARROW_TO_BLACK_DIAMOND,
'⤟' => LEFTWARDS_ARROW_FROM_BAR_TO_BLACK_DIAMOND,
'⤠' => RIGHTWARDS_ARROW_FROM_BAR_TO_BLACK_DIAMOND,
'⥄' => SHORT_RIGHTWARDS_ARROW_ABOVE_LEFTWARDS_ARROW,
'⥅' => RIGHTWARDS_ARROW_WITH_PLUS_BELOW,
'⥆' => LEFTWARDS_ARROW_WITH_PLUS_BELOW,
'⥇' => RIGHTWARDS_ARROW_THROUGH_X,
'⥈' => LEFT_RIGHT_ARROW_THROUGH_SMALL_CIRCLE,
'⥊' => LEFT_BARB_UP_RIGHT_BARB_DOWN_HARPOON,
'⥋' => LEFT_BARB_DOWN_RIGHT_BARB_UP_HARPOON,
'⥎' => LEFT_BARB_UP_RIGHT_BARB_UP_HARPOON,
'⥐' => LEFT_BARB_DOWN_RIGHT_BARB_DOWN_HARPOON,
'⥒' => LEFTWARDS_HARPOON_WITH_BARB_UP_TO_BAR,
'⥓' => RIGHTWARDS_HARPOON_WITH_BARB_UP_TO_BAR,
'⥖' => LEFTWARDS_HARPOON_WITH_BARB_DOWN_TO_BAR,
'⥗' => RIGHTWARDS_HARPOON_WITH_BARB_DOWN_TO_BAR,
'⥚' => LEFTWARDS_HARPOON_WITH_BARB_UP_FROM_BAR,
'⥛' => RIGHTWARDS_HARPOON_WITH_BARB_UP_FROM_BAR,
'⥞' => LEFTWARDS_HARPOON_WITH_BARB_DOWN_FROM_BAR,
'⥟' => RIGHTWARDS_HARPOON_WITH_BARB_DOWN_FROM_BAR,
'⥢' => LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_DOWN,
'⥤' => RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_DOWN,
'⥦' => LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_UP,
'⥧' => LEFTWARDS_HARPOON_WITH_BARB_DOWN_ABOVE_RIGHTWARDS_HARPOON_WITH_BARB_DOWN,
'⥨' => RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_UP,
'⥩' => RIGHTWARDS_HARPOON_WITH_BARB_DOWN_ABOVE_LEFTWARDS_HARPOON_WITH_BARB_DOWN,
'⥪' => LEFTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LONG_DASH,
'⥫' => LEFTWARDS_HARPOON_WITH_BARB_DOWN_BELOW_LONG_DASH,
'⥬' => RIGHTWARDS_HARPOON_WITH_BARB_UP_ABOVE_LONG_DASH,
'⥭' => RIGHTWARDS_HARPOON_WITH_BARB_DOWN_BELOW_LONG_DASH,
'⥰' => RIGHT_DOUBLE_ARROW_WITH_ROUNDED_HEAD,
'⧴' => RULE_DELAYED,
'⬱' => THREE_LEFTWARDS_ARROWS,
'⬰' => LEFT_ARROW_WITH_SMALL_CIRCLE,
'⬲' => LEFT_ARROW_WITH_CIRCLED_PLUS,
'⬳' => LONG_LEFTWARDS_SQUIGGLE_ARROW,
'⬴' => LEFTWARDS_TWO_HEADED_ARROW_WITH_VERTICAL_STROKE,
'⬵' => LEFTWARDS_TWO_HEADED_ARROW_WITH_DOUBLE_VERTICAL_STROKE,
'⬶' => LEFTWARDS_TWO_HEADED_ARROW_FROM_BAR,
'⬷' => LEFTWARDS_TWO_HEADED_TRIPLE_DASH_ARROW,
'⬸' => LEFTWARDS_ARROW_WITH_DOTTED_STEM,
'⬹' => LEFTWARDS_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE,
'⬺' => LEFTWARDS_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE,
'⬻' => LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL,
'⬼' => LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_VERTICAL_STROKE,
'⬽' => LEFTWARDS_TWO_HEADED_ARROW_WITH_TAIL_WITH_DOUBLE_VERTICAL_STROKE,
'⬾' => LEFTWARDS_ARROW_THROUGH_X,
'⬿' => WAVE_ARROW_POINTING_DIRECTLY_LEFT,
'⭀' => EQUALS_SIGN_ABOVE_LEFTWARDS_ARROW,
'⭁' => REVERSE_TILDE_OPERATOR_ABOVE_LEFTWARDS_ARROW,
'⭂' => LEFTWARDS_ARROW_ABOVE_REVERSE_ALMOST_EQUAL_TO,
'⭃' => RIGHTWARDS_ARROW_THROUGH_GREATER_THAN,
'⭄' => RIGHTWARDS_ARROW_THROUGH_SUPERSET,
'⭇' => REVERSE_TILDE_OPERATOR_ABOVE_RIGHTWARDS_ARROW,
'⭈' => RIGHTWARDS_ARROW_ABOVE_REVERSE_ALMOST_EQUAL_TO,
'⭉' => TILDE_OPERATOR_ABOVE_LEFTWARDS_ARROW,
'⭊' => LEFTWARDS_ARROW_ABOVE_ALMOST_EQUAL_TO,
'⭋' => LEFTWARDS_ARROW_ABOVE_REVERSE_TILDE_OPERATOR,
'⭌' => RIGHTWARDS_ARROW_ABOVE_REVERSE_TILDE_OPERATOR,
'￩' => HALFWIDTH_LEFTWARDS_ARROW,
'￫' => HALFWIDTH_RIGHTWARDS_ARROW,
'≥' => GREATER_THAN_OR_EQUAL_TO,
'≤' => LESS_THAN_OR_EQUAL_TO,
'≡' => IDENTICAL_TO,
'≠' => NOT_EQUAL_TO,
'≢' => NOT_IDENTICAL_TO,
'∈' => ELEMENT_OF,
'∉' => NOT_AN_ELEMENT_OF,
'∋' => CONTAINS_AS_MEMBER,
'∌' => DOES_NOT_CONTAIN_AS_MEMBER,
'⊆' => SUBSET_OF_OR_EQUAL_TO,
'⊈' => NEITHER_A_SUBSET_OF_NOR_EQUAL_TO,
'⊂' => SUBSET_OF,
'⊄' => NOT_A_SUBSET_OF,
'⊊' => SUBSET_OF_WITH_NOT_EQUAL_TO,
'∝' => PROPORTIONAL_TO,
'∊' => SMALL_ELEMENT_OF,
'∍' => SMALL_CONTAINS_AS_MEMBER,
'∥' => PARALLEL_TO,
'∦' => NOT_PARALLEL_TO,
'∷' => PROPORTION,
'∺' => GEOMETRIC_PROPORTION,
'∻' => HOMOTHETIC,
'∽' => REVERSED_TILDE,
'∾' => INVERTED_LAZY_S,
'≁' => NOT_TILDE,
'≃' => ASYMPTOTICALLY_EQUAL_TO,
'≄' => NOT_ASYMPTOTICALLY_EQUAL_TO,
'≅' => APPROXIMATELY_EQUAL_TO,
'≆' => APPROXIMATELY_BUT_NOT_ACTUALLY_EQUAL_TO,
'≇' => NEITHER_APPROXIMATELY_NOR_ACTUALLY_EQUAL_TO,
'≈' => ALMOST_EQUAL_TO,
'≉' => NOT_ALMOST_EQUAL_TO,
'≊' => ALMOST_EQUAL_OR_EQUAL_TO,
'≋' => TRIPLE_TILDE,
'≌' => ALL_EQUAL_TO,
'≍' => EQUIVALENT_TO,
'≎' => GEOMETRICALLY_EQUIVALENT_TO,
'≐' => APPROACHES_THE_LIMIT,
'≑' => GEOMETRICALLY_EQUAL_TO,
'≒' => APPROXIMATELY_EQUAL_TO_OR_THE_IMAGE_OF,
'≓' => IMAGE_OF_OR_APPROXIMATELY_EQUAL_TO,
'≔' => COLON_EQUALS,
'≕' => EQUALS_COLON,
'≖' => RING_IN_EQUAL_TO,
'≗' => RING_EQUAL_TO,
'≘' => CORRESPONDS_TO,
'≙' => ESTIMATES,
'≚' => EQUIANGULAR_TO,
'≛' => STAR_EQUALS,
'≜' => DELTA_EQUAL_TO,
'≝' => EQUAL_TO_BY_DEFINITION,
'≞' => MEASURED_BY,
'≟' => QUESTIONED_EQUAL_TO,
'≣' => STRICTLY_EQUIVALENT_TO,
'≦' => LESS_THAN_OVER_EQUAL_TO,
'≧' => GREATER_THAN_OVER_EQUAL_TO,
'≨' => LESS_THAN_BUT_NOT_EQUAL_TO,
'≩' => GREATER_THAN_BUT_NOT_EQUAL_TO,
'≪' => MUCH_LESS_THAN,
'≫' => MUCH_GREATER_THAN,
'≬' => BETWEEN,
'≭' => NOT_EQUIVALENT_TO,
'≮' => NOT_LESS_THAN,
'≯' => NOT_GREATER_THAN,
'≰' => NEITHER_LESS_THAN_NOR_EQUAL_TO,
'≱' => NEITHER_GREATER_THAN_NOR_EQUAL_TO,
'≲' => LESS_THAN_OR_EQUIVALENT_TO,
'≳' => GREATER_THAN_OR_EQUIVALENT_TO,
'≴' => NEITHER_LESS_THAN_NOR_EQUIVALENT_TO,
'≵' => NEITHER_GREATER_THAN_NOR_EQUIVALENT_TO,
'≶' => LESS_THAN_OR_GREATER_THAN,
'≷' => GREATER_THAN_OR_LESS_THAN,
'≸' => NEITHER_LESS_THAN_NOR_GREATER_THAN,
'≹' => NEITHER_GREATER_THAN_NOR_LESS_THAN,
'≺' => PRECEDES,
'≻' => SUCCEEDS,
'≼' => PRECEDES_OR_EQUAL_TO,
'≽' => SUCCEEDS_OR_EQUAL_TO,
'≾' => PRECEDES_OR_EQUIVALENT_TO,
'≿' => SUCCEEDS_OR_EQUIVALENT_TO,
'⊀' => DOES_NOT_PRECEDE,
'⊁' => DOES_NOT_SUCCEED,
'⊃' => SUPERSET_OF,
'⊅' => NOT_A_SUPERSET_OF,
'⊇' => SUPERSET_OF_OR_EQUAL_TO,
'⊉' => NEITHER_A_SUPERSET_OF_NOR_EQUAL_TO,
'⊋' => SUPERSET_OF_WITH_NOT_EQUAL_TO,
'⊏' => SQUARE_IMAGE_OF,
'⊐' => SQUARE_ORIGINAL_OF,
'⊑' => SQUARE_IMAGE_OF_OR_EQUAL_TO,
'⊒' => SQUARE_ORIGINAL_OF_OR_EQUAL_TO,
'⊜' => CIRCLED_EQUALS,
'⊩' => FORCES,
'⊬' => DOES_NOT_PROVE,
'⊮' => DOES_NOT_FORCE,
'⊰' => PRECEDES_UNDER_RELATION,
'⊱' => SUCCEEDS_UNDER_RELATION,
'⊲' => NORMAL_SUBGROUP_OF,
'⊳' => CONTAINS_AS_NORMAL_SUBGROUP,
'⊴' => NORMAL_SUBGROUP_OF_OR_EQUAL_TO,
'⊵' => CONTAINS_AS_NORMAL_SUBGROUP_OR_EQUAL_TO,
'⊶' => ORIGINAL_OF,
'⊷' => IMAGE_OF,
'⋍' => REVERSED_TILDE_EQUALS,
'⋐' => DOUBLE_SUBSET,
'⋑' => DOUBLE_SUPERSET,
'⋕' => EQUAL_AND_PARALLEL_TO,
'⋖' => LESS_THAN_WITH_DOT,
'⋗' => GREATER_THAN_WITH_DOT,
'⋘' => VERY_MUCH_LESS_THAN,
'⋙' => VERY_MUCH_GREATER_THAN,
'⋚' => LESS_THAN_EQUAL_TO_OR_GREATER_THAN,
'⋛' => GREATER_THAN_EQUAL_TO_OR_LESS_THAN,
'⋜' => EQUAL_TO_OR_LESS_THAN,
'⋝' => EQUAL_TO_OR_GREATER_THAN,
'⋞' => EQUAL_TO_OR_PRECEDES,
'⋟' => EQUAL_TO_OR_SUCCEEDS,
'⋠' => DOES_NOT_PRECEDE_OR_EQUAL,
'⋡' => DOES_NOT_SUCCEED_OR_EQUAL,
'⋢' => NOT_SQUARE_IMAGE_OF_OR_EQUAL_TO,
'⋣' => NOT_SQUARE_ORIGINAL_OF_OR_EQUAL_TO,
'⋤' => SQUARE_IMAGE_OF_OR_NOT_EQUAL_TO,
'⋥' => SQUARE_ORIGINAL_OF_OR_NOT_EQUAL_TO,
'⋦' => LESS_THAN_BUT_NOT_EQUIVALENT_TO,
'⋧' => GREATER_THAN_BUT_NOT_EQUIVALENT_TO,
'⋨' => PRECEDES_BUT_NOT_EQUIVALENT_TO,
'⋩' => SUCCEEDS_BUT_NOT_EQUIVALENT_TO,
'⋪' => NOT_NORMAL_SUBGROUP_OF,
'⋫' => DOES_NOT_CONTAIN_AS_NORMAL_SUBGROUP,
'⋬' => NOT_NORMAL_SUBGROUP_OF_OR_EQUAL_TO,
'⋭' => DOES_NOT_CONTAIN_AS_NORMAL_SUBGROUP_OR_EQUAL,
'⋲' => ELEMENT_OF_WITH_LONG_HORIZONTAL_STROKE,
'⋳' => ELEMENT_OF_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE,
'⋴' => SMALL_ELEMENT_OF_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE,
'⋵' => ELEMENT_OF_WITH_DOT_ABOVE,
'⋶' => ELEMENT_OF_WITH_OVERBAR,
'⋷' => SMALL_ELEMENT_OF_WITH_OVERBAR,
'⋸' => ELEMENT_OF_WITH_UNDERBAR,
'⋹' => ELEMENT_OF_WITH_TWO_HORIZONTAL_STROKES,
'⋺' => CONTAINS_WITH_LONG_HORIZONTAL_STROKE,
'⋻' => CONTAINS_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE,
'⋼' => SMALL_CONTAINS_WITH_VERTICAL_BAR_AT_END_OF_HORIZONTAL_STROKE,
'⋽' => CONTAINS_WITH_OVERBAR,
'⋾' => SMALL_CONTAINS_WITH_OVERBAR,
'⋿' => Z_NOTATION_BAG_MEMBERSHIP,
'⟈' => REVERSE_SOLIDUS_PRECEDING_SUBSET,
'⟉' => SUPERSET_PRECEDING_SOLIDUS,
'⟒' => ELEMENT_OF_OPENING_UPWARDS,
'⦷' => CIRCLED_PARALLEL,
'⧀' => CIRCLED_LESS_THAN,
'⧁' => CIRCLED_GREATER_THAN,
'⧡' => INCREASES_AS,
'⧣' => EQUALS_SIGN_AND_SLANTED_PARALLEL,
'⧤' => EQUALS_SIGN_AND_SLANTED_PARALLEL_WITH_TILDE_ABOVE,
'⧥' => IDENTICAL_TO_AND_SLANTED_PARALLEL,
'⩦' => EQUALS_SIGN_WITH_DOT_BELOW,
'⩧' => IDENTICAL_WITH_DOT_ABOVE,
'⩪' => TILDE_OPERATOR_WITH_DOT_ABOVE,
'⩫' => TILDE_OPERATOR_WITH_RISING_DOTS,
'⩬' => SIMILAR_MINUS_SIMILAR,
'⩭' => CONGRUENT_WITH_DOT_ABOVE,
'⩮' => EQUALS_WITH_ASTERISK,
'⩯' => ALMOST_EQUAL_TO_WITH_CIRCUMFLEX_ACCENT,
'⩰' => APPROXIMATELY_EQUAL_OR_EQUAL_TO,
'⩱' => EQUALS_SIGN_ABOVE_PLUS_SIGN,
'⩲' => PLUS_SIGN_ABOVE_EQUALS_SIGN,
'⩳' => EQUALS_SIGN_ABOVE_TILDE_OPERATOR,
'⩴' => DOUBLE_COLON_EQUAL,
'⩵' => TWO_CONSECUTIVE_EQUALS_SIGNS,
'⩶' => THREE_CONSECUTIVE_EQUALS_SIGNS,
'⩷' => EQUALS_SIGN_WITH_TWO_DOTS_ABOVE_AND_TWO_DOTS_BELOW,
'⩸' => EQUIVALENT_WITH_FOUR_DOTS_ABOVE,
'⩹' => LESS_THAN_WITH_CIRCLE_INSIDE,
'⩺' => GREATER_THAN_WITH_CIRCLE_INSIDE,
'⩻' => LESS_THAN_WITH_QUESTION_MARK_ABOVE,
'⩼' => GREATER_THAN_WITH_QUESTION_MARK_ABOVE,
'⩽' => LESS_THAN_OR_SLANTED_EQUAL_TO,
'⩾' => GREATER_THAN_OR_SLANTED_EQUAL_TO,
'⩿' => LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_INSIDE,
'⪀' => GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_INSIDE,
'⪁' => LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE,
'⪂' => GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE,
'⪃' => LESS_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE_RIGHT,
'⪄' => GREATER_THAN_OR_SLANTED_EQUAL_TO_WITH_DOT_ABOVE_LEFT,
'⪅' => LESS_THAN_OR_APPROXIMATE,
'⪆' => GREATER_THAN_OR_APPROXIMATE,
'⪇' => LESS_THAN_AND_SINGLE_LINE_NOT_EQUAL_TO,
'⪈' => GREATER_THAN_AND_SINGLE_LINE_NOT_EQUAL_TO,
'⪉' => LESS_THAN_AND_NOT_APPROXIMATE,
'⪊' => GREATER_THAN_AND_NOT_APPROXIMATE,
'⪋' => LESS_THAN_ABOVE_DOUBLE_LINE_EQUAL_ABOVE_GREATER_THAN,
'⪌' => GREATER_THAN_ABOVE_DOUBLE_LINE_EQUAL_ABOVE_LESS_THAN,
'⪍' => LESS_THAN_ABOVE_SIMILAR_OR_EQUAL,
'⪎' => GREATER_THAN_ABOVE_SIMILAR_OR_EQUAL,
'⪏' => LESS_THAN_ABOVE_SIMILAR_ABOVE_GREATER_THAN,
'⪐' => GREATER_THAN_ABOVE_SIMILAR_ABOVE_LESS_THAN,
'⪑' => LESS_THAN_ABOVE_GREATER_THAN_ABOVE_DOUBLE_LINE_EQUAL,
'⪒' => GREATER_THAN_ABOVE_LESS_THAN_ABOVE_DOUBLE_LINE_EQUAL,
'⪓' => LESS_THAN_ABOVE_SLANTED_EQUAL_ABOVE_GREATER_THAN_ABOVE_SLANTED_EQUAL,
'⪔' => GREATER_THAN_ABOVE_SLANTED_EQUAL_ABOVE_LESS_THAN_ABOVE_SLANTED_EQUAL,
'⪕' => SLANTED_EQUAL_TO_OR_LESS_THAN,
'⪖' => SLANTED_EQUAL_TO_OR_GREATER_THAN,
'⪗' => SLANTED_EQUAL_TO_OR_LESS_THAN_WITH_DOT_INSIDE,
'⪘' => SLANTED_EQUAL_TO_OR_GREATER_THAN_WITH_DOT_INSIDE,
'⪙' => DOUBLE_LINE_EQUAL_TO_OR_LESS_THAN,
'⪚' => DOUBLE_LINE_EQUAL_TO_OR_GREATER_THAN,
'⪛' => DOUBLE_LINE_SLANTED_EQUAL_TO_OR_LESS_THAN,
'⪜' => DOUBLE_LINE_SLANTED_EQUAL_TO_OR_GREATER_THAN,
'⪝' => SIMILAR_OR_LESS_THAN,
'⪞' => SIMILAR_OR_GREATER_THAN,
'⪟' => SIMILAR_ABOVE_LESS_THAN_ABOVE_EQUALS_SIGN,
'⪠' => SIMILAR_ABOVE_GREATER_THAN_ABOVE_EQUALS_SIGN,
'⪡' => DOUBLE_NESTED_LESS_THAN,
'⪢' => DOUBLE_NESTED_GREATER_THAN,
'⪣' => DOUBLE_NESTED_LESS_THAN_WITH_UNDERBAR,
'⪤' => GREATER_THAN_OVERLAPPING_LESS_THAN,
'⪥' => GREATER_THAN_BESIDE_LESS_THAN,
'⪦' => LESS_THAN_CLOSED_BY_CURVE,
'⪧' => GREATER_THAN_CLOSED_BY_CURVE,
'⪨' => LESS_THAN_CLOSED_BY_CURVE_ABOVE_SLANTED_EQUAL,
'⪩' => GREATER_THAN_CLOSED_BY_CURVE_ABOVE_SLANTED_EQUAL,
'⪪' => SMALLER_THAN,
'⪫' => LARGER_THAN,
'⪬' => SMALLER_THAN_OR_EQUAL_TO,
'⪭' => LARGER_THAN_OR_EQUAL_TO,
'⪮' => EQUALS_SIGN_WITH_BUMPY_ABOVE,
'⪯' => PRECEDES_ABOVE_SINGLE_LINE_EQUALS_SIGN,
'⪰' => SUCCEEDS_ABOVE_SINGLE_LINE_EQUALS_SIGN,
'⪱' => PRECEDES_ABOVE_SINGLE_LINE_NOT_EQUAL_TO,
'⪲' => SUCCEEDS_ABOVE_SINGLE_LINE_NOT_EQUAL_TO,
'⪳' => PRECEDES_ABOVE_EQUALS_SIGN,
'⪴' => SUCCEEDS_ABOVE_EQUALS_SIGN,
'⪵' => PRECEDES_ABOVE_NOT_EQUAL_TO,
'⪶' => SUCCEEDS_ABOVE_NOT_EQUAL_TO,
'⪷' => PRECEDES_ABOVE_ALMOST_EQUAL_TO,
'⪸' => SUCCEEDS_ABOVE_ALMOST_EQUAL_TO,
'⪹' => PRECEDES_ABOVE_NOT_ALMOST_EQUAL_TO,
'⪺' => SUCCEEDS_ABOVE_NOT_ALMOST_EQUAL_TO,
'⪻' => DOUBLE_PRECEDES,
'⪼' => DOUBLE_SUCCEEDS,
'⪽' => SUBSET_WITH_DOT,
'⪾' => SUPERSET_WITH_DOT,
'⪿' => SUBSET_WITH_PLUS_SIGN_BELOW,
'⫀' => SUPERSET_WITH_PLUS_SIGN_BELOW,
'⫁' => SUBSET_WITH_MULTIPLICATION_SIGN_BELOW,
'⫂' => SUPERSET_WITH_MULTIPLICATION_SIGN_BELOW,
'⫃' => SUBSET_OF_OR_EQUAL_TO_WITH_DOT_ABOVE,
'⫄' => SUPERSET_OF_OR_EQUAL_TO_WITH_DOT_ABOVE,
'⫅' => SUBSET_OF_ABOVE_EQUALS_SIGN,
'⫆' => SUPERSET_OF_ABOVE_EQUALS_SIGN,
'⫇' => SUBSET_OF_ABOVE_TILDE_OPERATOR,
'⫈' => SUPERSET_OF_ABOVE_TILDE_OPERATOR,
'⫉' => SUBSET_OF_ABOVE_ALMOST_EQUAL_TO,
'⫊' => SUPERSET_OF_ABOVE_ALMOST_EQUAL_TO,
'⫋' => SUBSET_OF_ABOVE_NOT_EQUAL_TO,
'⫌' => SUPERSET_OF_ABOVE_NOT_EQUAL_TO,
'⫍' => SQUARE_LEFT_OPEN_BOX_OPERATOR,
'⫎' => SQUARE_RIGHT_OPEN_BOX_OPERATOR,
'⫏' => CLOSED_SUBSET,
'⫐' => CLOSED_SUPERSET,
'⫑' => CLOSED_SUBSET_OR_EQUAL_TO,
'⫒' => CLOSED_SUPERSET_OR_EQUAL_TO,
'⫓' => SUBSET_ABOVE_SUPERSET,
'⫔' => SUPERSET_ABOVE_SUBSET,
'⫕' => SUBSET_ABOVE_SUBSET,
'⫖' => SUPERSET_ABOVE_SUPERSET,
'⫗' => SUPERSET_BESIDE_SUBSET,
'⫘' => SUPERSET_BESIDE_AND_JOINED_BY_DASH_WITH_SUBSET,
'⫙' => ELEMENT_OF_OPENING_DOWNWARDS,
'⫷' => TRIPLE_NESTED_LESS_THAN,
'⫸' => TRIPLE_NESTED_GREATER_THAN,
'⫹' => DOUBLE_LINE_SLANTED_LESS_THAN_OR_EQUAL_TO,
'⫺' => DOUBLE_LINE_SLANTED_GREATER_THAN_OR_EQUAL_TO,
'⊢' => RIGHT_TACK,
'⊣' => LEFT_TACK,
'⫪' => DOUBLE_DOWN_TACK,
'⫫' => DOUBLE_UP_TACK,
'⟂' => PERP,
'⊕' => CIRCLED_PLUS,
'⊖' => CIRCLED_MINUS,
'⊞' => SQUARED_PLUS,
'⊟' => SQUARED_MINUS,
'|' => OR,
'∪' => UNION,
'∨' => LOGICAL_OR,
'⊔' => SQUARE_CUP,
'±' => PLUS_MINUS_SIGN,
'∓' => MINUS_OR_PLUS_SIGN,
'∔' => DOT_PLUS,
'∸' => DOT_MINUS,
'≂' => MINUS_TILDE,
'≏' => DIFFERENCE_BETWEEN,
'⊎' => MULTISET_UNION,
'⊻' => XOR,
'⊽' => NOR,
'⋎' => CURLY_LOGICAL_OR,
'⋓' => DOUBLE_UNION,
'⧺' => DOUBLE_PLUS,
'⧻' => TRIPLE_PLUS,
'⨈' => TWO_LOGICAL_OR_OPERATOR,
'⨢' => PLUS_SIGN_WITH_SMALL_CIRCLE_ABOVE,
'⨣' => PLUS_SIGN_WITH_CIRCUMFLEX_ACCENT_ABOVE,
'⨤' => PLUS_SIGN_WITH_TILDE_ABOVE,
'⨥' => PLUS_SIGN_WITH_DOT_BELOW,
'⨦' => PLUS_SIGN_WITH_TILDE_BELOW,
'⨧' => PLUS_SIGN_WITH_SUBSCRIPT_TWO,
'⨨' => PLUS_SIGN_WITH_BLACK_TRIANGLE,
'⨩' => MINUS_SIGN_WITH_COMMA_ABOVE,
'⨪' => MINUS_SIGN_WITH_DOT_BELOW,
'⨫' => MINUS_SIGN_WITH_FALLING_DOTS,
'⨬' => MINUS_SIGN_WITH_RISING_DOTS,
'⨭' => PLUS_SIGN_IN_LEFT_HALF_CIRCLE,
'⨮' => PLUS_SIGN_IN_RIGHT_HALF_CIRCLE,
'⨹' => PLUS_SIGN_IN_TRIANGLE,
'⨺' => MINUS_SIGN_IN_TRIANGLE,
'⩁' => UNION_WITH_MINUS_SIGN,
'⩂' => UNION_WITH_OVERBAR,
'⩅' => UNION_WITH_LOGICAL_OR,
'⩊' => UNION_BESIDE_AND_JOINED_WITH_UNION,
'⩌' => CLOSED_UNION_WITH_SERIFS,
'⩏' => DOUBLE_SQUARE_UNION,
'⩐' => CLOSED_UNION_WITH_SERIFS_AND_SMASH_PRODUCT,
'⩒' => LOGICAL_OR_WITH_DOT_ABOVE,
'⩔' => DOUBLE_LOGICAL_OR,
'⩖' => TWO_INTERSECTING_LOGICAL_OR,
'⩗' => SLOPING_LARGE_OR,
'⩛' => LOGICAL_OR_WITH_MIDDLE_STEM,
'⩝' => LOGICAL_OR_WITH_HORIZONTAL_DASH,
'⩡' => SMALL_VEE_WITH_UNDERBAR,
'⩢' => LOGICAL_OR_WITH_DOUBLE_OVERBAR,
'⩣' => LOGICAL_OR_WITH_DOUBLE_UNDERBAR,
'∘' => RING_OPERATOR,
'×' => MULTIPLICATION_SIGN,
'∩' => INTERSECTION,
'∧' => LOGICAL_AND,
'⊗' => CIRCLED_TIMES,
'⊘' => CIRCLED_DIVISION_SLASH,
'⊙' => CIRCLED_DOT_OPERATOR,
'⊚' => CIRCLED_RING_OPERATOR,
'⊛' => CIRCLED_ASTERISK_OPERATOR,
'⊠' => SQUARED_TIMES,
'⊡' => SQUARED_DOT_OPERATOR,
'⊓' => SQUARE_CAP,
'∗' => ASTERISK_OPERATOR,
'∙' => BULLET_OPERATOR,
'∤' => DOES_NOT_DIVIDE,
'⅋' => TURNED_AMPERSAND,
'≀' => WREATH_PRODUCT,
'⊼' => NAND,
'⋄' => DIAMOND_OPERATOR,
'⋆' => STAR_OPERATOR,
'⋇' => DIVISION_TIMES,
'⋉' => LEFT_NORMAL_FACTOR_SEMIDIRECT_PRODUCT,
'⋊' => RIGHT_NORMAL_FACTOR_SEMIDIRECT_PRODUCT,
'⋋' => LEFT_SEMIDIRECT_PRODUCT,
'⋌' => RIGHT_SEMIDIRECT_PRODUCT,
'⋏' => CURLY_LOGICAL_AND,
'⋒' => DOUBLE_INTERSECTION,
'⟑' => AND_WITH_DOT,
'⦸' => CIRCLED_REVERSE_SOLIDUS,
'⦼' => CIRCLED_ANTICLOCKWISE_ROTATED_DIVISION_SIGN,
'⦾' => CIRCLED_WHITE_BULLET,
'⦿' => CIRCLED_BULLET,
'⧶' => SOLIDUS_WITH_OVERBAR,
'⧷' => REVERSE_SOLIDUS_WITH_HORIZONTAL_STROKE,
'⨇' => TWO_LOGICAL_AND_OPERATOR,
'⨰' => MULTIPLICATION_SIGN_WITH_DOT_ABOVE,
'⨱' => MULTIPLICATION_SIGN_WITH_UNDERBAR,
'⨲' => SEMIDIRECT_PRODUCT_WITH_BOTTOM_CLOSED,
'⨳' => SMASH_PRODUCT,
'⨴' => MULTIPLICATION_SIGN_IN_LEFT_HALF_CIRCLE,
'⨵' => MULTIPLICATION_SIGN_IN_RIGHT_HALF_CIRCLE,
'⨶' => CIRCLED_MULTIPLICATION_SIGN_WITH_CIRCUMFLEX_ACCENT,
'⨷' => MULTIPLICATION_SIGN_IN_DOUBLE_CIRCLE,
'⨸' => CIRCLED_DIVISION_SIGN,
'⨻' => MULTIPLICATION_SIGN_IN_TRIANGLE,
'⨼' => INTERIOR_PRODUCT,
'⨽' => RIGHTHAND_INTERIOR_PRODUCT,
'⩀' => INTERSECTION_WITH_DOT,
'⩃' => INTERSECTION_WITH_OVERBAR,
'⩄' => INTERSECTION_WITH_LOGICAL_AND,
'⩋' => INTERSECTION_BESIDE_AND_JOINED_WITH_INTERSECTION,
'⩍' => CLOSED_INTERSECTION_WITH_SERIFS,
'⩎' => DOUBLE_SQUARE_INTERSECTION,
'⩑' => LOGICAL_AND_WITH_DOT_ABOVE,
'⩓' => DOUBLE_LOGICAL_AND,
'⩕' => TWO_INTERSECTING_LOGICAL_AND,
'⩘' => SLOPING_LARGE_AND,
'⩚' => LOGICAL_AND_WITH_MIDDLE_STEM,
'⩜' => LOGICAL_AND_WITH_HORIZONTAL_DASH,
'⩞' => LOGICAL_AND_WITH_DOUBLE_OVERBAR,
'⩟' => LOGICAL_AND_WITH_UNDERBAR,
'⩠' => LOGICAL_AND_WITH_DOUBLE_UNDERBAR,
'⫛' => TRANSVERSAL_INTERSECTION,
'⊍' => MULTISET_MULTIPLICATION,
'▷' => WHITE_RIGHT_POINTING_TRIANGLE,
'⨝' => JOIN,
'⟕' => LEFT_OUTER_JOIN,
'⟖' => RIGHT_OUTER_JOIN,
'⟗' => FULL_OUTER_JOIN,
'^' => CIRCUMFLEX_ACCENT,
'↑' => UPWARDS_ARROW,
'↓' => DOWNWARDS_ARROW,
'⇵' => DOWNWARDS_ARROW_LEFTWARDS_OF_UPWARDS_ARROW,
'⟰' => UPWARDS_QUADRUPLE_ARROW,
'⟱' => DOWNWARDS_QUADRUPLE_ARROW,
'⤈' => DOWNWARDS_ARROW_WITH_HORIZONTAL_STROKE,
'⤉' => UPWARDS_ARROW_WITH_HORIZONTAL_STROKE,
'⤊' => UPWARDS_TRIPLE_ARROW,
'⤋' => DOWNWARDS_TRIPLE_ARROW,
'⤒' => UPWARDS_ARROW_TO_BAR,
'⤓' => DOWNWARDS_ARROW_TO_BAR,
'⥉' => UPWARDS_TWO_HEADED_ARROW_FROM_SMALL_CIRCLE,
'⥌' => UP_BARB_RIGHT_DOWN_BARB_LEFT_HARPOON,
'⥍' => UP_BARB_LEFT_DOWN_BARB_RIGHT_HARPOON,
'⥏' => UP_BARB_RIGHT_DOWN_BARB_RIGHT_HARPOON,
'⥑' => UP_BARB_LEFT_DOWN_BARB_LEFT_HARPOON,
'⥔' => UPWARDS_HARPOON_WITH_BARB_RIGHT_TO_BAR,
'⥕' => DOWNWARDS_HARPOON_WITH_BARB_RIGHT_TO_BAR,
'⥘' => UPWARDS_HARPOON_WITH_BARB_LEFT_TO_BAR,
'⥙' => DOWNWARDS_HARPOON_WITH_BARB_LEFT_TO_BAR,
'⥜' => UPWARDS_HARPOON_WITH_BARB_RIGHT_FROM_BAR,
'⥝' => DOWNWARDS_HARPOON_WITH_BARB_RIGHT_FROM_BAR,
'⥠' => UPWARDS_HARPOON_WITH_BARB_LEFT_FROM_BAR,
'⥡' => DOWNWARDS_HARPOON_WITH_BARB_LEFT_FROM_BAR,
'⥣' => UPWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_UPWARDS_HARPOON_WITH_BARB_RIGHT,
'⥥' => DOWNWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_DOWNWARDS_HARPOON_WITH_BARB_RIGHT,
'⥮' => UPWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_DOWNWARDS_HARPOON_WITH_BARB_RIGHT,
'⥯' => DOWNWARDS_HARPOON_WITH_BARB_LEFT_BESIDE_UPWARDS_HARPOON_WITH_BARB_RIGHT,
'￪' => HALFWIDTH_UPWARDS_ARROW,
'￬' => HALFWIDTH_DOWNWARDS_ARROW,
# Lookalikes which are normalized into UNICODE_DOT
# https://github.com/JuliaLang/julia/pull/25157
'\u00b7' => UNICODE_DOT, # '·' Middle Dot,
'\u0387' => UNICODE_DOT, # '·' Greek Ano Teleia,
'⋅' => UNICODE_DOT,
'…' => LDOTS,
'⁝' => TRICOLON,
'⋮' => VDOTS,
'⋱' => DDOTS,
'⋰' => ADOTS,
'⋯' => CDOTS,
'↻' => CIRCLE_ARROW_RIGHT,
'⇜' => LEFT_SQUIGGLE_ARROW,
'⇝' => RIGHT_SQUIGGLE_ARROW,
'↜' => LEFT_WAVE_ARROW,
'↝' => RIGHT_WAVE_ARROW,
'↩' => LEFTWARDS_ARROW_WITH_HOOK,
'↪' => RIGHTWARDS_ARROW_WITH_HOOK,
'↫' => LOOP_ARROW_LEFT,
'↬' => LOOP_ARROW_RIGHT,
'↼' => LEFT_HARPOON_UP,
'↽' => LEFT_HARPOON_DOWN,
'⇀' => RIGHT_HARPOON_UP,
'⇁' => RIGHT_HARPOON_DOWN,
'⇄' => RIGHT_LEFT_ARROWS,
'⇆' => LEFT_RIGHT_ARROWS,
'⇇' => LEFT_LEFT_ARROWS,
'⇉' => RIGHT_RIGHT_ARROWS,
'⇋' => LEFT_RIGHT_HARPOONS,
'⇌' => RIGHT_LEFT_HARPOONS,
'⇚' => L_LEFT_ARROW,
'⇛' => R_RIGHT_ARROW,
'⇠' => LEFT_DASH_ARROW,
'⇢' => RIGHT_DASH_ARROW,
'↷' => CURVE_ARROW_RIGHT,
'↶' => CURVE_ARROW_LEFT,
'↺' => CIRCLE_ARROW_LEFT,
'¦' => BROKEN_BAR,
'⌿' => NOT_SLASH,
'⨟' => BB_SEMI)

const UNICODE_OPS_REVERSE = Dict{Kind,Symbol}()
for (k, v) in UNICODE_OPS
    k in ('\u00b7', '\u0387') && continue
    UNICODE_OPS_REVERSE[v] = Symbol(k)
end

UNICODE_OPS_REVERSE[EQ] = :(=)
UNICODE_OPS_REVERSE[PLUS_EQ] = :(+=)
UNICODE_OPS_REVERSE[MINUS_EQ] = :(-=)
UNICODE_OPS_REVERSE[STAR_EQ] = :(*=)
UNICODE_OPS_REVERSE[FWD_SLASH_EQ] = :(/=)
UNICODE_OPS_REVERSE[FWDFWD_SLASH_EQ] = :(//=)
UNICODE_OPS_REVERSE[OR_EQ] = :(|=)
UNICODE_OPS_REVERSE[CIRCUMFLEX_EQ] = :(^=)
UNICODE_OPS_REVERSE[DIVISION_EQ] = :(÷=)
UNICODE_OPS_REVERSE[REM_EQ] = :(%=)
UNICODE_OPS_REVERSE[LBITSHIFT_EQ] = :(<<=)
UNICODE_OPS_REVERSE[RBITSHIFT_EQ] = :(>>=)
UNICODE_OPS_REVERSE[LBITSHIFT] = :(<<)
UNICODE_OPS_REVERSE[RBITSHIFT] = :(>>)
UNICODE_OPS_REVERSE[UNSIGNED_BITSHIFT] = :(>>>)
UNICODE_OPS_REVERSE[UNSIGNED_BITSHIFT_EQ] = :(>>>=)
UNICODE_OPS_REVERSE[BACKSLASH_EQ] = :(\=)
UNICODE_OPS_REVERSE[AND_EQ] = :(&=)
UNICODE_OPS_REVERSE[COLON_EQ] = :(:=)
UNICODE_OPS_REVERSE[PAIR_ARROW] = :(=>)
UNICODE_OPS_REVERSE[APPROX] = :(~)
UNICODE_OPS_REVERSE[EX_OR_EQ] = :($=)
UNICODE_OPS_REVERSE[XOR_EQ] = :(⊻=)
UNICODE_OPS_REVERSE[RIGHT_ARROW] = :(-->)
UNICODE_OPS_REVERSE[LAZY_OR] = :(||)
UNICODE_OPS_REVERSE[LAZY_AND] = :(&&)
UNICODE_OPS_REVERSE[ISSUBTYPE] = :(<:)
UNICODE_OPS_REVERSE[ISSUPERTYPE] = :(>:)
UNICODE_OPS_REVERSE[GREATER] = :(>)
UNICODE_OPS_REVERSE[LESS] = :(<)
UNICODE_OPS_REVERSE[GREATER_EQ] = :(>=)
UNICODE_OPS_REVERSE[GREATER_THAN_OR_EQUAL_TO] = :(≥)
UNICODE_OPS_REVERSE[LESS_EQ] = :(<=)
UNICODE_OPS_REVERSE[LESS_THAN_OR_EQUAL_TO] = :(≤)
UNICODE_OPS_REVERSE[EQEQ] = :(==)
UNICODE_OPS_REVERSE[EQEQEQ] = :(===)
UNICODE_OPS_REVERSE[IDENTICAL_TO] = :(≡)
UNICODE_OPS_REVERSE[NOT_EQ] = :(!=)
UNICODE_OPS_REVERSE[NOT_EQUAL_TO] = :(≠)
UNICODE_OPS_REVERSE[NOT_IS] = :(!==)
UNICODE_OPS_REVERSE[NOT_IDENTICAL_TO] = :(≢)
UNICODE_OPS_REVERSE[IN] = :(in)
UNICODE_OPS_REVERSE[ISA] = :(isa)
UNICODE_OPS_REVERSE[LPIPE] = :(<|)
UNICODE_OPS_REVERSE[RPIPE] = :(|>)
UNICODE_OPS_REVERSE[COLON] = :(:)
UNICODE_OPS_REVERSE[DDOT] = :(..)
UNICODE_OPS_REVERSE[EX_OR] = :($)
UNICODE_OPS_REVERSE[PLUS] = :(+)
UNICODE_OPS_REVERSE[MINUS] = :(-)
UNICODE_OPS_REVERSE[PLUSPLUS] = :(++)
UNICODE_OPS_REVERSE[OR] = :(|)
UNICODE_OPS_REVERSE[STAR] = :(*)
UNICODE_OPS_REVERSE[FWD_SLASH] = :(/)
UNICODE_OPS_REVERSE[REM] = :(%)
UNICODE_OPS_REVERSE[BACKSLASH] = :(\)
UNICODE_OPS_REVERSE[AND] = :(&)
UNICODE_OPS_REVERSE[FWDFWD_SLASH] = :(//)
UNICODE_OPS_REVERSE[CIRCUMFLEX_ACCENT] = :(^)
UNICODE_OPS_REVERSE[DECLARATION] = :(::)
UNICODE_OPS_REVERSE[CONDITIONAL] = :?
UNICODE_OPS_REVERSE[DOT] = :(.)
UNICODE_OPS_REVERSE[NOT] = :(!)
UNICODE_OPS_REVERSE[PRIME] = Symbol(''')
UNICODE_OPS_REVERSE[DDDOT] = :(...)
UNICODE_OPS_REVERSE[TRANSPOSE] = Symbol(".'")
UNICODE_OPS_REVERSE[ANON_FUNC] = :(->)
UNICODE_OPS_REVERSE[WHERE] = :where


text:
# PREPROCESSING
# atomnum, chirality, degree, charge, numH, radicale, hybridization, isarom, isisring
const OGB_NUM_ATOM_FEATURES = [119, 4, 12, 12, 10, 6, 6, 2, 2]

# bondtype, bondstereo, isconjugated
const OGB_NUM_BOND_FEATURES = [5, 6, 2]

function nhotfeats(a::AbstractArray, numfeats::AbstractVector, normalize=false)
    batchsize = size(a, 2)
    sumnumfeats = circshift(cumsum(numfeats), 1)
    sumnumfeats[1] = 0

    out = similar(a, Float32, (sum(numfeats), batchsize)) .= 0

    for i in 1:size(a, 2)
        out[a[:, i] .+ sumnumfeats, i] .= 1
    end

    normalize ? out ./ length(numfeats) : out
end

# dataloading
function getmolbatch(features, graphdata, idxs)
    cumnumnodes = cumsum(graphdata["numnodes"])
    cumnumedges = cumsum(graphdata["numedges"])

    graphs = Vector{GraphTuple}(undef, length(idxs))

    for (i, idx) in enumerate(idxs)
        nodef = features["nodes"][:, (idx == 1 ? 1 : cumnumnodes[idx - 1]+1):cumnumnodes[idx]]
        nodef = nhotfeats(nodef, OGB_NUM_ATOM_FEATURES)

        edgeidxs = (idx == 1 ? 1 : cumnumedges[idx - 1]+1):cumnumedges[idx]
        edgef = nhotfeats(features["edges"][:, edgeidxs], OGB_NUM_BOND_FEATURES)

        senders = graphdata["edgelist"][edgeidxs, 1]
        receivers = graphdata["edgelist"][edgeidxs, 2]

        graphs[i] = GraphTuple(nodes=nodef, edges=edgef, senders=senders, receivers=receivers)
    end
    batchgraphs(graphs, graphdata["numnodes"][idxs], graphdata["numedges"][idxs], true)
end

# ENCODERS
struct NHotEncoder
    W
end

function NHotEncoder(featdims::AbstractVector{<:Integer}, embdim::Integer)
    W = zeros(embdim, sum(featdims))

    current = 1
    for featdim in featdims
        W[:, current:current+featdim-1] .= Flux.glorot_uniform(embdim, featdim)
        current += featdim
    end

    return NHotEncoder(W)
end

(l::NHotEncoder)(nhotmatrix::AbstractMatrix) = l.W * nhotmatrix

Flux.@functor NHotEncoder

# atom and bond encoders
struct OGBAtomEncoder
    embedder
end

OGBAtomEncoder(embdim::Integer) = OGBAtomEncoder(NHotEncoder(OGB_NUM_ATOM_FEATURES, embdim))
(l::OGBAtomEncoder)(g::AbstractGraphTuple) = @set g.nodes = l.embedder(g.nodes)
Flux.@functor OGBAtomEncoder

struct OGBBondEncoder
    embedder
end

OGBBondEncoder(embdim::Integer) = OGBBondEncoder(NHotEncoder(OGB_NUM_BOND_FEATURES, embdim))
(l::OGBBondEncoder)(g::AbstractGraphTuple) = @set g.edges = l.embedder(g.edges)
Flux.@functor OGBBondEncoder

# GNN LAYERS
struct GCNₑ
    l
    edgeembedding
    rootembedding
end

function GCNₑ(in::Integer, out::Integer, σ=identity)
    l = Dense(in, out, σ)
    edgeembedding = OGBBondEncoder(out)
    rootembedding = randn(out)
    return GCNₑ(l, edgeembedding, rootembedding)
end

function (layer::GCNₑ)(g::AbstractGraphTuple)
    edgeh = layer.edgeembedding(g) |> edges
    nodeh = layer.l(nodes(g))

    deg = indegrees(g) .+ 1

    gathered = gather(nodeh, senders(g))
    gathered = reshape(symmetricnorm(g, deg), 1, :) .* relu.(gathered .+ edgeh)

    nodeh = scatter(gathered, receivers(g), nnodes(g), +) .+ relu.(nodeh .+ layer.rootembedding) ./ reshape(deg, 1, :)

    updatenodes(g, nodeh)
end

Flux.@functor GCNₑ

struct CoraBlock
    gcn
    drop
end

function (l::CoraBlock)(g::GraphTuple)
    g = (@set g.nodes = l.drop(g.nodes))
    l.gcn(g)
end

Flux.@functor CoraBlock

ext: jl


text:
module CGPrism3D
using LinearAlgebra
#using StatsBase

# Define chop function
export numchop
numchop(num::Real)=(abs(num) >= 1000*eps() ? num : zero(Real))

function numchop(num::Complex)
    numchop(imag(num)) == 0. ? numchop(real(num)) : Complex(numchop(real(num)), numchop(imag(num)))
end

# Define k-level of SU(2)_k
const K = 3 # K-level
const x = K+1 #number of spins
const y = K/2 # maximum spin

#Define all functions needed for G-symbol
# Define delta{ijk} -> coupling rules
function delta(i::Float64,j::Float64,k::Float64)
    sol = 0.
    if i <=(j+k) && j<=(i+k) && k<=(i+j) && i+j+k <= K && 2*(i+j+k)%2 ==0
        sol = 1.
    end
    return sol
end

#Define quantum numbers qn (this is real)
function qn(n::Float64)
    sol = (exp(pi*n*im/(K+2)) - exp(-pi*n*im/(K+2))) / (exp(pi*im/(K+2)) - exp(-pi*im/(K+2)))
    return real(sol)
end

#Define qn factorial
function qnfact(n::Float64)
    sol = 1.
    for i in 1:n
        sol *= qn(i)
    end
    return sol
end

#Define square root of quantum dimension
function visqrt(i::Float64)
    sol = ((-1+0im)^i )*sqrt(qn(2*i+1))
    return sol
end

#Define triangle equality
function trian(i::Float64,j::Float64,k::Float64)
    sol = 0.
    if delta(i,j,k) == 1.
        sol = delta(i,j,k)*sqrt(qnfact(i+j-k)*qnfact(i-j+k)*qnfact(-i+j+k)/qnfact(i+j+k+1))
    end
    return sol
end

# Define Racah-Wigner six-j symbol
function RacWig6j(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    a = i+j+m; b = i+l+n; c = k+j+n; d = k+l+m;  e = i+j+k+l; f = i+k+m+n; g = j+l+m+n
    sol = 0.
    if delta(i,j,m) != 0. && delta(i,l,n) != 0. && delta(k,j,n) != 0. && delta(k,l,m) != 0.
        sumz = 0
        for z in max(a,b,c,d):min(e,f,g)
            sumz += (-1)^z *qnfact(z+1)/
                ((qnfact(e-z)*qnfact(f-z)*qnfact(g-z))* (qnfact(z-a)*qnfact(z-b)*qnfact(z-c)*qnfact(z-d)))
        end
        sol = trian(i,j,m)*trian(i,l,n)*trian(k,j,n)*trian(k,l,m)*sumz
    end
    return sol
end

#Define F-symbol
function Fsymb(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0.
    if delta(i,j,m) != 0. && delta(i,l,n) != 0. && delta(k,j,n) != 0. && delta(k,l,m) != 0.
        sol = (-1+0im)^(i+j+k+l)*sqrt(qn(2*m+1)*qn(2*n+1)) * RacWig6j(i,j,m,k,l,n)
    end
    return sol
end

#Define G-symbol
function Gsymb(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0.
    if delta(i,j,m) != 0. && delta(i,l,n) != 0. && delta(k,j,n) != 0. && delta(k,l,m) != 0.
        sol = Fsymb(i,j,m,k,l,n) /(visqrt(m)*visqrt(n))
    end
    return sol
end

export Tetra6j, delta, visqrt
#Define G-symbol times dimensions
function Tetra6j(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0.
    if delta(i,j,m) != 0. && delta(i,l,n) != 0. && delta(k,j,n) != 0. && delta(k,l,m) != 0.
        dims = prod(visqrt.([i,j,m,k,l,n]))
        sol =  numchop(dims*Gsymb(i,j,m,k,l,n))
    end
    return sol
end

#Define G-symbol 2
function Gsymb2(i::Float64,j::Float64,m::Float64,k::Float64,l::Float64,n::Float64)
    sol = 0.
    if delta(i,j,m) != 0. && delta(i,l,n) != 0. && delta(k,j,n) != 0. && delta(k,l,m) != 0.
        sol = (-1+0im)^(i+j+k+l+m+n)*RacWig6j(i,j,m,k,l,n)
    end
    return sol
end

## Define Prisms
#Prism A as gluing together three tetrahedra ;  Prism B changing diagonals using F-symbol
function prismA(jd1::Float64,jd2::Float64,je1::Float64,jb1::Float64,jg::Float64,ja1::Float64,jc::Float64,
                jb2::Float64,jf1::Float64,ja2::Float64,jf2::Float64,je2::Float64)
    sol = 0.
    if delta(jd1,jd2,je1) != 0. && delta(jd1,jb1,jg) != 0. && delta(ja1,jd2,jg) != 0. && delta(ja1,jb1,je1) != 0. && delta(jd1,jc,jb2) != 0. && delta(jf1,jd2,jb2) != 0. && delta(jf1,jc,je1) != 0. && delta(jd1,ja2,jf2) != 0. && delta(jc,je2,jf2) != 0. && delta(jb2,je2,ja2) != 0.
        dims = visqrt(ja1)*visqrt(jb1)*visqrt(jg)*visqrt(jf1)*visqrt(jc)*visqrt(jb2)*visqrt(je2)*visqrt(ja2)*visqrt(jf2)*visqrt(jd1)*visqrt(jd2)*visqrt(je1)
        sol =  dims*Gsymb(jd1,jd2,je1,ja1,jb1,jg) * Gsymb(jd1,jd2,je1,jf1,jc,jb2) * Gsymb(jd1,jc,jb2,je2,ja2,jf2)
    end
    return sol
end

function prismB(jd2p::Float64,jd1p::Float64,je2::Float64,jb2::Float64,jg::Float64,ja2::Float64,jc::Float64,
                jb1::Float64,jf2::Float64,ja1::Float64,jf1::Float64,je1::Float64)
    sol = 0.
    if delta(jd1p,jd2p,je2) != 0 && delta(jd2p,jb2,jg) != 0 && delta(ja2,jd1p,jg) != 0 && delta(ja2,jb2,je2) != 0 && delta(jd2p,jc,jb1) != 0 && delta(jf2,jd1p,jb1) != 0 && delta(jf2,jc,je2) != 0 && delta(jd2p,ja1,jf1) != 0 && delta(jc,je1,jf1) != 0 && delta(jb1,je1,ja1) != 0
        for jd1 in 0.:0.5:y
            if delta(jb1,jg,jd1) != 0 && delta(ja2,jf2,jd1) != 0
                for jd2 in 0.:0.5:y
                    if delta(ja1,jg,jd2) != 0 && delta(jb2,jf1,jd2) != 0
                        sol += numchop(Fsymb(ja1,jg,jd2,jb2,jf1,jd2p)*Fsymb(jb1,jg,jd1,ja2,jf2,jd1p)*prismA(jd1,jd2,je1,jb1,jg,ja1,jc,jb2,jf1,ja2,jf2,je2))
                    end
                end
            end
        end
    end
    return sol
end

# This is the relabelling vertices. Does it work in general after one step in algorithm?
function prismB2(jd2::Float64,jd1::Float64,je2::Float64,jb2::Float64,jg::Float64,ja2::Float64,jc::Float64,
                jb1::Float64,jf2::Float64,ja1::Float64,jf1::Float64,je1::Float64)
    return prismA(jd1,jd2,je1,jb1,jg,ja1,jc,jb2,jf1,ja2,jf2,je2)
end

export dataTet, dataFsymb, dataPrsmA

# get all non-zero amplitudes and spins

# get all non-zero tetrahedron amplitude and spins (also for F-symbol)

function dataTet()
    t6j = []
    t6s = []
    for ja in 0.:0.5:y, jb in 0.:0.5:y, jc in 0.:0.5:y
        if delta(ja,jb,jc) != 0
            for jd in 0.:0.5:y, je in 0.:0.5:y
                if delta(jc,jd,je) != 0
                    for jf in 0.:0.5:y
                        if delta(ja,je,jf) != 0 && delta(jb,jd,jf) != 0
            #dims = prod(visqrt.([ja,jb,jc,jd,je,jf]))
                            sol = numchop(Tetra6j(ja,jb,jc,jd,je,jf) )
                            if sol != 0
                                push!(t6s,sol)
                                push!(t6j,[ja,jb,jc,jd,je,jf])
                            end
                        end
                    end
                end
            end
        end
    end
    return t6j,t6s
end

function dataFsymb()
    t6j = []
    t6s = []
    for ja in 0.:0.5:y, jb in 0.:0.5:y, jc in 0.:0.5:y
        if delta(ja,jb,jc) != 0
            for jd in 0.:0.5:y, je in 0.:0.5:y
                if delta(jc,jd,je) != 0
                    for jf in 0.:0.5:y
                        if delta(ja,je,jf) != 0 && delta(jb,jd,jf) != 0
            #dims = prod(visqrt.([ja,jb,jc,jd,je,jf]))
                            sol = numchop(Fsymb(ja,jb,jc,jd,je,jf) )
                            if sol != 0
                                push!(t6s,sol)
                                push!(t6j,[ja,jb,jc,jd,je,jf])
                            end
                        end
                    end
                end
            end
        end
    end
    return t6j,t6s
end

function dataPrsmA()
    ampsInfo = Array{Float64,1}[]
    amps = Float64[]
    for jd1 in 0.:0.5:y, jd2 in 0.:0.5:y, je1 in 0.:0.5:y
        if delta(jd1,jd2,je1) != 0
            for jb1 in 0.:0.5:y, jg in 0.:0.5:y
                if delta(jd1,jb1,jg) != 0
                    for ja1 in 0.:0.5:y
                        if delta(ja1,jd2,jg) != 0 && delta(ja1,jb1,je1) != 0
                            for jc in 0.:0.5:y, jb2 in 0.:0.5:y
                                if delta(jd1,jc,jb2) != 0
                                    for jf1 in 0.:0.5:y
                                        if delta(jf1,jd2,jb2) != 0 && delta(jf1,jc,je1) != 0
                                            for ja2 in 0.:0.5:y, jf2 in 0.:0.5:y
                                                if delta(jd1,ja2,jf2) != 0
                                                    for je2 in 0.:0.5:y
                                                        if delta(jc,je2,jf2) != 0 && delta(jb2,je2,ja2) != 0 #
                                                            sol = numchop(prismA(jd1,jd2,je1,jb1,jg,ja1,jc,jb2,jf1,ja2,jf2,je2))
                                                            if sol != 0
                                                                indx = [jb1,jg,ja1,jd1,jd2,je1,jc,jb2,jf1,ja2,jf2,je2]
                                                                push!(ampsInfo,indx)
                                                                push!(amps,sol)
                                                            end
                                                        end
                                                    end
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return ampsInfo, amps
end

# Operation on tensors
# 1. Gluing tensors, 2. Splitting tensors, 3. Summing over tensor indices

export tensorBlock, fullSplitTet3D, fullSplit3D, tensorGlue, tensorSum, tensorGlueTet3D, tensor22move, tensorSumTet3D

# a general function to split a tensor M^C_{AB} to U^C_{Ai} and  V^C_{iB} with C the shared face.. for particular values of C
# first reduce to matrix form and then perform svd to get U, s, and V

#This reduces the tensor M into matrix form M^C_{AB} with AB labelling matric indices.
function tensorBlock(tensorM,posnA,posnB,posnC,spinC)
    indx = tensorM[1]
    ampvals = tensorM[2]
    fc = findall(x-> x[posnC] == spinC, indx) #find spinsC in all spins length(posnC) = length(spinC)
    ampInfo = indx[fc] # this contains all spin configuration containing ampsC
    amps = ampvals[fc] # this contains the corresponding spin amplitude values for ampsC
    Acol = unique(getindex.(ampInfo, [posnA]))
    Brow = unique(getindex.(ampInfo, [posnB]))
    indxU = vcat.(Acol,repeat([spinC],length(Acol)))
    indxV = vcat.(Brow,repeat([spinC],length(Brow)))
    matM = zeros(length(Acol),length(Brow))
    if length(matM) != 0
        for i in 1:length(ampInfo)
            qu = findfirst(x-> x == ampInfo[i][posnA], Acol)[1]# [1] since it returns an array [1]
            qv = findfirst(x-> x == ampInfo[i][posnB], Brow)[1]
            matM[qu,qv] = amps[i]
        end
        #U, s, V = svd(matM)
        return matM,indxU,indxV
    else
        return 0,0,0
    end
end

#A general function to splits fully the tensor M^C_{AB} to tensors U^C_{Ai} and V^C_{iB}.
# It uses tensorBlock, 'fixes' the sign problem from svd, and normalize
# We keep only the first singular value (assuming a gemetric split)
# splits prism into tetrahedron and a  pyramid
# Multiply back by missing dimension factors
function fullSplitTet3D(dataM,posnA,posnB,posnC) # posnA,B,C must be vectors @assert length(dataM) = vcat(A,B,C)
    indx = dataM[1]
    ampvals = dataM[2]
    #posAC = vcat(posnA,posnC) # assert posnA,posnC must be both vectors
    #posBC = vcat(posnB,posnC) # assert posnB,posnC must be both vectors
    #indxU = unique(getindex.(indx, [posAC]))
    #indxV = unique(getindex.(indx, [posBC]))
    ampsC = unique(getindex.(indx, [posnC])) # get all unique spins C (jd1,jd2,je1)
    ampsU = [] # this will store all amplitudes for U^C_{A}
    ampsV = [] # this will store all amplitudes for V^C_{B}
    indxsU = []
    indxsV = []
    #indxUV = [] # if we want this will store all spins in the right order
    for i in 1:length(ampsC) # loop over the unique spins
        jd1 = ampsC[i][1]; jd2 = ampsC[i][2]; je1 = ampsC[i][3]  #jd1,jd2,je1 in spin C form a triangle
        if delta(jd1,jd2,je1) != 0
            spinC = [jd1,jd2,je1]
            block = tensorBlock(dataM,posnA,posnB,posnC,spinC)
            mat = block[1]
            blkU = block[2]
            blkV = block[3]
            U, s, V = svd(mat)
            #truncate and use only first singular value
            U1 = U[:,1]
            V1 = V[:,1]
            s1 = s[1]
            valU = U1*sqrt(s1)*sqrt(prod(visqrt.(ampsC[i]))) # multiply by leftover dimension factors
            valV = V1*sqrt(s1)*sqrt(prod(visqrt.(ampsC[i])))
            # valU,valV are all either real or purely imaginary
            valU = real(valU) - imag(valU) # take -ve of imaginary part if it gives only imag
            valV = real(valV) + imag(valV)
            ja1 = blkU[1][1]; jb1 = blkU[1][2]; jg = blkU[1][3] # pick the first spins in U
            #Fixing sign problem -- keep the same sign for U and Tetra6j symbol
            if sign(valU[1]) == sign(Tetra6j(jd1,jd2,je1,ja1,jb1,jg) )
                push!(ampsU, valU)#/valU[1])
                push!(ampsV, valV)#/valV[1])
                push!(indxsU,blkU)
                push!(indxsV,blkV)
            else
                push!(ampsU, -valU)#/valU[1])
                push!(ampsV, -valV)#/valV[1])
                push!(indxsU,blkU)
                push!(indxsV,blkV)
            end
        end
    end
    solU = collect(Iterators.flatten(ampsU))
    solV = collect(Iterators.flatten(ampsV))
    ansU = solU/solU[1] # normalization condition
    ansV = solV/solV[1]
    indxUs = collect(Iterators.flatten(indxsU))
    indxVs = collect(Iterators.flatten(indxsV))
    return (indxUs,ansU),(indxVs,ansV) #,indxUV
end

# This doesn't take care of dimension factors
function fullSplit3DOld(dataM,posnA,posnB,posnC) # posnA,B,C must be vectors @assert length(dataM) = vcat(A,B,C)
    indx = dataM[1]
    ampvals = dataM[2]
    #posAC = vcat(posnA,posnC) # assert posnA,posnC must be both vectors
    #posBC = vcat(posnB,posnC) # assert posnB,posnC must be both vectors
    #indxU = unique(getindex.(indx, [posAC]))
    #indxV = unique(getindex.(indx, [posBC]))
    ampsC = unique(getindex.(indx, [posnC])) # get all unique spins C (jd1,jd2,je1)
    ampsU = [] # this will store all amplitudes for U^C_{A}
    ampsV = [] # this will store all amplitudes for V^C_{B}
    indxsU = []
    indxsV = []
    #indxUV = [] # if we want this will store all spins in the right order
    for i in 1:length(ampsC) # loop over the unique spins
        jd1 = ampsC[i][1]; jd2 = ampsC[i][2]; je1 = ampsC[i][3]  #jd1,jd2,je1 in spin C form a triangle
        if delta(jd1,jd2,je1) != 0
            spinC = [jd1,jd2,je1]
            block = tensorBlock(dataM,posnA,posnB,posnC,spinC)
            mat = block[1]
            blkU = block[2]
            blkV = block[3]
            U, s, V = svd(mat)
            #truncate and use only first singular value
            U1 = U[:,1]
            V1 = V[:,1]
            s1 = s[1]
            valU = U1*sqrt(s1)#*sqrt(prod(visqrt.(ampsC[i]))) # multiply by leftover dimension factors
            valV = V1*sqrt(s1)#*sqrt(prod(visqrt.(ampsC[i])))
            # valU,valV are all either real or purely imaginary
            valU = real(valU) - imag(valU) # take -ve of imaginary part if it gives only imag
            valV = real(valV) + imag(valV)
            ja1 = blkU[1][1]; jb1 = blkU[1][2]; jg = blkU[1][3] # pick the first spins in U
            #Fixing sign problem -- keep the same sign for U and Tetra6j symbol
            if sign(valU[1]) == sign(Tetra6j(jd1,jd2,je1,ja1,jb1,jg) )
                push!(ampsU, valU)#/valU[1])
                push!(ampsV, valV)#/valV[1])
                push!(indxsU,blkU)
                push!(indxsV,blkV)
            else
                push!(ampsU, -valU)#/valU[1])
                push!(ampsV, -valV)#/valV[1])
                push!(indxsU,blkU)
                push!(indxsV,blkV)
            end
        end
    end
    solU = collect(Iterators.flatten(ampsU))
    solV = collect(Iterators.flatten(ampsV))
    ansU = solU/solU[1] # normalization condition
    ansV = solV/solV[1]
    indxUs = collect(Iterators.flatten(indxsU))
    indxVs = collect(Iterators.flatten(indxsV))
    return (indxUs,ansU),(indxVs,ansV) #,indxUV
end

function fullSplit3D(dataM,posnA,posnB,posnC) # posnA,B,C must be vectors @assert length(dataM) = vcat(A,B,C)
    indx = dataM[1]
    ampvals = dataM[2]
    #posAC = vcat(posnA,posnC) # assert posnA,posnC must be both vectors
    #posBC = vcat(posnB,posnC) # assert posnB,posnC must be both vectors
    #indxU = unique(getindex.(indx, [posAC]))
    #indxV = unique(getindex.(indx, [posBC]))
    ampsC = unique(getindex.(indx, [posnC])) # get all unique spins C (jd1,jd2,je1)
    ampsU = [] # this will store all amplitudes for U^C_{A}
    ampsV = [] # this will store all amplitudes for V^C_{B}
    indxsU = []
    indxsV = []
    #indxUV = [] # if we want this will store all spins in the right order
    for i in 1:length(ampsC) # loop over the unique spins
        #jd1 = ampsC[i][1]; jd2 = ampsC[i][2]; je1 = ampsC[i][3]  #jd1,jd2,je1 in spin C form a triangle
        #if delta(jd1,jd2,je1) != 0
        #spinC = [jd1,jd2,je1]
        block = tensorBlock(dataM,posnA,posnB,posnC,ampsC[i])
        mat = block[1]
        blkU = block[2]
        blkV = block[3]
        if mat != 0
            U, s, V = svd(mat)
            #truncate and use only first singular value
            U1 = U[:,1]
            V1 = V[:,1]
            s1 = s[1]
            valU = U1*sqrt(s1)#*sqrt(prod(visqrt.(ampsC[i]))) # multiply by leftover dimension factors
            valV = V1*sqrt(s1)#*sqrt(prod(visqrt.(ampsC[i])))
            # valU,valV are all either real or purely imaginary
            valU = real(valU) - imag(valU) # take -ve of imaginary part if it gives only imag
            valV = real(valV) + imag(valV)
            #ja1 = blkU[1][1]; jb1 = blkU[1][2]; jg = blkU[1][3] # pick the first spins in U
            #Fixing sign problem -- keep the same sign for U and Tetra6j symbol
            #if sign(valU[1]) == sign(Tetra6j(jd1,jd2,je1,ja1,jb1,jg) )
            push!(ampsU, valU)#/valU[1])
            push!(ampsV, valV)#/valV[1])
            push!(indxsU,blkU)
            push!(indxsV,blkV)
            #else
                #push!(ampsU, -valU)#/valU[1])
                #push!(ampsV, -valV)#/valV[1])
                #push!(indxsU,blkU)
                #push!(indxsV,blkV)
            #end
        end
    end
    solU = collect(Iterators.flatten(ampsU))
    solV = collect(Iterators.flatten(ampsV))
    ansU = solU/solU[1] # normalization condition
    ansV = solV/solV[1]
    indxUs = collect(Iterators.flatten(indxsU))
    indxVs = collect(Iterators.flatten(indxsV))
    return (indxUs,ansU),(indxVs,ansV) #,indxUV
end

## Glue any two tensors A, B along shared faces ..
function tensorGlue(tensorB,tensorA,posnB,posnA)# glue two tensors TA,TB along posnA from TA and posnB from TB
    indxA = tensorA[1] # spins of TA
    indxB = tensorB[1] # spins of TB
    ampsA = tensorA[2] # amplitudes of TA
    ampsB = tensorB[2] # amplitudes of TB
    shrdA = getindex.(indxA,[posnA])
    shrdB = getindex.(indxB,[posnB])
    lena = collect(1:length(indxA[1]))
    deleteat!(lena,sort(posnA)) # assert needs posnA to be a vector
    #if length(posnA) > 1 deleteat!(lena,sort(posnA,dims=2) else  deleteat!(lena,sort(posnA)) end
    shrdAB = []
    qq = []
    amps = []
    sa = sortperm(shrdA)
    sb = sortperm(shrdB)
    shrdA = shrdA[sa]
    shrdB = shrdB[sb]
    sindxA = indxA[sa]
    sindxB = indxB[sb]
    sampsA = ampsA[sa]
    sampsB = ampsB[sb]
    shrdAu = unique(shrdA)
    shrdBu = unique(shrdB)
    shrd = shrdAu
    if shrdAu == shrdBu
        shrd = shrdAu
    else
        for i in shrdBu
            shrd = shrdAu
            if !(i in shrdAu)
                push!(shrd,i)
            end
        end
    end
    #cshrdA = countmap(shrdA)
    #cshrdB = countmap(shrdB)
    for i in shrd
        ca = count(x->x==i,shrdA)
        cb = count(x->x==i,shrdB)
        push!(shrdAB, (i, ca,cb ))
    end
    cnt = 1
    cntb = 1
    for i in shrdAB
        if i[2] == 0 || i[3] == 0 # Put condition for when one of a or b is zero
            nothing
        else
            for j in 1:i[2]
                ans = @. sampsA[j]*sampsB[1:i[3]]
                push!(amps,ans)
                indxa = repeat([sindxA[j][lena]],i[3])
                #for j in indxA[i][lena]
                indxa1 = vcat.(sindxB[1:i[3]],indxa)
                push!(qq,indxa1)
            end
        end
        deleteat!(sindxA,1:i[2])
        deleteat!(sindxB,1:i[3])
        deleteat!(sampsA,1:i[2])
        deleteat!(sampsB,1:i[3])
        #cntb = cnt
    end
    indxsol = collect(Iterators.flatten(qq))
    sol = collect(Iterators.flatten(amps))
    return indxsol,sol
end

#Take care of dimesion factors after gluing
function tensorGlueTet3D(tensorB,tensorA,posnB,posnA)
    indx, amps = tensorGlue(tensorB,tensorA,posnB,posnA)
    face = getindex.(indx, [posnB])
    ans = complex(ones(length(face)))
    for i in 1:length(face)
        ans[i] = prod(visqrt.(face[i]))
    end
    ampsN = @. amps / ans
    ampsN = real(ampsN)+imag(ampsN)
    return indx, ampsN
end

# Summing a tensor  over indices labelled posnN
function tensorSum(ampJ,posnN)
    indx = ampJ[1]
    amps = ampJ[2]
    lenS = collect(1:length(indx[1]))
    deleteat!(lenS,sort(posnN))
    #if length(posnN) > 1   deleteat!(lenS,sort(posnN,dims=2)) else  deleteat!(lenS,sort(posnN)) end
    indxef = getindex.(indx,[lenS]) # get all spins without N
    #indxefu = unique(indxef)
    tt = sortperm(indxef) # sort them spins
    indxef = indxef[tt] # apply sort on spins
    amps = amps[tt]  # apply sort on amps
    #fc = findall(x-> x[lenS]==ans ,indx)
    qq = []
    indq = []
    fc = sortperm(indxef)
    indxN = indxef[fc]
    ampsN = amps[fc]

    for i in 2:length(indxN)
        if indxN[i] == indxN[i-1]
            ampsN[i] += ampsN[i-1]
        elseif numchop(ampsN[i-1]) != 0
            push!(indq,indxN[i-1])
            push!(qq,ampsN[i-1])
        end
    end
    push!(indq,indxN[end])
    push!(qq,ampsN[end])
    return indq, qq
end

# Take care of dimension factors when summing. This makes sure Pachner 3-2 move works
# For now this only works for summing over one index-- we can change this easily
function tensorSumTet3D(tensor,posnN)
    indx = tensor[1]
    amps = tensor[2]
    blkdims = getindex.(indx,posnN)
    ampsN = amps .*visqrt.(blkdims)
    ampsN2 = real(ampsN) - imag(ampsN)
    data = indx, ampsN2
    ans = tensorSum(data,posnN)
    return ans
end

# Performs an F-move on a face.. equivalent to 2-2 Pachner move -- this uses Fsymbol
function tensor22move(tensor,face)# place middle edge at last position
    # assert face contains 5 elements
    indx = tensor[1] # spins of TB
    amps = tensor[2]
    flast = face[end]
    indxlast = getindex.(indx, [flast])
    #for i in 1:4
    #    if face[i]>flast
    #        face[i] = face[i]-1
    #    end
    #end
    famps = []
    findx = []
    for i in 1:length(indx)
        #iMod = copy(indx[i])
        j1 = indx[i][face[1]]; j2 = indx[i][face[2]]; j3 = indx[i][face[3]]; j4 = indx[i][face[4]]; j5 = indx[i][face[5]]
    #    #ans = 0#amps[i]
    #    ind = zeros(length(indx[1]))
        for j in 0.:0.5:y
            #iMod = deepcopy(indxf1u)
            if delta(j1,j3,j) != 0 && delta(j2,j4,j) != 0 && Fsymb(j1,j3,j,j4,j2,j5) != 0
                iMod = copy(indx[i])
                ampsA = numchop(amps[i] * Fsymb(j1,j3,j,j4,j2,j5))
                push!(famps,ampsA)
                iMod[flast] = j
                push!(findx,iMod)
                #fc = findall(x-> x == iMod,indxf1 )
            end
        end
    end
    #indxf1 = getindex.(indx, [ff1]) # get all spins without spin N to be summed over
    tt = sortperm(findx) # sort them spins
    indxf1 = findx[tt] # apply sort on spins
    famps = famps[tt]  # apply sort on amps
    #fc = findall(x-> x[lenS]==ans ,indx)
    qq = []
    indxeff = []
    for i in 2:length(indxf1)
        if indxf1[i] == indxf1[i-1]
            famps[i] += famps[i-1]
        elseif numchop(famps[i-1]) != 0
            push!(indxeff,indxf1[i-1])
            push!(qq,famps[i-1])
        end
    end
    push!(indxeff,indxf1[end])
    push!(qq,famps[end])
    #indxeff = unique(indxf1)
    return indxeff, qq
end

export tensor22moveA, tensor22moveB, permuteInd, tensorPermute

function swap2(vec,a,b)
    vec[a],vec[b] = vec[b], vec[a]
    return vec
end

function tensor22moveA(tensor,posF)
    glu = tensorGlue(tensor,dataFsymb(),posF,[1,5,2,4,6])
    n = posF[end]
    m = length(glu[1][1])
    swp = @. swap2(glu[1],n,m)
    tensorN = swp, glu[2]
    ans = tensorSum(tensorN,[length(glu[1][1])])
    return ans
end

function tensor22moveB(tensor,posF)
    glu = tensorGlueTet3D(tensor,dataTet(),posF,[1,5,2,4,6])
    n = posF[end]
    m = length(glu[1][1])
    swp = @. swap2(glu[1],n,m)
    tensorN = swp, glu[2]
    ans = tensorSumTet3D(tensorN,[m])
    return ans
end
# For 3-1 move, we will use SVD -- i.e. use the function fullSplitTet3D or fullSplit3D

function permuteInd(vec)
    perm = [5,1,11,2,6,3,10,12,7,8,4,9]
    nvec = vec[perm]
    return nvec
end

function tensorPermute(tensor)
    indx = tensor[1]
    indxN = permuteInd.(indx)
    return indxN , tensor[2]
end

end # module


text:
@testset "length - tests" begin
    for (r,b) in ((DynamicAxis(10), OneTo(10)),
                  (mrange(1.0, step=2.0, stop=10.0), 1.0:2.0:10.0),
                  (mrange(UInt32(1), step=UInt32(2), stop=UInt32(10)), UInt32(1):UInt32(2):UInt32(10)),
                 )

        @testset "length($r)" begin
            @test @inferred(length(r)) == length(b)
            @test @inferred(length(r)) == length(b)

            @test length(GapRange(1:5, 6:10)) == 10
        end
    end

    @testset "length($(mrange))" begin
        @test length(mrange(.1, step=.1, stop=.3)) == 3
        @test length(mrange(1.1, step=1.1, stop=3.3)) == 3
        @test length(mrange(1.1, step=1.3, stop=3)) == 2
        @test length(mrange(1, step=1, stop=1.8)) == 1
        @test length(mrange(1, step=.2, stop=2)) == 6
        @test length(mrange(1., step=.2, stop=2.)) == 6
        @test length(mrange(2, step=-.2, stop=1)) == 6
        @test length(mrange(2., step=-.2, stop=1.)) == 6
        @test length(mrange(2, step=.2, stop=1)) == 0
        @test length(mrange(2., step=.2, stop=1.)) == 0

        @test length(mrange(1, 0)) == 0
        @test length(mrange(0.0, -0.5)) == 0
        @test length(mrange(1, step=2, stop=0)) == 0
        @test length(mrange(Char(0), Char(0x001fffff))) == 2097152
        @test length(mrange(typemax(UInt64)//one(UInt64), step=1, stop=typemax(UInt64)//one(UInt64))) == 1
    end

    @testset "length(mrange) with typemin/typemax" begin
        let r = mrange(typemin(Int64), step=2, stop=typemax(Int64)), s = mrange(typemax(Int64), step=-2, stop=typemin(Int64))
            @test first(r) == typemin(Int64)
            @test last(r) == (typemax(Int64)-1)
            #@test_throws OverflowError length(r)

            @test first(s) == typemax(Int64)
            @test last(s) == (typemin(Int64)+1)
            #@test_throws OverflowError length(s)
        end

        @test length(mrange(typemin(Int64), step=3, stop=typemax(Int64))) == 6148914691236517206
        @test length(mrange(typemax(Int64), step=-3, stop=typemin(Int64))) == 6148914691236517206

        #= No static type for big
        for s in 3:100
            @test length(mrange(typemin(Int), step=s, stop=typemax(Int))) == length(big(typemin(Int)):big(s):big(typemax(Int)))
            @test length(mrange(typemax(Int), step=-s, stop=typemin(Int))) == length(big(typemax(Int)):big(-s):big(typemin(Int)))
        end
        =#

        #= TODO
        @test length(mrange(UInt(1), step=UInt(1), stop=UInt(0))) == 0
        @test length(mrange(typemax(UInt), step=UInt(1), stop=(typemax(UInt)-1))) == 0
        @test length(mrange(typemax(UInt), step=UInt(2), stop=(typemax(UInt)-1))) == 0
        @test length((mrange(typemin(Int)+3, step=5, stop=(typemin(Int)+1)))) == 0
        =#
    end

    # avoiding intermediate overflow (#5065)
    @test length(mrange(1, step=4, stop=typemax(Int))) == div(typemax(Int),4) + 1

    # issue #6364
    @test length((mrange(1, 64))*(pi/5)) == 64

    @testset "overflow in length($mrange)" begin
        Tset = Int === Int64 ? (Int,UInt,Int128,UInt128) :
                               (Int,UInt,Int64,UInt64,Int128, UInt128)
        for T in Tset
            @test_throws OverflowError length(zero(T):typemax(T))
            @test_throws OverflowError length(typemin(T):typemax(T))
            @test_throws OverflowError length(zero(T):one(T):typemax(T))
            @test_throws OverflowError length(typemin(T):one(T):typemax(T))
            if T <: Signed
                @test_throws OverflowError length(-one(T):typemax(T)-one(T))
                @test_throws OverflowError length(-one(T):one(T):typemax(T)-one(T))
            end
        end
    end
    # small ints
    @test length(DynamicAxis(10)) == 10

    @test length(DynamicAxis(UInt8(10))) == 10
end


text:
using Unicode: normalize

using WordTokenizers: TokenBuffer, isdone, flush!, character, spaces, atoms

function isinvalid(c)
  if c == '\t' || c == '\n' || c == '\r'
    return false
  end
  c == Char(0) || c == Char(0xfffd) || iscntrl(c)
end

"""
    invalid(ts)

ignore invalid characters such like U+0000, U+fffd, and Control characters
"""
function invalid(ts)
  isinvalid(ts[]) || return false
  ts.idx += 1
  return true
end

ischinese(c) =
  Char(0x4e00)  ≤ c ≤ Char(0x9fff)  ||
  Char(0x3400)  ≤ c ≤ Char(0x4dbf)  ||
  Char(0x20000) ≤ c ≤ Char(0x2a6df) ||
  Char(0x2a700) ≤ c ≤ Char(0x2b73f) ||
  Char(0x2b740) ≤ c ≤ Char(0x2b81f) ||
  Char(0x2b820) ≤ c ≤ Char(0x2ceaf) ||
  Char(0xf900)  ≤ c ≤ Char(0xfaff)  ||
  Char(0x2f800) ≤ c ≤ Char(0x2fa1f)

"""
    chinese(ts)

separate on chinese characters
"""
function chinese(ts)
  ischinese(ts[]) || return false
  flush!(ts, string(ts[]))
  ts.idx += 1
  return true
end

isbertpunct(c) = ispunct(c) ||
  Char(33)  ≤ c ≤ Char(47)  ||
  Char(58)  ≤ c ≤ Char(64)  ||
  Char(91)  ≤ c ≤ Char(96)  ||
  Char(123) ≤ c ≤ Char(126)

function bertpunct(ts)
  isbertpunct(ts[]) || return false
  flush!(ts, string(ts[]))
  ts.idx += 1
  return true
end

iscatemn(c) = Base.Unicode.category_code(c) == Base.Unicode.UTF8PROC_CATEGORY_MN
function catemn(ts)
  iscatemn(ts[]) || return false
  ts.idx += 1
  return true
end

#=
bert basic tokenizer pipeline
skip 1. convert to unicode
2. clean text
3. handle chinese character
4. tokenize with white space
5. if lower case : lower, NFD normalize, skip 'Mn' unicode on each tokens
6. split each token with punct and punct remain

=#
function tokenise(input, ::Val{lower}) where lower
  ts = TokenBuffer(lower ? normalize(lowercase(input), :NFD) : input)
  while !isdone(ts)
    (lower && catemn(ts)) ||
    invalid(ts)   ||
    chinese(ts)   ||
    spaces(ts)    ||
    bertpunct(ts) ||
    character(ts)
  end
  return ts.tokens
end

"""

    bert_uncased_tokenizer(input)

google bert tokenizer which do lower case on input before tokenization.
"""
bert_uncased_tokenizer(input) = tokenise(input, Val(true))

"""
    bert_cased_tokenizer(input)

google bert tokenizer which remain the case during tokenization. Recommended for multi-lingual data.
"""
bert_cased_tokenizer(input) = tokenise(input, Val(false))


text:
module BubblerFrostHelper

using ..Ahorn, Maple

@mapdef Entity "FrostHelper/Bubbler" Bubbler(x::Integer, y::Integer, visible::Bool=true, color::String="White")

const colors = sort(collect(keys(Ahorn.XNAColors.colors)))

Ahorn.editingOptions(entity::Bubbler) = Dict{String, Any}(
    "color" => colors
)

const placements = Ahorn.PlacementDict(
    "Bubbler (Frost Helper)" => Ahorn.EntityPlacement(
        Bubbler,
        "point",
        Dict{String, Any}(),
        function(entity::Bubbler)
            entity.data["nodes"] = [
                (Int(entity.data["x"]) + 32, Int(entity.data["y"])),
                (Int(entity.data["x"]) + 64, Int(entity.data["y"]))
            ]
        end
    )
)

Ahorn.nodeLimits(entity::Bubbler) = length(get(entity.data, "nodes", [])) == 2 ? (2, 2) : (0, 0)

sprite = "characters/player/bubble.png"

function Ahorn.selection(entity::Bubbler)
    x, y = Ahorn.position(entity)

    if haskey(entity.data, "nodes")
        controllX, controllY = Int.(entity.data["nodes"][1])
        endX, endY = Int.(entity.data["nodes"][2])

        return [
            Ahorn.getSpriteRectangle(sprite, x, y),
            Ahorn.getSpriteRectangle(sprite, controllX, controllY),
            Ahorn.getSpriteRectangle(sprite, endX, endY)
        ]

    else
        return Ahorn.getSpriteRectangle(sprite, x, y)
    end
end

function Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::Bubbler)
    px, py = Ahorn.position(entity)
    nodes = get(entity.data, "nodes", Tuple{Int, Int}[])

    for node in nodes
        nx, ny = Int.(node)

        Ahorn.drawArrow(ctx, px, py, nx, ny, Ahorn.colors.selection_selected_fc, headLength=6)
        Ahorn.drawSprite(ctx, sprite, nx, ny)
        px, py = nx, ny
    end
end

Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::Bubbler, room::Maple.Room) = Ahorn.drawSprite(ctx, sprite, 0, 0)

end

max_stars_repo_name: JaThePlayer/FrostHelper

max_issues_repo_name: JaThePlayer/FrostHelper

max_forks_repo_name: JaThePlayer/FrostHelper


text:
#=
Examples of tuples.
=#

# Tuple of strings
people = ("Marge", "Homer", "Bart", "Lisa")
println(people)
println(people[2])

# Tuple with one item
item = (2,)
println(item[1])

# Named tuple
vehicle = (make="Ford", model="Bronco", year=2021)
println(vehicle.make)
println(vehicle[1])
println(vehicle.model)
println(vehicle.year)

max_stars_repo_name: wigging/julia-computing

max_issues_repo_name: wigging/julia-computing

max_forks_repo_name: wigging/julia-computing


text:
using Plots
using DelimitedFiles

include("options.jl")

print("Plotting results using $blas_num_threads BLAS thread")
blas_num_threads > 1 ? println("s") : println()

println("Plotting results using $omp_num_threads OpenBLAS threads")
println("Maximum DMRG bond dimension is set to $maxdim.")

seperator = "#"^70

times = Float64[]

filename_suffix_maxdim = "_maxdim_$(maxdim)"

N = length(omp_num_threads)
for j in 1:N
  omp_num_thread = omp_num_threads[j]
  println(seperator)
  println("Plot C++ benchmark in path $(@__DIR__) with $omp_num_thread OpenMP threads.")
  println()

  # Printing results
  # TODO: add version number to data file name
  filename_suffix = filename_suffix_maxdim *
                    "_omp_num_threads_$(omp_num_thread)"

  filename = joinpath(@__DIR__, "data",
                      "data" * filename_suffix * ".txt")

  println("Reading results from $filename")
  println()
  time = readdlm(filename)[]
  push!(times, time)
end

title = "DMRG, 2D Hubbard model\n"
title *= "Hybrid real and momentum space\n"
title *= "Nx, Ny = $Nx, $Ny\n"
title *= "U = $U, maxdim = $maxdim"

speedup_ratio = times[1] ./ times

p = plot(omp_num_threads, speedup_ratio;
         title = title,
         legendfontsize = 14,
         xlabel = "n (number of OpenMP threads)",
         ylabel = "Speedup ratio time₁/timeₙ",
         xtickfontsize = 14,
         ytickfontsize = 14,
         xguidefontsize = 14,
         yguidefontsize = 14,
         line = (:solid, 4),
         marker = (:dot, 8),
         legend = :bottomright,
         label = "Actual C++")

# Plot the ideal speedup ratio
max_ratio = Int(round(maximum(speedup_ratio), RoundUp))
plot!(p, 1:max_ratio, 1:max_ratio;
      line = (:solid, 4),
      label = "Ideal")

savefig(p, joinpath(@__DIR__, "plots",
                    "plot" * filename_suffix_maxdim * ".png"))