--- /dev/null
+(puthash "\\exclamdown" "¡" julia-latexsubs)
+(puthash "\\sterling" "£" julia-latexsubs)
+(puthash "\\yen" "¥" julia-latexsubs)
+(puthash "\\brokenbar" "¦" julia-latexsubs)
+(puthash "\\S" "§" julia-latexsubs)
+(puthash "\\copyright" "©" julia-latexsubs)
+(puthash "\\:copyright:" "©" julia-latexsubs)
+(puthash "\\ordfeminine" "ª" julia-latexsubs)
+(puthash "\\neg" "¬" julia-latexsubs)
+(puthash "\\:registered:" "®" julia-latexsubs)
+(puthash "\\circledR" "®" julia-latexsubs)
+(puthash "\\highminus" "¯" julia-latexsubs)
+(puthash "\\degree" "°" julia-latexsubs)
+(puthash "\\pm" "±" julia-latexsubs)
+(puthash "\\^2" "²" julia-latexsubs)
+(puthash "\\^3" "³" julia-latexsubs)
+(puthash "\\P" "¶" julia-latexsubs)
+(puthash "\\cdotp" "·" julia-latexsubs)
+(puthash "\\^1" "¹" julia-latexsubs)
+(puthash "\\ordmasculine" "º" julia-latexsubs)
+(puthash "\\1/4" "¼" julia-latexsubs)
+(puthash "\\1/2" "½" julia-latexsubs)
+(puthash "\\3/4" "¾" julia-latexsubs)
+(puthash "\\questiondown" "¿" julia-latexsubs)
+(puthash "\\AA" "Å" julia-latexsubs)
+(puthash "\\AE" "Æ" julia-latexsubs)
+(puthash "\\DH" "Ð" julia-latexsubs)
+(puthash "\\times" "×" julia-latexsubs)
+(puthash "\\O" "Ø" julia-latexsubs)
+(puthash "\\TH" "Þ" julia-latexsubs)
+(puthash "\\ss" "ß" julia-latexsubs)
+(puthash "\\aa" "å" julia-latexsubs)
+(puthash "\\ae" "æ" julia-latexsubs)
+(puthash "\\eth" "ð" julia-latexsubs)
+(puthash "\\dh" "ð" julia-latexsubs)
+(puthash "\\div" "÷" julia-latexsubs)
+(puthash "\\o" "ø" julia-latexsubs)
+(puthash "\\th" "þ" julia-latexsubs)
+(puthash "\\DJ" "Đ" julia-latexsubs)
+(puthash "\\dj" "đ" julia-latexsubs)
+(puthash "\\hbar" "ħ" julia-latexsubs)
+(puthash "\\imath" "ı" julia-latexsubs)
+(puthash "\\L" "Ł" julia-latexsubs)
+(puthash "\\l" "ł" julia-latexsubs)
+(puthash "\\NG" "Ŋ" julia-latexsubs)
+(puthash "\\ng" "ŋ" julia-latexsubs)
+(puthash "\\OE" "Œ" julia-latexsubs)
+(puthash "\\oe" "œ" julia-latexsubs)
+(puthash "\\hvlig" "ƕ" julia-latexsubs)
+(puthash "\\nrleg" "ƞ" julia-latexsubs)
+(puthash "\\Zbar" "Ƶ" julia-latexsubs)
+(puthash "\\doublepipe" "ǂ" julia-latexsubs)
+(puthash "\\jmath" "ȷ" julia-latexsubs)
+(puthash "\\trna" "ɐ" julia-latexsubs)
+(puthash "\\trnsa" "ɒ" julia-latexsubs)
+(puthash "\\openo" "ɔ" julia-latexsubs)
+(puthash "\\rtld" "ɖ" julia-latexsubs)
+(puthash "\\schwa" "ə" julia-latexsubs)
+(puthash "\\pgamma" "ɣ" julia-latexsubs)
+(puthash "\\pbgam" "ɤ" julia-latexsubs)
+(puthash "\\trnh" "ɥ" julia-latexsubs)
+(puthash "\\btdl" "ɬ" julia-latexsubs)
+(puthash "\\rtll" "ɭ" julia-latexsubs)
+(puthash "\\trnm" "ɯ" julia-latexsubs)
+(puthash "\\trnmlr" "ɰ" julia-latexsubs)
+(puthash "\\ltlmr" "ɱ" julia-latexsubs)
+(puthash "\\ltln" "ɲ" julia-latexsubs)
+(puthash "\\rtln" "ɳ" julia-latexsubs)
+(puthash "\\clomeg" "ɷ" julia-latexsubs)
+(puthash "\\ltphi" "ɸ" julia-latexsubs)
+(puthash "\\trnr" "ɹ" julia-latexsubs)
+(puthash "\\trnrl" "ɺ" julia-latexsubs)
+(puthash "\\rttrnr" "ɻ" julia-latexsubs)
+(puthash "\\rl" "ɼ" julia-latexsubs)
+(puthash "\\rtlr" "ɽ" julia-latexsubs)
+(puthash "\\fhr" "ɾ" julia-latexsubs)
+(puthash "\\rtls" "ʂ" julia-latexsubs)
+(puthash "\\esh" "ʃ" julia-latexsubs)
+(puthash "\\trnt" "ʇ" julia-latexsubs)
+(puthash "\\rtlt" "ʈ" julia-latexsubs)
+(puthash "\\pupsil" "ʊ" julia-latexsubs)
+(puthash "\\pscrv" "ʋ" julia-latexsubs)
+(puthash "\\invv" "ʌ" julia-latexsubs)
+(puthash "\\invw" "ʍ" julia-latexsubs)
+(puthash "\\trny" "ʎ" julia-latexsubs)
+(puthash "\\rtlz" "ʐ" julia-latexsubs)
+(puthash "\\yogh" "ʒ" julia-latexsubs)
+(puthash "\\glst" "ʔ" julia-latexsubs)
+(puthash "\\reglst" "ʕ" julia-latexsubs)
+(puthash "\\inglst" "ʖ" julia-latexsubs)
+(puthash "\\turnk" "ʞ" julia-latexsubs)
+(puthash "\\dyogh" "ʤ" julia-latexsubs)
+(puthash "\\tesh" "ʧ" julia-latexsubs)
+(puthash "\\^h" "ʰ" julia-latexsubs)
+(puthash "\\^j" "ʲ" julia-latexsubs)
+(puthash "\\^r" "ʳ" julia-latexsubs)
+(puthash "\\^w" "ʷ" julia-latexsubs)
+(puthash "\\^y" "ʸ" julia-latexsubs)
+(puthash "\\rasp" "ʼ" julia-latexsubs)
+(puthash "\\verts" "ˈ" julia-latexsubs)
+(puthash "\\verti" "ˌ" julia-latexsubs)
+(puthash "\\lmrk" "ː" julia-latexsubs)
+(puthash "\\hlmrk" "ˑ" julia-latexsubs)
+(puthash "\\sbrhr" "˒" julia-latexsubs)
+(puthash "\\sblhr" "˓" julia-latexsubs)
+(puthash "\\rais" "˔" julia-latexsubs)
+(puthash "\\low" "˕" julia-latexsubs)
+(puthash "\\u" "˘" julia-latexsubs)
+(puthash "\\tildelow" "˜" julia-latexsubs)
+(puthash "\\^l" "ˡ" julia-latexsubs)
+(puthash "\\^s" "ˢ" julia-latexsubs)
+(puthash "\\^x" "ˣ" julia-latexsubs)
+(puthash "\\grave" "̀" julia-latexsubs)
+(puthash "\\acute" "́" julia-latexsubs)
+(puthash "\\hat" "̂" julia-latexsubs)
+(puthash "\\tilde" "̃" julia-latexsubs)
+(puthash "\\bar" "̄" julia-latexsubs)
+(puthash "\\overbar" "̅" julia-latexsubs)
+(puthash "\\breve" "̆" julia-latexsubs)
+(puthash "\\dot" "̇" julia-latexsubs)
+(puthash "\\ddot" "̈" julia-latexsubs)
+(puthash "\\ovhook" "̉" julia-latexsubs)
+(puthash "\\ocirc" "̊" julia-latexsubs)
+(puthash "\\H" "̋" julia-latexsubs)
+(puthash "\\check" "̌" julia-latexsubs)
+(puthash "\\candra" "̐" julia-latexsubs)
+(puthash "\\oturnedcomma" "̒" julia-latexsubs)
+(puthash "\\ocommatopright" "̕" julia-latexsubs)
+(puthash "\\droang" "̚" julia-latexsubs)
+(puthash "\\palh" "̡" julia-latexsubs)
+(puthash "\\rh" "̢" julia-latexsubs)
+(puthash "\\c" "̧" julia-latexsubs)
+(puthash "\\k" "̨" julia-latexsubs)
+(puthash "\\sbbrg" "̪" julia-latexsubs)
+(puthash "\\wideutilde" "̰" julia-latexsubs)
+(puthash "\\underbar" "̲" julia-latexsubs)
+(puthash "\\strike" "̶" julia-latexsubs)
+(puthash "\\sout" "̶" julia-latexsubs)
+(puthash "\\not" "̸" julia-latexsubs)
+(puthash "\\underleftrightarrow" "͍" julia-latexsubs)
+(puthash "\\Alpha" "Α" julia-latexsubs)
+(puthash "\\Beta" "Β" julia-latexsubs)
+(puthash "\\Gamma" "Γ" julia-latexsubs)
+(puthash "\\Delta" "Δ" julia-latexsubs)
+(puthash "\\Epsilon" "Ε" julia-latexsubs)
+(puthash "\\Zeta" "Ζ" julia-latexsubs)
+(puthash "\\Eta" "Η" julia-latexsubs)
+(puthash "\\Theta" "Θ" julia-latexsubs)
+(puthash "\\Iota" "Ι" julia-latexsubs)
+(puthash "\\Kappa" "Κ" julia-latexsubs)
+(puthash "\\Lambda" "Λ" julia-latexsubs)
+(puthash "\\upMu" "Μ" julia-latexsubs)
+(puthash "\\upNu" "Ν" julia-latexsubs)
+(puthash "\\Xi" "Ξ" julia-latexsubs)
+(puthash "\\upOmicron" "Ο" julia-latexsubs)
+(puthash "\\Pi" "Π" julia-latexsubs)
+(puthash "\\Rho" "Ρ" julia-latexsubs)
+(puthash "\\Sigma" "Σ" julia-latexsubs)
+(puthash "\\Tau" "Τ" julia-latexsubs)
+(puthash "\\Upsilon" "Υ" julia-latexsubs)
+(puthash "\\Phi" "Φ" julia-latexsubs)
+(puthash "\\Chi" "Χ" julia-latexsubs)
+(puthash "\\Psi" "Ψ" julia-latexsubs)
+(puthash "\\Omega" "Ω" julia-latexsubs)
+(puthash "\\alpha" "α" julia-latexsubs)
+(puthash "\\beta" "β" julia-latexsubs)
+(puthash "\\gamma" "γ" julia-latexsubs)
+(puthash "\\delta" "δ" julia-latexsubs)
+(puthash "\\upepsilon" "ε" julia-latexsubs)
+(puthash "\\varepsilon" "ε" julia-latexsubs)
+(puthash "\\zeta" "ζ" julia-latexsubs)
+(puthash "\\eta" "η" julia-latexsubs)
+(puthash "\\theta" "θ" julia-latexsubs)
+(puthash "\\iota" "ι" julia-latexsubs)
+(puthash "\\kappa" "κ" julia-latexsubs)
+(puthash "\\lambda" "λ" julia-latexsubs)
+(puthash "\\mu" "μ" julia-latexsubs)
+(puthash "\\nu" "ν" julia-latexsubs)
+(puthash "\\xi" "ξ" julia-latexsubs)
+(puthash "\\upomicron" "ο" julia-latexsubs)
+(puthash "\\pi" "π" julia-latexsubs)
+(puthash "\\rho" "ρ" julia-latexsubs)
+(puthash "\\varsigma" "ς" julia-latexsubs)
+(puthash "\\sigma" "σ" julia-latexsubs)
+(puthash "\\tau" "τ" julia-latexsubs)
+(puthash "\\upsilon" "υ" julia-latexsubs)
+(puthash "\\varphi" "φ" julia-latexsubs)
+(puthash "\\chi" "χ" julia-latexsubs)
+(puthash "\\psi" "ψ" julia-latexsubs)
+(puthash "\\omega" "ω" julia-latexsubs)
+(puthash "\\upvarbeta" "ϐ" julia-latexsubs)
+(puthash "\\vartheta" "ϑ" julia-latexsubs)
+(puthash "\\phi" "ϕ" julia-latexsubs)
+(puthash "\\varpi" "ϖ" julia-latexsubs)
+(puthash "\\upoldKoppa" "Ϙ" julia-latexsubs)
+(puthash "\\upoldkoppa" "ϙ" julia-latexsubs)
+(puthash "\\Stigma" "Ϛ" julia-latexsubs)
+(puthash "\\upstigma" "ϛ" julia-latexsubs)
+(puthash "\\Digamma" "Ϝ" julia-latexsubs)
+(puthash "\\digamma" "ϝ" julia-latexsubs)
+(puthash "\\Koppa" "Ϟ" julia-latexsubs)
+(puthash "\\upkoppa" "ϟ" julia-latexsubs)
+(puthash "\\Sampi" "Ϡ" julia-latexsubs)
+(puthash "\\upsampi" "ϡ" julia-latexsubs)
+(puthash "\\varkappa" "ϰ" julia-latexsubs)
+(puthash "\\varrho" "ϱ" julia-latexsubs)
+(puthash "\\varTheta" "ϴ" julia-latexsubs)
+(puthash "\\epsilon" "ϵ" julia-latexsubs)
+(puthash "\\backepsilon" "϶" julia-latexsubs)
+(puthash "\\^A" "ᴬ" julia-latexsubs)
+(puthash "\\^B" "ᴮ" julia-latexsubs)
+(puthash "\\^D" "ᴰ" julia-latexsubs)
+(puthash "\\^E" "ᴱ" julia-latexsubs)
+(puthash "\\^G" "ᴳ" julia-latexsubs)
+(puthash "\\^H" "ᴴ" julia-latexsubs)
+(puthash "\\^I" "ᴵ" julia-latexsubs)
+(puthash "\\^J" "ᴶ" julia-latexsubs)
+(puthash "\\^K" "ᴷ" julia-latexsubs)
+(puthash "\\^L" "ᴸ" julia-latexsubs)
+(puthash "\\^M" "ᴹ" julia-latexsubs)
+(puthash "\\^N" "ᴺ" julia-latexsubs)
+(puthash "\\^O" "ᴼ" julia-latexsubs)
+(puthash "\\^P" "ᴾ" julia-latexsubs)
+(puthash "\\^R" "ᴿ" julia-latexsubs)
+(puthash "\\^T" "ᵀ" julia-latexsubs)
+(puthash "\\^U" "ᵁ" julia-latexsubs)
+(puthash "\\^W" "ᵂ" julia-latexsubs)
+(puthash "\\^a" "ᵃ" julia-latexsubs)
+(puthash "\\^alpha" "ᵅ" julia-latexsubs)
+(puthash "\\^b" "ᵇ" julia-latexsubs)
+(puthash "\\^d" "ᵈ" julia-latexsubs)
+(puthash "\\^e" "ᵉ" julia-latexsubs)
+(puthash "\\^epsilon" "ᵋ" julia-latexsubs)
+(puthash "\\^g" "ᵍ" julia-latexsubs)
+(puthash "\\^k" "ᵏ" julia-latexsubs)
+(puthash "\\^m" "ᵐ" julia-latexsubs)
+(puthash "\\^o" "ᵒ" julia-latexsubs)
+(puthash "\\^p" "ᵖ" julia-latexsubs)
+(puthash "\\^t" "ᵗ" julia-latexsubs)
+(puthash "\\^u" "ᵘ" julia-latexsubs)
+(puthash "\\^v" "ᵛ" julia-latexsubs)
+(puthash "\\^beta" "ᵝ" julia-latexsubs)
+(puthash "\\^gamma" "ᵞ" julia-latexsubs)
+(puthash "\\^delta" "ᵟ" julia-latexsubs)
+(puthash "\\^phi" "ᵠ" julia-latexsubs)
+(puthash "\\^chi" "ᵡ" julia-latexsubs)
+(puthash "\\_i" "ᵢ" julia-latexsubs)
+(puthash "\\_r" "ᵣ" julia-latexsubs)
+(puthash "\\_u" "ᵤ" julia-latexsubs)
+(puthash "\\_v" "ᵥ" julia-latexsubs)
+(puthash "\\_beta" "ᵦ" julia-latexsubs)
+(puthash "\\_gamma" "ᵧ" julia-latexsubs)
+(puthash "\\_rho" "ᵨ" julia-latexsubs)
+(puthash "\\_phi" "ᵩ" julia-latexsubs)
+(puthash "\\_chi" "ᵪ" julia-latexsubs)
+(puthash "\\^c" "ᶜ" julia-latexsubs)
+(puthash "\\^f" "ᶠ" julia-latexsubs)
+(puthash "\\^iota" "ᶥ" julia-latexsubs)
+(puthash "\\^Phi" "ᶲ" julia-latexsubs)
+(puthash "\\^z" "ᶻ" julia-latexsubs)
+(puthash "\\^theta" "ᶿ" julia-latexsubs)
+(puthash "\\enspace" " " julia-latexsubs)
+(puthash "\\quad" " " julia-latexsubs)
+(puthash "\\thickspace" " " julia-latexsubs)
+(puthash "\\thinspace" " " julia-latexsubs)
+(puthash "\\hspace" " " julia-latexsubs)
+(puthash "\\endash" "–" julia-latexsubs)
+(puthash "\\emdash" "—" julia-latexsubs)
+(puthash "\\Vert" "‖" julia-latexsubs)
+(puthash "\\lq" "‘" julia-latexsubs)
+(puthash "\\rq" "’" julia-latexsubs)
+(puthash "\\reapos" "‛" julia-latexsubs)
+(puthash "\\quotedblleft" "“" julia-latexsubs)
+(puthash "\\quotedblright" "”" julia-latexsubs)
+(puthash "\\dagger" "†" julia-latexsubs)
+(puthash "\\ddagger" "‡" julia-latexsubs)
+(puthash "\\bullet" "•" julia-latexsubs)
+(puthash "\\dots" "…" julia-latexsubs)
+(puthash "\\ldots" "…" julia-latexsubs)
+(puthash "\\perthousand" "‰" julia-latexsubs)
+(puthash "\\pertenthousand" "‱" julia-latexsubs)
+(puthash "\\prime" "′" julia-latexsubs)
+(puthash "\\pprime" "″" julia-latexsubs)
+(puthash "\\ppprime" "‴" julia-latexsubs)
+(puthash "\\backprime" "‵" julia-latexsubs)
+(puthash "\\backpprime" "‶" julia-latexsubs)
+(puthash "\\backppprime" "‷" julia-latexsubs)
+(puthash "\\guilsinglleft" "‹" julia-latexsubs)
+(puthash "\\guilsinglright" "›" julia-latexsubs)
+(puthash "\\:bangbang:" "‼" julia-latexsubs)
+(puthash "\\tieconcat" "⁀" julia-latexsubs)
+(puthash "\\:interrobang:" "⁉" julia-latexsubs)
+(puthash "\\pppprime" "⁗" julia-latexsubs)
+(puthash "\\tricolon" "⁝" julia-latexsubs)
+(puthash "\\nolinebreak" "\u2060" julia-latexsubs)
+(puthash "\\^0" "⁰" julia-latexsubs)
+(puthash "\\^i" "ⁱ" julia-latexsubs)
+(puthash "\\^4" "⁴" julia-latexsubs)
+(puthash "\\^5" "⁵" julia-latexsubs)
+(puthash "\\^6" "⁶" julia-latexsubs)
+(puthash "\\^7" "⁷" julia-latexsubs)
+(puthash "\\^8" "⁸" julia-latexsubs)
+(puthash "\\^9" "⁹" julia-latexsubs)
+(puthash "\\^+" "⁺" julia-latexsubs)
+(puthash "\\^-" "⁻" julia-latexsubs)
+(puthash "\\^=" "⁼" julia-latexsubs)
+(puthash "\\^(" "⁽" julia-latexsubs)
+(puthash "\\^)" "⁾" julia-latexsubs)
+(puthash "\\^n" "ⁿ" julia-latexsubs)
+(puthash "\\_0" "₀" julia-latexsubs)
+(puthash "\\_1" "₁" julia-latexsubs)
+(puthash "\\_2" "₂" julia-latexsubs)
+(puthash "\\_3" "₃" julia-latexsubs)
+(puthash "\\_4" "₄" julia-latexsubs)
+(puthash "\\_5" "₅" julia-latexsubs)
+(puthash "\\_6" "₆" julia-latexsubs)
+(puthash "\\_7" "₇" julia-latexsubs)
+(puthash "\\_8" "₈" julia-latexsubs)
+(puthash "\\_9" "₉" julia-latexsubs)
+(puthash "\\_+" "₊" julia-latexsubs)
+(puthash "\\_-" "₋" julia-latexsubs)
+(puthash "\\_=" "₌" julia-latexsubs)
+(puthash "\\_(" "₍" julia-latexsubs)
+(puthash "\\_)" "₎" julia-latexsubs)
+(puthash "\\_a" "ₐ" julia-latexsubs)
+(puthash "\\_e" "ₑ" julia-latexsubs)
+(puthash "\\_o" "ₒ" julia-latexsubs)
+(puthash "\\_x" "ₓ" julia-latexsubs)
+(puthash "\\_schwa" "ₔ" julia-latexsubs)
+(puthash "\\_h" "ₕ" julia-latexsubs)
+(puthash "\\_k" "ₖ" julia-latexsubs)
+(puthash "\\_l" "ₗ" julia-latexsubs)
+(puthash "\\_m" "ₘ" julia-latexsubs)
+(puthash "\\_n" "ₙ" julia-latexsubs)
+(puthash "\\_p" "ₚ" julia-latexsubs)
+(puthash "\\_s" "ₛ" julia-latexsubs)
+(puthash "\\_t" "ₜ" julia-latexsubs)
+(puthash "\\pes" "₧" julia-latexsubs)
+(puthash "\\euro" "€" julia-latexsubs)
+(puthash "\\leftharpoonaccent" "⃐" julia-latexsubs)
+(puthash "\\rightharpoonaccent" "⃑" julia-latexsubs)
+(puthash "\\vertoverlay" "⃒" julia-latexsubs)
+(puthash "\\overleftarrow" "⃖" julia-latexsubs)
+(puthash "\\vec" "⃗" julia-latexsubs)
+(puthash "\\dddot" "⃛" julia-latexsubs)
+(puthash "\\ddddot" "⃜" julia-latexsubs)
+(puthash "\\enclosecircle" "⃝" julia-latexsubs)
+(puthash "\\enclosesquare" "⃞" julia-latexsubs)
+(puthash "\\enclosediamond" "⃟" julia-latexsubs)
+(puthash "\\overleftrightarrow" "⃡" julia-latexsubs)
+(puthash "\\enclosetriangle" "⃤" julia-latexsubs)
+(puthash "\\annuity" "⃧" julia-latexsubs)
+(puthash "\\threeunderdot" "⃨" julia-latexsubs)
+(puthash "\\widebridgeabove" "⃩" julia-latexsubs)
+(puthash "\\underrightharpoondown" "⃬" julia-latexsubs)
+(puthash "\\underleftharpoondown" "⃭" julia-latexsubs)
+(puthash "\\underleftarrow" "⃮" julia-latexsubs)
+(puthash "\\underrightarrow" "⃯" julia-latexsubs)
+(puthash "\\asteraccent" "⃰" julia-latexsubs)
+(puthash "\\bbC" "ℂ" julia-latexsubs)
+(puthash "\\eulermascheroni" "ℇ" julia-latexsubs)
+(puthash "\\scrg" "ℊ" julia-latexsubs)
+(puthash "\\scrH" "ℋ" julia-latexsubs)
+(puthash "\\frakH" "ℌ" julia-latexsubs)
+(puthash "\\bbH" "ℍ" julia-latexsubs)
+(puthash "\\planck" "ℎ" julia-latexsubs)
+(puthash "\\hslash" "ℏ" julia-latexsubs)
+(puthash "\\scrI" "ℐ" julia-latexsubs)
+(puthash "\\Im" "ℑ" julia-latexsubs)
+(puthash "\\scrL" "ℒ" julia-latexsubs)
+(puthash "\\ell" "ℓ" julia-latexsubs)
+(puthash "\\bbN" "ℕ" julia-latexsubs)
+(puthash "\\numero" "№" julia-latexsubs)
+(puthash "\\wp" "℘" julia-latexsubs)
+(puthash "\\bbP" "ℙ" julia-latexsubs)
+(puthash "\\bbQ" "ℚ" julia-latexsubs)
+(puthash "\\scrR" "ℛ" julia-latexsubs)
+(puthash "\\Re" "ℜ" julia-latexsubs)
+(puthash "\\bbR" "ℝ" julia-latexsubs)
+(puthash "\\xrat" "℞" julia-latexsubs)
+(puthash "\\trademark" "™" julia-latexsubs)
+(puthash "\\:tm:" "™" julia-latexsubs)
+(puthash "\\bbZ" "ℤ" julia-latexsubs)
+(puthash "\\ohm" "Ω" julia-latexsubs)
+(puthash "\\mho" "℧" julia-latexsubs)
+(puthash "\\frakZ" "ℨ" julia-latexsubs)
+(puthash "\\turnediota" "℩" julia-latexsubs)
+(puthash "\\Angstrom" "Å" julia-latexsubs)
+(puthash "\\scrB" "ℬ" julia-latexsubs)
+(puthash "\\frakC" "ℭ" julia-latexsubs)
+(puthash "\\scre" "ℯ" julia-latexsubs)
+(puthash "\\euler" "ℯ" julia-latexsubs)
+(puthash "\\scrE" "ℰ" julia-latexsubs)
+(puthash "\\scrF" "ℱ" julia-latexsubs)
+(puthash "\\Finv" "Ⅎ" julia-latexsubs)
+(puthash "\\scrM" "ℳ" julia-latexsubs)
+(puthash "\\scro" "ℴ" julia-latexsubs)
+(puthash "\\aleph" "ℵ" julia-latexsubs)
+(puthash "\\beth" "ℶ" julia-latexsubs)
+(puthash "\\gimel" "ℷ" julia-latexsubs)
+(puthash "\\daleth" "ℸ" julia-latexsubs)
+(puthash "\\:information_source:" "ℹ" julia-latexsubs)
+(puthash "\\bbpi" "ℼ" julia-latexsubs)
+(puthash "\\bbgamma" "ℽ" julia-latexsubs)
+(puthash "\\bbGamma" "ℾ" julia-latexsubs)
+(puthash "\\bbPi" "ℿ" julia-latexsubs)
+(puthash "\\bbsum" "⅀" julia-latexsubs)
+(puthash "\\Game" "⅁" julia-latexsubs)
+(puthash "\\sansLturned" "⅂" julia-latexsubs)
+(puthash "\\sansLmirrored" "⅃" julia-latexsubs)
+(puthash "\\Yup" "⅄" julia-latexsubs)
+(puthash "\\bbiD" "ⅅ" julia-latexsubs)
+(puthash "\\bbid" "ⅆ" julia-latexsubs)
+(puthash "\\bbie" "ⅇ" julia-latexsubs)
+(puthash "\\bbii" "ⅈ" julia-latexsubs)
+(puthash "\\bbij" "ⅉ" julia-latexsubs)
+(puthash "\\PropertyLine" "⅊" julia-latexsubs)
+(puthash "\\upand" "⅋" julia-latexsubs)
+(puthash "\\1/7" "⅐" julia-latexsubs)
+(puthash "\\1/9" "⅑" julia-latexsubs)
+(puthash "\\1/10" "⅒" julia-latexsubs)
+(puthash "\\1/3" "⅓" julia-latexsubs)
+(puthash "\\2/3" "⅔" julia-latexsubs)
+(puthash "\\1/5" "⅕" julia-latexsubs)
+(puthash "\\2/5" "⅖" julia-latexsubs)
+(puthash "\\3/5" "⅗" julia-latexsubs)
+(puthash "\\4/5" "⅘" julia-latexsubs)
+(puthash "\\1/6" "⅙" julia-latexsubs)
+(puthash "\\5/6" "⅚" julia-latexsubs)
+(puthash "\\1/8" "⅛" julia-latexsubs)
+(puthash "\\3/8" "⅜" julia-latexsubs)
+(puthash "\\5/8" "⅝" julia-latexsubs)
+(puthash "\\7/8" "⅞" julia-latexsubs)
+(puthash "\\1/" "⅟" julia-latexsubs)
+(puthash "\\0/3" "↉" julia-latexsubs)
+(puthash "\\leftarrow" "←" julia-latexsubs)
+(puthash "\\uparrow" "↑" julia-latexsubs)
+(puthash "\\rightarrow" "→" julia-latexsubs)
+(puthash "\\to" "→" julia-latexsubs)
+(puthash "\\downarrow" "↓" julia-latexsubs)
+(puthash "\\:left_right_arrow:" "↔" julia-latexsubs)
+(puthash "\\leftrightarrow" "↔" julia-latexsubs)
+(puthash "\\:arrow_up_down:" "↕" julia-latexsubs)
+(puthash "\\updownarrow" "↕" julia-latexsubs)
+(puthash "\\:arrow_upper_left:" "↖" julia-latexsubs)
+(puthash "\\nwarrow" "↖" julia-latexsubs)
+(puthash "\\nearrow" "↗" julia-latexsubs)
+(puthash "\\:arrow_upper_right:" "↗" julia-latexsubs)
+(puthash "\\:arrow_lower_right:" "↘" julia-latexsubs)
+(puthash "\\searrow" "↘" julia-latexsubs)
+(puthash "\\swarrow" "↙" julia-latexsubs)
+(puthash "\\:arrow_lower_left:" "↙" julia-latexsubs)
+(puthash "\\nleftarrow" "↚" julia-latexsubs)
+(puthash "\\nrightarrow" "↛" julia-latexsubs)
+(puthash "\\leftwavearrow" "↜" julia-latexsubs)
+(puthash "\\rightwavearrow" "↝" julia-latexsubs)
+(puthash "\\twoheadleftarrow" "↞" julia-latexsubs)
+(puthash "\\twoheaduparrow" "↟" julia-latexsubs)
+(puthash "\\twoheadrightarrow" "↠" julia-latexsubs)
+(puthash "\\twoheaddownarrow" "↡" julia-latexsubs)
+(puthash "\\leftarrowtail" "↢" julia-latexsubs)
+(puthash "\\rightarrowtail" "↣" julia-latexsubs)
+(puthash "\\mapsfrom" "↤" julia-latexsubs)
+(puthash "\\mapsup" "↥" julia-latexsubs)
+(puthash "\\mapsto" "↦" julia-latexsubs)
+(puthash "\\mapsdown" "↧" julia-latexsubs)
+(puthash "\\updownarrowbar" "↨" julia-latexsubs)
+(puthash "\\hookleftarrow" "↩" julia-latexsubs)
+(puthash "\\:leftwards_arrow_with_hook:" "↩" julia-latexsubs)
+(puthash "\\:arrow_right_hook:" "↪" julia-latexsubs)
+(puthash "\\hookrightarrow" "↪" julia-latexsubs)
+(puthash "\\looparrowleft" "↫" julia-latexsubs)
+(puthash "\\looparrowright" "↬" julia-latexsubs)
+(puthash "\\leftrightsquigarrow" "↭" julia-latexsubs)
+(puthash "\\nleftrightarrow" "↮" julia-latexsubs)
+(puthash "\\downzigzagarrow" "↯" julia-latexsubs)
+(puthash "\\Lsh" "↰" julia-latexsubs)
+(puthash "\\Rsh" "↱" julia-latexsubs)
+(puthash "\\Ldsh" "↲" julia-latexsubs)
+(puthash "\\Rdsh" "↳" julia-latexsubs)
+(puthash "\\linefeed" "↴" julia-latexsubs)
+(puthash "\\carriagereturn" "↵" julia-latexsubs)
+(puthash "\\curvearrowleft" "↶" julia-latexsubs)
+(puthash "\\curvearrowright" "↷" julia-latexsubs)
+(puthash "\\barovernorthwestarrow" "↸" julia-latexsubs)
+(puthash "\\barleftarrowrightarrowbar" "↹" julia-latexsubs)
+(puthash "\\circlearrowleft" "↺" julia-latexsubs)
+(puthash "\\circlearrowright" "↻" julia-latexsubs)
+(puthash "\\leftharpoonup" "↼" julia-latexsubs)
+(puthash "\\leftharpoondown" "↽" julia-latexsubs)
+(puthash "\\upharpoonright" "↾" julia-latexsubs)
+(puthash "\\upharpoonleft" "↿" julia-latexsubs)
+(puthash "\\rightharpoonup" "⇀" julia-latexsubs)
+(puthash "\\rightharpoondown" "⇁" julia-latexsubs)
+(puthash "\\downharpoonright" "⇂" julia-latexsubs)
+(puthash "\\downharpoonleft" "⇃" julia-latexsubs)
+(puthash "\\rightleftarrows" "⇄" julia-latexsubs)
+(puthash "\\dblarrowupdown" "⇅" julia-latexsubs)
+(puthash "\\leftrightarrows" "⇆" julia-latexsubs)
+(puthash "\\leftleftarrows" "⇇" julia-latexsubs)
+(puthash "\\upuparrows" "⇈" julia-latexsubs)
+(puthash "\\rightrightarrows" "⇉" julia-latexsubs)
+(puthash "\\downdownarrows" "⇊" julia-latexsubs)
+(puthash "\\leftrightharpoons" "⇋" julia-latexsubs)
+(puthash "\\rightleftharpoons" "⇌" julia-latexsubs)
+(puthash "\\nLeftarrow" "⇍" julia-latexsubs)
+(puthash "\\nLeftrightarrow" "⇎" julia-latexsubs)
+(puthash "\\nRightarrow" "⇏" julia-latexsubs)
+(puthash "\\Leftarrow" "⇐" julia-latexsubs)
+(puthash "\\Uparrow" "⇑" julia-latexsubs)
+(puthash "\\Rightarrow" "⇒" julia-latexsubs)
+(puthash "\\Downarrow" "⇓" julia-latexsubs)
+(puthash "\\Leftrightarrow" "⇔" julia-latexsubs)
+(puthash "\\Updownarrow" "⇕" julia-latexsubs)
+(puthash "\\Nwarrow" "⇖" julia-latexsubs)
+(puthash "\\Nearrow" "⇗" julia-latexsubs)
+(puthash "\\Searrow" "⇘" julia-latexsubs)
+(puthash "\\Swarrow" "⇙" julia-latexsubs)
+(puthash "\\Lleftarrow" "⇚" julia-latexsubs)
+(puthash "\\Rrightarrow" "⇛" julia-latexsubs)
+(puthash "\\leftsquigarrow" "⇜" julia-latexsubs)
+(puthash "\\rightsquigarrow" "⇝" julia-latexsubs)
+(puthash "\\nHuparrow" "⇞" julia-latexsubs)
+(puthash "\\nHdownarrow" "⇟" julia-latexsubs)
+(puthash "\\leftdasharrow" "⇠" julia-latexsubs)
+(puthash "\\updasharrow" "⇡" julia-latexsubs)
+(puthash "\\rightdasharrow" "⇢" julia-latexsubs)
+(puthash "\\downdasharrow" "⇣" julia-latexsubs)
+(puthash "\\barleftarrow" "⇤" julia-latexsubs)
+(puthash "\\rightarrowbar" "⇥" julia-latexsubs)
+(puthash "\\leftwhitearrow" "⇦" julia-latexsubs)
+(puthash "\\upwhitearrow" "⇧" julia-latexsubs)
+(puthash "\\rightwhitearrow" "⇨" julia-latexsubs)
+(puthash "\\downwhitearrow" "⇩" julia-latexsubs)
+(puthash "\\whitearrowupfrombar" "⇪" julia-latexsubs)
+(puthash "\\circleonrightarrow" "⇴" julia-latexsubs)
+(puthash "\\DownArrowUpArrow" "⇵" julia-latexsubs)
+(puthash "\\rightthreearrows" "⇶" julia-latexsubs)
+(puthash "\\nvleftarrow" "⇷" julia-latexsubs)
+(puthash "\\nvrightarrow" "⇸" julia-latexsubs)
+(puthash "\\nvleftrightarrow" "⇹" julia-latexsubs)
+(puthash "\\nVleftarrow" "⇺" julia-latexsubs)
+(puthash "\\nVrightarrow" "⇻" julia-latexsubs)
+(puthash "\\nVleftrightarrow" "⇼" julia-latexsubs)
+(puthash "\\leftarrowtriangle" "⇽" julia-latexsubs)
+(puthash "\\rightarrowtriangle" "⇾" julia-latexsubs)
+(puthash "\\leftrightarrowtriangle" "⇿" julia-latexsubs)
+(puthash "\\forall" "∀" julia-latexsubs)
+(puthash "\\complement" "∁" julia-latexsubs)
+(puthash "\\partial" "∂" julia-latexsubs)
+(puthash "\\exists" "∃" julia-latexsubs)
+(puthash "\\nexists" "∄" julia-latexsubs)
+(puthash "\\varnothing" "∅" julia-latexsubs)
+(puthash "\\emptyset" "∅" julia-latexsubs)
+(puthash "\\increment" "∆" julia-latexsubs)
+(puthash "\\del" "∇" julia-latexsubs)
+(puthash "\\nabla" "∇" julia-latexsubs)
+(puthash "\\in" "∈" julia-latexsubs)
+(puthash "\\notin" "∉" julia-latexsubs)
+(puthash "\\smallin" "∊" julia-latexsubs)
+(puthash "\\ni" "∋" julia-latexsubs)
+(puthash "\\nni" "∌" julia-latexsubs)
+(puthash "\\smallni" "∍" julia-latexsubs)
+(puthash "\\QED" "∎" julia-latexsubs)
+(puthash "\\prod" "∏" julia-latexsubs)
+(puthash "\\coprod" "∐" julia-latexsubs)
+(puthash "\\sum" "∑" julia-latexsubs)
+(puthash "\\minus" "−" julia-latexsubs)
+(puthash "\\mp" "∓" julia-latexsubs)
+(puthash "\\dotplus" "∔" julia-latexsubs)
+(puthash "\\setminus" "∖" julia-latexsubs)
+(puthash "\\ast" "∗" julia-latexsubs)
+(puthash "\\circ" "∘" julia-latexsubs)
+(puthash "\\vysmblkcircle" "∙" julia-latexsubs)
+(puthash "\\sqrt" "√" julia-latexsubs)
+(puthash "\\surd" "√" julia-latexsubs)
+(puthash "\\cbrt" "∛" julia-latexsubs)
+(puthash "\\fourthroot" "∜" julia-latexsubs)
+(puthash "\\propto" "∝" julia-latexsubs)
+(puthash "\\infty" "∞" julia-latexsubs)
+(puthash "\\rightangle" "∟" julia-latexsubs)
+(puthash "\\angle" "∠" julia-latexsubs)
+(puthash "\\measuredangle" "∡" julia-latexsubs)
+(puthash "\\sphericalangle" "∢" julia-latexsubs)
+(puthash "\\mid" "∣" julia-latexsubs)
+(puthash "\\nmid" "∤" julia-latexsubs)
+(puthash "\\parallel" "∥" julia-latexsubs)
+(puthash "\\nparallel" "∦" julia-latexsubs)
+(puthash "\\wedge" "∧" julia-latexsubs)
+(puthash "\\vee" "∨" julia-latexsubs)
+(puthash "\\cap" "∩" julia-latexsubs)
+(puthash "\\cup" "∪" julia-latexsubs)
+(puthash "\\int" "∫" julia-latexsubs)
+(puthash "\\iint" "∬" julia-latexsubs)
+(puthash "\\iiint" "∭" julia-latexsubs)
+(puthash "\\oint" "∮" julia-latexsubs)
+(puthash "\\oiint" "∯" julia-latexsubs)
+(puthash "\\oiiint" "∰" julia-latexsubs)
+(puthash "\\clwintegral" "∱" julia-latexsubs)
+(puthash "\\varointclockwise" "∲" julia-latexsubs)
+(puthash "\\ointctrclockwise" "∳" julia-latexsubs)
+(puthash "\\therefore" "∴" julia-latexsubs)
+(puthash "\\because" "∵" julia-latexsubs)
+(puthash "\\Colon" "∷" julia-latexsubs)
+(puthash "\\dotminus" "∸" julia-latexsubs)
+(puthash "\\dotsminusdots" "∺" julia-latexsubs)
+(puthash "\\kernelcontraction" "∻" julia-latexsubs)
+(puthash "\\sim" "∼" julia-latexsubs)
+(puthash "\\backsim" "∽" julia-latexsubs)
+(puthash "\\lazysinv" "∾" julia-latexsubs)
+(puthash "\\sinewave" "∿" julia-latexsubs)
+(puthash "\\wr" "≀" julia-latexsubs)
+(puthash "\\nsim" "≁" julia-latexsubs)
+(puthash "\\eqsim" "≂" julia-latexsubs)
+(puthash "\\neqsim" "≂̸" julia-latexsubs)
+(puthash "\\simeq" "≃" julia-latexsubs)
+(puthash "\\nsime" "≄" julia-latexsubs)
+(puthash "\\cong" "≅" julia-latexsubs)
+(puthash "\\approxnotequal" "≆" julia-latexsubs)
+(puthash "\\ncong" "≇" julia-latexsubs)
+(puthash "\\approx" "≈" julia-latexsubs)
+(puthash "\\napprox" "≉" julia-latexsubs)
+(puthash "\\approxeq" "≊" julia-latexsubs)
+(puthash "\\tildetrpl" "≋" julia-latexsubs)
+(puthash "\\allequal" "≌" julia-latexsubs)
+(puthash "\\asymp" "≍" julia-latexsubs)
+(puthash "\\Bumpeq" "≎" julia-latexsubs)
+(puthash "\\nBumpeq" "≎̸" julia-latexsubs)
+(puthash "\\bumpeq" "≏" julia-latexsubs)
+(puthash "\\nbumpeq" "≏̸" julia-latexsubs)
+(puthash "\\doteq" "≐" julia-latexsubs)
+(puthash "\\Doteq" "≑" julia-latexsubs)
+(puthash "\\fallingdotseq" "≒" julia-latexsubs)
+(puthash "\\risingdotseq" "≓" julia-latexsubs)
+(puthash "\\coloneq" "≔" julia-latexsubs)
+(puthash "\\eqcolon" "≕" julia-latexsubs)
+(puthash "\\eqcirc" "≖" julia-latexsubs)
+(puthash "\\circeq" "≗" julia-latexsubs)
+(puthash "\\arceq" "≘" julia-latexsubs)
+(puthash "\\wedgeq" "≙" julia-latexsubs)
+(puthash "\\veeeq" "≚" julia-latexsubs)
+(puthash "\\starequal" "≛" julia-latexsubs)
+(puthash "\\triangleq" "≜" julia-latexsubs)
+(puthash "\\eqdef" "≝" julia-latexsubs)
+(puthash "\\measeq" "≞" julia-latexsubs)
+(puthash "\\questeq" "≟" julia-latexsubs)
+(puthash "\\ne" "≠" julia-latexsubs)
+(puthash "\\equiv" "≡" julia-latexsubs)
+(puthash "\\nequiv" "≢" julia-latexsubs)
+(puthash "\\Equiv" "≣" julia-latexsubs)
+(puthash "\\le" "≤" julia-latexsubs)
+(puthash "\\leq" "≤" julia-latexsubs)
+(puthash "\\ge" "≥" julia-latexsubs)
+(puthash "\\geq" "≥" julia-latexsubs)
+(puthash "\\leqq" "≦" julia-latexsubs)
+(puthash "\\geqq" "≧" julia-latexsubs)
+(puthash "\\lneqq" "≨" julia-latexsubs)
+(puthash "\\lvertneqq" "≨︀" julia-latexsubs)
+(puthash "\\gneqq" "≩" julia-latexsubs)
+(puthash "\\gvertneqq" "≩︀" julia-latexsubs)
+(puthash "\\ll" "≪" julia-latexsubs)
+(puthash "\\NotLessLess" "≪̸" julia-latexsubs)
+(puthash "\\gg" "≫" julia-latexsubs)
+(puthash "\\NotGreaterGreater" "≫̸" julia-latexsubs)
+(puthash "\\between" "≬" julia-latexsubs)
+(puthash "\\nasymp" "≭" julia-latexsubs)
+(puthash "\\nless" "≮" julia-latexsubs)
+(puthash "\\ngtr" "≯" julia-latexsubs)
+(puthash "\\nleq" "≰" julia-latexsubs)
+(puthash "\\ngeq" "≱" julia-latexsubs)
+(puthash "\\lesssim" "≲" julia-latexsubs)
+(puthash "\\gtrsim" "≳" julia-latexsubs)
+(puthash "\\nlesssim" "≴" julia-latexsubs)
+(puthash "\\ngtrsim" "≵" julia-latexsubs)
+(puthash "\\lessgtr" "≶" julia-latexsubs)
+(puthash "\\gtrless" "≷" julia-latexsubs)
+(puthash "\\notlessgreater" "≸" julia-latexsubs)
+(puthash "\\notgreaterless" "≹" julia-latexsubs)
+(puthash "\\prec" "≺" julia-latexsubs)
+(puthash "\\succ" "≻" julia-latexsubs)
+(puthash "\\preccurlyeq" "≼" julia-latexsubs)
+(puthash "\\succcurlyeq" "≽" julia-latexsubs)
+(puthash "\\precsim" "≾" julia-latexsubs)
+(puthash "\\nprecsim" "≾̸" julia-latexsubs)
+(puthash "\\succsim" "≿" julia-latexsubs)
+(puthash "\\nsuccsim" "≿̸" julia-latexsubs)
+(puthash "\\nprec" "⊀" julia-latexsubs)
+(puthash "\\nsucc" "⊁" julia-latexsubs)
+(puthash "\\subset" "⊂" julia-latexsubs)
+(puthash "\\supset" "⊃" julia-latexsubs)
+(puthash "\\nsubset" "⊄" julia-latexsubs)
+(puthash "\\nsupset" "⊅" julia-latexsubs)
+(puthash "\\subseteq" "⊆" julia-latexsubs)
+(puthash "\\supseteq" "⊇" julia-latexsubs)
+(puthash "\\nsubseteq" "⊈" julia-latexsubs)
+(puthash "\\nsupseteq" "⊉" julia-latexsubs)
+(puthash "\\subsetneq" "⊊" julia-latexsubs)
+(puthash "\\varsubsetneqq" "⊊︀" julia-latexsubs)
+(puthash "\\supsetneq" "⊋" julia-latexsubs)
+(puthash "\\varsupsetneq" "⊋︀" julia-latexsubs)
+(puthash "\\cupdot" "⊍" julia-latexsubs)
+(puthash "\\uplus" "⊎" julia-latexsubs)
+(puthash "\\sqsubset" "⊏" julia-latexsubs)
+(puthash "\\NotSquareSubset" "⊏̸" julia-latexsubs)
+(puthash "\\sqsupset" "⊐" julia-latexsubs)
+(puthash "\\NotSquareSuperset" "⊐̸" julia-latexsubs)
+(puthash "\\sqsubseteq" "⊑" julia-latexsubs)
+(puthash "\\sqsupseteq" "⊒" julia-latexsubs)
+(puthash "\\sqcap" "⊓" julia-latexsubs)
+(puthash "\\sqcup" "⊔" julia-latexsubs)
+(puthash "\\oplus" "⊕" julia-latexsubs)
+(puthash "\\ominus" "⊖" julia-latexsubs)
+(puthash "\\otimes" "⊗" julia-latexsubs)
+(puthash "\\oslash" "⊘" julia-latexsubs)
+(puthash "\\odot" "⊙" julia-latexsubs)
+(puthash "\\circledcirc" "⊚" julia-latexsubs)
+(puthash "\\circledast" "⊛" julia-latexsubs)
+(puthash "\\circledequal" "⊜" julia-latexsubs)
+(puthash "\\circleddash" "⊝" julia-latexsubs)
+(puthash "\\boxplus" "⊞" julia-latexsubs)
+(puthash "\\boxminus" "⊟" julia-latexsubs)
+(puthash "\\boxtimes" "⊠" julia-latexsubs)
+(puthash "\\boxdot" "⊡" julia-latexsubs)
+(puthash "\\vdash" "⊢" julia-latexsubs)
+(puthash "\\dashv" "⊣" julia-latexsubs)
+(puthash "\\top" "⊤" julia-latexsubs)
+(puthash "\\bot" "⊥" julia-latexsubs)
+(puthash "\\models" "⊧" julia-latexsubs)
+(puthash "\\vDash" "⊨" julia-latexsubs)
+(puthash "\\Vdash" "⊩" julia-latexsubs)
+(puthash "\\Vvdash" "⊪" julia-latexsubs)
+(puthash "\\VDash" "⊫" julia-latexsubs)
+(puthash "\\nvdash" "⊬" julia-latexsubs)
+(puthash "\\nvDash" "⊭" julia-latexsubs)
+(puthash "\\nVdash" "⊮" julia-latexsubs)
+(puthash "\\nVDash" "⊯" julia-latexsubs)
+(puthash "\\prurel" "⊰" julia-latexsubs)
+(puthash "\\scurel" "⊱" julia-latexsubs)
+(puthash "\\vartriangleleft" "⊲" julia-latexsubs)
+(puthash "\\vartriangleright" "⊳" julia-latexsubs)
+(puthash "\\trianglelefteq" "⊴" julia-latexsubs)
+(puthash "\\trianglerighteq" "⊵" julia-latexsubs)
+(puthash "\\original" "⊶" julia-latexsubs)
+(puthash "\\image" "⊷" julia-latexsubs)
+(puthash "\\multimap" "⊸" julia-latexsubs)
+(puthash "\\hermitconjmatrix" "⊹" julia-latexsubs)
+(puthash "\\intercal" "⊺" julia-latexsubs)
+(puthash "\\veebar" "⊻" julia-latexsubs)
+(puthash "\\xor" "⊻" julia-latexsubs)
+(puthash "\\barwedge" "⊼" julia-latexsubs)
+(puthash "\\barvee" "⊽" julia-latexsubs)
+(puthash "\\rightanglearc" "⊾" julia-latexsubs)
+(puthash "\\varlrtriangle" "⊿" julia-latexsubs)
+(puthash "\\bigwedge" "⋀" julia-latexsubs)
+(puthash "\\bigvee" "⋁" julia-latexsubs)
+(puthash "\\bigcap" "⋂" julia-latexsubs)
+(puthash "\\bigcup" "⋃" julia-latexsubs)
+(puthash "\\diamond" "⋄" julia-latexsubs)
+(puthash "\\cdot" "⋅" julia-latexsubs)
+(puthash "\\star" "⋆" julia-latexsubs)
+(puthash "\\divideontimes" "⋇" julia-latexsubs)
+(puthash "\\bowtie" "⋈" julia-latexsubs)
+(puthash "\\ltimes" "⋉" julia-latexsubs)
+(puthash "\\rtimes" "⋊" julia-latexsubs)
+(puthash "\\leftthreetimes" "⋋" julia-latexsubs)
+(puthash "\\rightthreetimes" "⋌" julia-latexsubs)
+(puthash "\\backsimeq" "⋍" julia-latexsubs)
+(puthash "\\curlyvee" "⋎" julia-latexsubs)
+(puthash "\\curlywedge" "⋏" julia-latexsubs)
+(puthash "\\Subset" "⋐" julia-latexsubs)
+(puthash "\\Supset" "⋑" julia-latexsubs)
+(puthash "\\Cap" "⋒" julia-latexsubs)
+(puthash "\\Cup" "⋓" julia-latexsubs)
+(puthash "\\pitchfork" "⋔" julia-latexsubs)
+(puthash "\\equalparallel" "⋕" julia-latexsubs)
+(puthash "\\lessdot" "⋖" julia-latexsubs)
+(puthash "\\gtrdot" "⋗" julia-latexsubs)
+(puthash "\\verymuchless" "⋘" julia-latexsubs)
+(puthash "\\ggg" "⋙" julia-latexsubs)
+(puthash "\\lesseqgtr" "⋚" julia-latexsubs)
+(puthash "\\gtreqless" "⋛" julia-latexsubs)
+(puthash "\\eqless" "⋜" julia-latexsubs)
+(puthash "\\eqgtr" "⋝" julia-latexsubs)
+(puthash "\\curlyeqprec" "⋞" julia-latexsubs)
+(puthash "\\curlyeqsucc" "⋟" julia-latexsubs)
+(puthash "\\npreccurlyeq" "⋠" julia-latexsubs)
+(puthash "\\nsucccurlyeq" "⋡" julia-latexsubs)
+(puthash "\\nsqsubseteq" "⋢" julia-latexsubs)
+(puthash "\\nsqsupseteq" "⋣" julia-latexsubs)
+(puthash "\\sqsubsetneq" "⋤" julia-latexsubs)
+(puthash "\\sqspne" "⋥" julia-latexsubs)
+(puthash "\\lnsim" "⋦" julia-latexsubs)
+(puthash "\\gnsim" "⋧" julia-latexsubs)
+(puthash "\\precnsim" "⋨" julia-latexsubs)
+(puthash "\\succnsim" "⋩" julia-latexsubs)
+(puthash "\\ntriangleleft" "⋪" julia-latexsubs)
+(puthash "\\ntriangleright" "⋫" julia-latexsubs)
+(puthash "\\ntrianglelefteq" "⋬" julia-latexsubs)
+(puthash "\\ntrianglerighteq" "⋭" julia-latexsubs)
+(puthash "\\vdots" "⋮" julia-latexsubs)
+(puthash "\\cdots" "⋯" julia-latexsubs)
+(puthash "\\adots" "⋰" julia-latexsubs)
+(puthash "\\ddots" "⋱" julia-latexsubs)
+(puthash "\\disin" "⋲" julia-latexsubs)
+(puthash "\\varisins" "⋳" julia-latexsubs)
+(puthash "\\isins" "⋴" julia-latexsubs)
+(puthash "\\isindot" "⋵" julia-latexsubs)
+(puthash "\\varisinobar" "⋶" julia-latexsubs)
+(puthash "\\isinobar" "⋷" julia-latexsubs)
+(puthash "\\isinvb" "⋸" julia-latexsubs)
+(puthash "\\isinE" "⋹" julia-latexsubs)
+(puthash "\\nisd" "⋺" julia-latexsubs)
+(puthash "\\varnis" "⋻" julia-latexsubs)
+(puthash "\\nis" "⋼" julia-latexsubs)
+(puthash "\\varniobar" "⋽" julia-latexsubs)
+(puthash "\\niobar" "⋾" julia-latexsubs)
+(puthash "\\bagmember" "⋿" julia-latexsubs)
+(puthash "\\diameter" "⌀" julia-latexsubs)
+(puthash "\\house" "⌂" julia-latexsubs)
+(puthash "\\varbarwedge" "⌅" julia-latexsubs)
+(puthash "\\vardoublebarwedge" "⌆" julia-latexsubs)
+(puthash "\\lceil" "⌈" julia-latexsubs)
+(puthash "\\rceil" "⌉" julia-latexsubs)
+(puthash "\\lfloor" "⌊" julia-latexsubs)
+(puthash "\\rfloor" "⌋" julia-latexsubs)
+(puthash "\\invnot" "⌐" julia-latexsubs)
+(puthash "\\sqlozenge" "⌑" julia-latexsubs)
+(puthash "\\profline" "⌒" julia-latexsubs)
+(puthash "\\profsurf" "⌓" julia-latexsubs)
+(puthash "\\recorder" "⌕" julia-latexsubs)
+(puthash "\\viewdata" "⌗" julia-latexsubs)
+(puthash "\\turnednot" "⌙" julia-latexsubs)
+(puthash "\\:watch:" "⌚" julia-latexsubs)
+(puthash "\\:hourglass:" "⌛" julia-latexsubs)
+(puthash "\\ulcorner" "⌜" julia-latexsubs)
+(puthash "\\urcorner" "⌝" julia-latexsubs)
+(puthash "\\llcorner" "⌞" julia-latexsubs)
+(puthash "\\lrcorner" "⌟" julia-latexsubs)
+(puthash "\\frown" "⌢" julia-latexsubs)
+(puthash "\\smile" "⌣" julia-latexsubs)
+(puthash "\\varhexagonlrbonds" "⌬" julia-latexsubs)
+(puthash "\\conictaper" "⌲" julia-latexsubs)
+(puthash "\\topbot" "⌶" julia-latexsubs)
+(puthash "\\obar" "⌽" julia-latexsubs)
+(puthash "\\notslash" "⌿" julia-latexsubs)
+(puthash "\\notbackslash" "⍀" julia-latexsubs)
+(puthash "\\boxupcaret" "⍓" julia-latexsubs)
+(puthash "\\boxquestion" "⍰" julia-latexsubs)
+(puthash "\\hexagon" "⎔" julia-latexsubs)
+(puthash "\\dlcorn" "⎣" julia-latexsubs)
+(puthash "\\lmoustache" "⎰" julia-latexsubs)
+(puthash "\\rmoustache" "⎱" julia-latexsubs)
+(puthash "\\overbracket" "⎴" julia-latexsubs)
+(puthash "\\underbracket" "⎵" julia-latexsubs)
+(puthash "\\bbrktbrk" "⎶" julia-latexsubs)
+(puthash "\\sqrtbottom" "⎷" julia-latexsubs)
+(puthash "\\lvboxline" "⎸" julia-latexsubs)
+(puthash "\\rvboxline" "⎹" julia-latexsubs)
+(puthash "\\varcarriagereturn" "⏎" julia-latexsubs)
+(puthash "\\overbrace" "⏞" julia-latexsubs)
+(puthash "\\underbrace" "⏟" julia-latexsubs)
+(puthash "\\trapezium" "⏢" julia-latexsubs)
+(puthash "\\benzenr" "⏣" julia-latexsubs)
+(puthash "\\strns" "⏤" julia-latexsubs)
+(puthash "\\fltns" "⏥" julia-latexsubs)
+(puthash "\\accurrent" "⏦" julia-latexsubs)
+(puthash "\\elinters" "⏧" julia-latexsubs)
+(puthash "\\:fast_forward:" "⏩" julia-latexsubs)
+(puthash "\\:rewind:" "⏪" julia-latexsubs)
+(puthash "\\:arrow_double_up:" "⏫" julia-latexsubs)
+(puthash "\\:arrow_double_down:" "⏬" julia-latexsubs)
+(puthash "\\:alarm_clock:" "⏰" julia-latexsubs)
+(puthash "\\:hourglass_flowing_sand:" "⏳" julia-latexsubs)
+(puthash "\\blanksymbol" "␢" julia-latexsubs)
+(puthash "\\visiblespace" "␣" julia-latexsubs)
+(puthash "\\:m:" "Ⓜ" julia-latexsubs)
+(puthash "\\circledS" "Ⓢ" julia-latexsubs)
+(puthash "\\dshfnc" "┆" julia-latexsubs)
+(puthash "\\sqfnw" "┙" julia-latexsubs)
+(puthash "\\diagup" "╱" julia-latexsubs)
+(puthash "\\diagdown" "╲" julia-latexsubs)
+(puthash "\\blockuphalf" "▀" julia-latexsubs)
+(puthash "\\blocklowhalf" "▄" julia-latexsubs)
+(puthash "\\blockfull" "█" julia-latexsubs)
+(puthash "\\blocklefthalf" "▌" julia-latexsubs)
+(puthash "\\blockrighthalf" "▐" julia-latexsubs)
+(puthash "\\blockqtrshaded" "░" julia-latexsubs)
+(puthash "\\blockhalfshaded" "▒" julia-latexsubs)
+(puthash "\\blockthreeqtrshaded" "▓" julia-latexsubs)
+(puthash "\\blacksquare" "■" julia-latexsubs)
+(puthash "\\square" "□" julia-latexsubs)
+(puthash "\\squoval" "▢" julia-latexsubs)
+(puthash "\\blackinwhitesquare" "▣" julia-latexsubs)
+(puthash "\\squarehfill" "▤" julia-latexsubs)
+(puthash "\\squarevfill" "▥" julia-latexsubs)
+(puthash "\\squarehvfill" "▦" julia-latexsubs)
+(puthash "\\squarenwsefill" "▧" julia-latexsubs)
+(puthash "\\squareneswfill" "▨" julia-latexsubs)
+(puthash "\\squarecrossfill" "▩" julia-latexsubs)
+(puthash "\\smblksquare" "▪" julia-latexsubs)
+(puthash "\\:black_small_square:" "▪" julia-latexsubs)
+(puthash "\\:white_small_square:" "▫" julia-latexsubs)
+(puthash "\\smwhtsquare" "▫" julia-latexsubs)
+(puthash "\\hrectangleblack" "▬" julia-latexsubs)
+(puthash "\\hrectangle" "▭" julia-latexsubs)
+(puthash "\\vrectangleblack" "▮" julia-latexsubs)
+(puthash "\\vrecto" "▯" julia-latexsubs)
+(puthash "\\parallelogramblack" "▰" julia-latexsubs)
+(puthash "\\parallelogram" "▱" julia-latexsubs)
+(puthash "\\bigblacktriangleup" "▲" julia-latexsubs)
+(puthash "\\bigtriangleup" "△" julia-latexsubs)
+(puthash "\\blacktriangle" "▴" julia-latexsubs)
+(puthash "\\vartriangle" "▵" julia-latexsubs)
+(puthash "\\:arrow_forward:" "▶" julia-latexsubs)
+(puthash "\\blacktriangleright" "▶" julia-latexsubs)
+(puthash "\\triangleright" "▷" julia-latexsubs)
+(puthash "\\smallblacktriangleright" "▸" julia-latexsubs)
+(puthash "\\smalltriangleright" "▹" julia-latexsubs)
+(puthash "\\blackpointerright" "►" julia-latexsubs)
+(puthash "\\whitepointerright" "▻" julia-latexsubs)
+(puthash "\\bigblacktriangledown" "▼" julia-latexsubs)
+(puthash "\\bigtriangledown" "▽" julia-latexsubs)
+(puthash "\\blacktriangledown" "▾" julia-latexsubs)
+(puthash "\\triangledown" "▿" julia-latexsubs)
+(puthash "\\blacktriangleleft" "◀" julia-latexsubs)
+(puthash "\\:arrow_backward:" "◀" julia-latexsubs)
+(puthash "\\triangleleft" "◁" julia-latexsubs)
+(puthash "\\smallblacktriangleleft" "◂" julia-latexsubs)
+(puthash "\\smalltriangleleft" "◃" julia-latexsubs)
+(puthash "\\blackpointerleft" "◄" julia-latexsubs)
+(puthash "\\whitepointerleft" "◅" julia-latexsubs)
+(puthash "\\mdlgblkdiamond" "◆" julia-latexsubs)
+(puthash "\\mdlgwhtdiamond" "◇" julia-latexsubs)
+(puthash "\\blackinwhitediamond" "◈" julia-latexsubs)
+(puthash "\\fisheye" "◉" julia-latexsubs)
+(puthash "\\lozenge" "◊" julia-latexsubs)
+(puthash "\\bigcirc" "○" julia-latexsubs)
+(puthash "\\dottedcircle" "◌" julia-latexsubs)
+(puthash "\\circlevertfill" "◍" julia-latexsubs)
+(puthash "\\bullseye" "◎" julia-latexsubs)
+(puthash "\\mdlgblkcircle" "●" julia-latexsubs)
+(puthash "\\cirfl" "◐" julia-latexsubs)
+(puthash "\\cirfr" "◑" julia-latexsubs)
+(puthash "\\cirfb" "◒" julia-latexsubs)
+(puthash "\\circletophalfblack" "◓" julia-latexsubs)
+(puthash "\\circleurquadblack" "◔" julia-latexsubs)
+(puthash "\\blackcircleulquadwhite" "◕" julia-latexsubs)
+(puthash "\\blacklefthalfcircle" "◖" julia-latexsubs)
+(puthash "\\blackrighthalfcircle" "◗" julia-latexsubs)
+(puthash "\\rvbull" "◘" julia-latexsubs)
+(puthash "\\inversewhitecircle" "◙" julia-latexsubs)
+(puthash "\\invwhiteupperhalfcircle" "◚" julia-latexsubs)
+(puthash "\\invwhitelowerhalfcircle" "◛" julia-latexsubs)
+(puthash "\\ularc" "◜" julia-latexsubs)
+(puthash "\\urarc" "◝" julia-latexsubs)
+(puthash "\\lrarc" "◞" julia-latexsubs)
+(puthash "\\llarc" "◟" julia-latexsubs)
+(puthash "\\topsemicircle" "◠" julia-latexsubs)
+(puthash "\\botsemicircle" "◡" julia-latexsubs)
+(puthash "\\lrblacktriangle" "◢" julia-latexsubs)
+(puthash "\\llblacktriangle" "◣" julia-latexsubs)
+(puthash "\\ulblacktriangle" "◤" julia-latexsubs)
+(puthash "\\urblacktriangle" "◥" julia-latexsubs)
+(puthash "\\smwhtcircle" "◦" julia-latexsubs)
+(puthash "\\sqfl" "◧" julia-latexsubs)
+(puthash "\\sqfr" "◨" julia-latexsubs)
+(puthash "\\squareulblack" "◩" julia-latexsubs)
+(puthash "\\sqfse" "◪" julia-latexsubs)
+(puthash "\\boxbar" "◫" julia-latexsubs)
+(puthash "\\trianglecdot" "◬" julia-latexsubs)
+(puthash "\\triangleleftblack" "◭" julia-latexsubs)
+(puthash "\\trianglerightblack" "◮" julia-latexsubs)
+(puthash "\\lgwhtcircle" "◯" julia-latexsubs)
+(puthash "\\squareulquad" "◰" julia-latexsubs)
+(puthash "\\squarellquad" "◱" julia-latexsubs)
+(puthash "\\squarelrquad" "◲" julia-latexsubs)
+(puthash "\\squareurquad" "◳" julia-latexsubs)
+(puthash "\\circleulquad" "◴" julia-latexsubs)
+(puthash "\\circlellquad" "◵" julia-latexsubs)
+(puthash "\\circlelrquad" "◶" julia-latexsubs)
+(puthash "\\circleurquad" "◷" julia-latexsubs)
+(puthash "\\ultriangle" "◸" julia-latexsubs)
+(puthash "\\urtriangle" "◹" julia-latexsubs)
+(puthash "\\lltriangle" "◺" julia-latexsubs)
+(puthash "\\mdwhtsquare" "◻" julia-latexsubs)
+(puthash "\\:white_medium_square:" "◻" julia-latexsubs)
+(puthash "\\:black_medium_square:" "◼" julia-latexsubs)
+(puthash "\\mdblksquare" "◼" julia-latexsubs)
+(puthash "\\:white_medium_small_square:" "◽" julia-latexsubs)
+(puthash "\\mdsmwhtsquare" "◽" julia-latexsubs)
+(puthash "\\mdsmblksquare" "◾" julia-latexsubs)
+(puthash "\\:black_medium_small_square:" "◾" julia-latexsubs)
+(puthash "\\lrtriangle" "◿" julia-latexsubs)
+(puthash "\\:sunny:" "☀" julia-latexsubs)
+(puthash "\\:cloud:" "☁" julia-latexsubs)
+(puthash "\\bigstar" "★" julia-latexsubs)
+(puthash "\\bigwhitestar" "☆" julia-latexsubs)
+(puthash "\\astrosun" "☉" julia-latexsubs)
+(puthash "\\:phone:" "☎" julia-latexsubs)
+(puthash "\\:ballot_box_with_check:" "☑" julia-latexsubs)
+(puthash "\\:umbrella:" "☔" julia-latexsubs)
+(puthash "\\:coffee:" "☕" julia-latexsubs)
+(puthash "\\:point_up:" "☝" julia-latexsubs)
+(puthash "\\danger" "☡" julia-latexsubs)
+(puthash "\\:relaxed:" "☺" julia-latexsubs)
+(puthash "\\blacksmiley" "☻" julia-latexsubs)
+(puthash "\\sun" "☼" julia-latexsubs)
+(puthash "\\rightmoon" "☽" julia-latexsubs)
+(puthash "\\leftmoon" "☾" julia-latexsubs)
+(puthash "\\mercury" "☿" julia-latexsubs)
+(puthash "\\venus" "♀" julia-latexsubs)
+(puthash "\\female" "♀" julia-latexsubs)
+(puthash "\\male" "♂" julia-latexsubs)
+(puthash "\\mars" "♂" julia-latexsubs)
+(puthash "\\jupiter" "♃" julia-latexsubs)
+(puthash "\\saturn" "♄" julia-latexsubs)
+(puthash "\\uranus" "♅" julia-latexsubs)
+(puthash "\\neptune" "♆" julia-latexsubs)
+(puthash "\\pluto" "♇" julia-latexsubs)
+(puthash "\\:aries:" "♈" julia-latexsubs)
+(puthash "\\aries" "♈" julia-latexsubs)
+(puthash "\\:taurus:" "♉" julia-latexsubs)
+(puthash "\\taurus" "♉" julia-latexsubs)
+(puthash "\\:gemini:" "♊" julia-latexsubs)
+(puthash "\\gemini" "♊" julia-latexsubs)
+(puthash "\\:cancer:" "♋" julia-latexsubs)
+(puthash "\\cancer" "♋" julia-latexsubs)
+(puthash "\\leo" "♌" julia-latexsubs)
+(puthash "\\:leo:" "♌" julia-latexsubs)
+(puthash "\\:virgo:" "♍" julia-latexsubs)
+(puthash "\\virgo" "♍" julia-latexsubs)
+(puthash "\\:libra:" "♎" julia-latexsubs)
+(puthash "\\libra" "♎" julia-latexsubs)
+(puthash "\\scorpio" "♏" julia-latexsubs)
+(puthash "\\:scorpius:" "♏" julia-latexsubs)
+(puthash "\\sagittarius" "♐" julia-latexsubs)
+(puthash "\\:sagittarius:" "♐" julia-latexsubs)
+(puthash "\\capricornus" "♑" julia-latexsubs)
+(puthash "\\:capricorn:" "♑" julia-latexsubs)
+(puthash "\\aquarius" "♒" julia-latexsubs)
+(puthash "\\:aquarius:" "♒" julia-latexsubs)
+(puthash "\\pisces" "♓" julia-latexsubs)
+(puthash "\\:pisces:" "♓" julia-latexsubs)
+(puthash "\\spadesuit" "♠" julia-latexsubs)
+(puthash "\\:spades:" "♠" julia-latexsubs)
+(puthash "\\heartsuit" "♡" julia-latexsubs)
+(puthash "\\diamondsuit" "♢" julia-latexsubs)
+(puthash "\\:clubs:" "♣" julia-latexsubs)
+(puthash "\\clubsuit" "♣" julia-latexsubs)
+(puthash "\\varspadesuit" "♤" julia-latexsubs)
+(puthash "\\varheartsuit" "♥" julia-latexsubs)
+(puthash "\\:hearts:" "♥" julia-latexsubs)
+(puthash "\\vardiamondsuit" "♦" julia-latexsubs)
+(puthash "\\:diamonds:" "♦" julia-latexsubs)
+(puthash "\\varclubsuit" "♧" julia-latexsubs)
+(puthash "\\:hotsprings:" "♨" julia-latexsubs)
+(puthash "\\quarternote" "♩" julia-latexsubs)
+(puthash "\\eighthnote" "♪" julia-latexsubs)
+(puthash "\\twonotes" "♫" julia-latexsubs)
+(puthash "\\flat" "♭" julia-latexsubs)
+(puthash "\\natural" "♮" julia-latexsubs)
+(puthash "\\sharp" "♯" julia-latexsubs)
+(puthash "\\:recycle:" "♻" julia-latexsubs)
+(puthash "\\acidfree" "♾" julia-latexsubs)
+(puthash "\\:wheelchair:" "♿" julia-latexsubs)
+(puthash "\\dicei" "⚀" julia-latexsubs)
+(puthash "\\diceii" "⚁" julia-latexsubs)
+(puthash "\\diceiii" "⚂" julia-latexsubs)
+(puthash "\\diceiv" "⚃" julia-latexsubs)
+(puthash "\\dicev" "⚄" julia-latexsubs)
+(puthash "\\dicevi" "⚅" julia-latexsubs)
+(puthash "\\circledrightdot" "⚆" julia-latexsubs)
+(puthash "\\circledtwodots" "⚇" julia-latexsubs)
+(puthash "\\blackcircledrightdot" "⚈" julia-latexsubs)
+(puthash "\\blackcircledtwodots" "⚉" julia-latexsubs)
+(puthash "\\:anchor:" "⚓" julia-latexsubs)
+(puthash "\\:warning:" "⚠" julia-latexsubs)
+(puthash "\\:zap:" "⚡" julia-latexsubs)
+(puthash "\\hermaphrodite" "⚥" julia-latexsubs)
+(puthash "\\:white_circle:" "⚪" julia-latexsubs)
+(puthash "\\mdwhtcircle" "⚪" julia-latexsubs)
+(puthash "\\mdblkcircle" "⚫" julia-latexsubs)
+(puthash "\\:black_circle:" "⚫" julia-latexsubs)
+(puthash "\\mdsmwhtcircle" "⚬" julia-latexsubs)
+(puthash "\\neuter" "⚲" julia-latexsubs)
+(puthash "\\:soccer:" "⚽" julia-latexsubs)
+(puthash "\\:baseball:" "⚾" julia-latexsubs)
+(puthash "\\:snowman:" "⛄" julia-latexsubs)
+(puthash "\\:partly_sunny:" "⛅" julia-latexsubs)
+(puthash "\\:ophiuchus:" "⛎" julia-latexsubs)
+(puthash "\\:no_entry:" "⛔" julia-latexsubs)
+(puthash "\\:church:" "⛪" julia-latexsubs)
+(puthash "\\:fountain:" "⛲" julia-latexsubs)
+(puthash "\\:golf:" "⛳" julia-latexsubs)
+(puthash "\\:boat:" "⛵" julia-latexsubs)
+(puthash "\\:tent:" "⛺" julia-latexsubs)
+(puthash "\\:fuelpump:" "⛽" julia-latexsubs)
+(puthash "\\:scissors:" "✂" julia-latexsubs)
+(puthash "\\:white_check_mark:" "✅" julia-latexsubs)
+(puthash "\\:airplane:" "✈" julia-latexsubs)
+(puthash "\\:email:" "✉" julia-latexsubs)
+(puthash "\\:fist:" "✊" julia-latexsubs)
+(puthash "\\:hand:" "✋" julia-latexsubs)
+(puthash "\\:v:" "✌" julia-latexsubs)
+(puthash "\\:pencil2:" "✏" julia-latexsubs)
+(puthash "\\:black_nib:" "✒" julia-latexsubs)
+(puthash "\\checkmark" "✓" julia-latexsubs)
+(puthash "\\:heavy_check_mark:" "✔" julia-latexsubs)
+(puthash "\\:heavy_multiplication_x:" "✖" julia-latexsubs)
+(puthash "\\maltese" "✠" julia-latexsubs)
+(puthash "\\:sparkles:" "✨" julia-latexsubs)
+(puthash "\\circledstar" "✪" julia-latexsubs)
+(puthash "\\:eight_spoked_asterisk:" "✳" julia-latexsubs)
+(puthash "\\:eight_pointed_black_star:" "✴" julia-latexsubs)
+(puthash "\\varstar" "✶" julia-latexsubs)
+(puthash "\\dingasterisk" "✽" julia-latexsubs)
+(puthash "\\:snowflake:" "❄" julia-latexsubs)
+(puthash "\\:sparkle:" "❇" julia-latexsubs)
+(puthash "\\:x:" "❌" julia-latexsubs)
+(puthash "\\:negative_squared_cross_mark:" "❎" julia-latexsubs)
+(puthash "\\:question:" "❓" julia-latexsubs)
+(puthash "\\:grey_question:" "❔" julia-latexsubs)
+(puthash "\\:grey_exclamation:" "❕" julia-latexsubs)
+(puthash "\\:exclamation:" "❗" julia-latexsubs)
+(puthash "\\:heart:" "❤" julia-latexsubs)
+(puthash "\\:heavy_plus_sign:" "➕" julia-latexsubs)
+(puthash "\\:heavy_minus_sign:" "➖" julia-latexsubs)
+(puthash "\\:heavy_division_sign:" "➗" julia-latexsubs)
+(puthash "\\draftingarrow" "➛" julia-latexsubs)
+(puthash "\\:arrow_right:" "➡" julia-latexsubs)
+(puthash "\\:curly_loop:" "➰" julia-latexsubs)
+(puthash "\\:loop:" "➿" julia-latexsubs)
+(puthash "\\threedangle" "⟀" julia-latexsubs)
+(puthash "\\whiteinwhitetriangle" "⟁" julia-latexsubs)
+(puthash "\\perp" "⟂" julia-latexsubs)
+(puthash "\\bsolhsub" "⟈" julia-latexsubs)
+(puthash "\\suphsol" "⟉" julia-latexsubs)
+(puthash "\\wedgedot" "⟑" julia-latexsubs)
+(puthash "\\upin" "⟒" julia-latexsubs)
+(puthash "\\leftouterjoin" "⟕" julia-latexsubs)
+(puthash "\\rightouterjoin" "⟖" julia-latexsubs)
+(puthash "\\fullouterjoin" "⟗" julia-latexsubs)
+(puthash "\\bigbot" "⟘" julia-latexsubs)
+(puthash "\\bigtop" "⟙" julia-latexsubs)
+(puthash "\\openbracketleft" "⟦" julia-latexsubs)
+(puthash "\\llbracket" "⟦" julia-latexsubs)
+(puthash "\\openbracketright" "⟧" julia-latexsubs)
+(puthash "\\rrbracket" "⟧" julia-latexsubs)
+(puthash "\\langle" "⟨" julia-latexsubs)
+(puthash "\\rangle" "⟩" julia-latexsubs)
+(puthash "\\UUparrow" "⟰" julia-latexsubs)
+(puthash "\\DDownarrow" "⟱" julia-latexsubs)
+(puthash "\\longleftarrow" "⟵" julia-latexsubs)
+(puthash "\\longrightarrow" "⟶" julia-latexsubs)
+(puthash "\\longleftrightarrow" "⟷" julia-latexsubs)
+(puthash "\\impliedby" "⟸" julia-latexsubs)
+(puthash "\\Longleftarrow" "⟸" julia-latexsubs)
+(puthash "\\implies" "⟹" julia-latexsubs)
+(puthash "\\Longrightarrow" "⟹" julia-latexsubs)
+(puthash "\\iff" "⟺" julia-latexsubs)
+(puthash "\\Longleftrightarrow" "⟺" julia-latexsubs)
+(puthash "\\longmapsfrom" "⟻" julia-latexsubs)
+(puthash "\\longmapsto" "⟼" julia-latexsubs)
+(puthash "\\Longmapsfrom" "⟽" julia-latexsubs)
+(puthash "\\Longmapsto" "⟾" julia-latexsubs)
+(puthash "\\longrightsquigarrow" "⟿" julia-latexsubs)
+(puthash "\\nvtwoheadrightarrow" "⤀" julia-latexsubs)
+(puthash "\\nVtwoheadrightarrow" "⤁" julia-latexsubs)
+(puthash "\\nvLeftarrow" "⤂" julia-latexsubs)
+(puthash "\\nvRightarrow" "⤃" julia-latexsubs)
+(puthash "\\nvLeftrightarrow" "⤄" julia-latexsubs)
+(puthash "\\twoheadmapsto" "⤅" julia-latexsubs)
+(puthash "\\Mapsfrom" "⤆" julia-latexsubs)
+(puthash "\\Mapsto" "⤇" julia-latexsubs)
+(puthash "\\downarrowbarred" "⤈" julia-latexsubs)
+(puthash "\\uparrowbarred" "⤉" julia-latexsubs)
+(puthash "\\Uuparrow" "⤊" julia-latexsubs)
+(puthash "\\Ddownarrow" "⤋" julia-latexsubs)
+(puthash "\\leftbkarrow" "⤌" julia-latexsubs)
+(puthash "\\bkarow" "⤍" julia-latexsubs)
+(puthash "\\leftdbkarrow" "⤎" julia-latexsubs)
+(puthash "\\dbkarow" "⤏" julia-latexsubs)
+(puthash "\\drbkarrow" "⤐" julia-latexsubs)
+(puthash "\\rightdotarrow" "⤑" julia-latexsubs)
+(puthash "\\UpArrowBar" "⤒" julia-latexsubs)
+(puthash "\\DownArrowBar" "⤓" julia-latexsubs)
+(puthash "\\nvrightarrowtail" "⤔" julia-latexsubs)
+(puthash "\\nVrightarrowtail" "⤕" julia-latexsubs)
+(puthash "\\twoheadrightarrowtail" "⤖" julia-latexsubs)
+(puthash "\\nvtwoheadrightarrowtail" "⤗" julia-latexsubs)
+(puthash "\\nVtwoheadrightarrowtail" "⤘" julia-latexsubs)
+(puthash "\\diamondleftarrow" "⤝" julia-latexsubs)
+(puthash "\\rightarrowdiamond" "⤞" julia-latexsubs)
+(puthash "\\diamondleftarrowbar" "⤟" julia-latexsubs)
+(puthash "\\barrightarrowdiamond" "⤠" julia-latexsubs)
+(puthash "\\hksearow" "⤥" julia-latexsubs)
+(puthash "\\hkswarow" "⤦" julia-latexsubs)
+(puthash "\\tona" "⤧" julia-latexsubs)
+(puthash "\\toea" "⤨" julia-latexsubs)
+(puthash "\\tosa" "⤩" julia-latexsubs)
+(puthash "\\towa" "⤪" julia-latexsubs)
+(puthash "\\rdiagovfdiag" "⤫" julia-latexsubs)
+(puthash "\\fdiagovrdiag" "⤬" julia-latexsubs)
+(puthash "\\seovnearrow" "⤭" julia-latexsubs)
+(puthash "\\neovsearrow" "⤮" julia-latexsubs)
+(puthash "\\fdiagovnearrow" "⤯" julia-latexsubs)
+(puthash "\\rdiagovsearrow" "⤰" julia-latexsubs)
+(puthash "\\neovnwarrow" "⤱" julia-latexsubs)
+(puthash "\\nwovnearrow" "⤲" julia-latexsubs)
+(puthash "\\:arrow_heading_up:" "⤴" julia-latexsubs)
+(puthash "\\:arrow_heading_down:" "⤵" julia-latexsubs)
+(puthash "\\Rlarr" "⥂" julia-latexsubs)
+(puthash "\\rLarr" "⥄" julia-latexsubs)
+(puthash "\\rightarrowplus" "⥅" julia-latexsubs)
+(puthash "\\leftarrowplus" "⥆" julia-latexsubs)
+(puthash "\\rarrx" "⥇" julia-latexsubs)
+(puthash "\\leftrightarrowcircle" "⥈" julia-latexsubs)
+(puthash "\\twoheaduparrowcircle" "⥉" julia-latexsubs)
+(puthash "\\leftrightharpoonupdown" "⥊" julia-latexsubs)
+(puthash "\\leftrightharpoondownup" "⥋" julia-latexsubs)
+(puthash "\\updownharpoonrightleft" "⥌" julia-latexsubs)
+(puthash "\\updownharpoonleftright" "⥍" julia-latexsubs)
+(puthash "\\LeftRightVector" "⥎" julia-latexsubs)
+(puthash "\\RightUpDownVector" "⥏" julia-latexsubs)
+(puthash "\\DownLeftRightVector" "⥐" julia-latexsubs)
+(puthash "\\LeftUpDownVector" "⥑" julia-latexsubs)
+(puthash "\\LeftVectorBar" "⥒" julia-latexsubs)
+(puthash "\\RightVectorBar" "⥓" julia-latexsubs)
+(puthash "\\RightUpVectorBar" "⥔" julia-latexsubs)
+(puthash "\\RightDownVectorBar" "⥕" julia-latexsubs)
+(puthash "\\DownLeftVectorBar" "⥖" julia-latexsubs)
+(puthash "\\DownRightVectorBar" "⥗" julia-latexsubs)
+(puthash "\\LeftUpVectorBar" "⥘" julia-latexsubs)
+(puthash "\\LeftDownVectorBar" "⥙" julia-latexsubs)
+(puthash "\\LeftTeeVector" "⥚" julia-latexsubs)
+(puthash "\\RightTeeVector" "⥛" julia-latexsubs)
+(puthash "\\RightUpTeeVector" "⥜" julia-latexsubs)
+(puthash "\\RightDownTeeVector" "⥝" julia-latexsubs)
+(puthash "\\DownLeftTeeVector" "⥞" julia-latexsubs)
+(puthash "\\DownRightTeeVector" "⥟" julia-latexsubs)
+(puthash "\\LeftUpTeeVector" "⥠" julia-latexsubs)
+(puthash "\\LeftDownTeeVector" "⥡" julia-latexsubs)
+(puthash "\\leftharpoonsupdown" "⥢" julia-latexsubs)
+(puthash "\\upharpoonsleftright" "⥣" julia-latexsubs)
+(puthash "\\rightharpoonsupdown" "⥤" julia-latexsubs)
+(puthash "\\downharpoonsleftright" "⥥" julia-latexsubs)
+(puthash "\\leftrightharpoonsup" "⥦" julia-latexsubs)
+(puthash "\\leftrightharpoonsdown" "⥧" julia-latexsubs)
+(puthash "\\rightleftharpoonsup" "⥨" julia-latexsubs)
+(puthash "\\rightleftharpoonsdown" "⥩" julia-latexsubs)
+(puthash "\\leftharpoonupdash" "⥪" julia-latexsubs)
+(puthash "\\dashleftharpoondown" "⥫" julia-latexsubs)
+(puthash "\\rightharpoonupdash" "⥬" julia-latexsubs)
+(puthash "\\dashrightharpoondown" "⥭" julia-latexsubs)
+(puthash "\\UpEquilibrium" "⥮" julia-latexsubs)
+(puthash "\\ReverseUpEquilibrium" "⥯" julia-latexsubs)
+(puthash "\\RoundImplies" "⥰" julia-latexsubs)
+(puthash "\\Vvert" "⦀" julia-latexsubs)
+(puthash "\\Elroang" "⦆" julia-latexsubs)
+(puthash "\\ddfnc" "⦙" julia-latexsubs)
+(puthash "\\measuredangleleft" "⦛" julia-latexsubs)
+(puthash "\\Angle" "⦜" julia-latexsubs)
+(puthash "\\rightanglemdot" "⦝" julia-latexsubs)
+(puthash "\\angles" "⦞" julia-latexsubs)
+(puthash "\\angdnr" "⦟" julia-latexsubs)
+(puthash "\\lpargt" "⦠" julia-latexsubs)
+(puthash "\\sphericalangleup" "⦡" julia-latexsubs)
+(puthash "\\turnangle" "⦢" julia-latexsubs)
+(puthash "\\revangle" "⦣" julia-latexsubs)
+(puthash "\\angleubar" "⦤" julia-latexsubs)
+(puthash "\\revangleubar" "⦥" julia-latexsubs)
+(puthash "\\wideangledown" "⦦" julia-latexsubs)
+(puthash "\\wideangleup" "⦧" julia-latexsubs)
+(puthash "\\measanglerutone" "⦨" julia-latexsubs)
+(puthash "\\measanglelutonw" "⦩" julia-latexsubs)
+(puthash "\\measanglerdtose" "⦪" julia-latexsubs)
+(puthash "\\measangleldtosw" "⦫" julia-latexsubs)
+(puthash "\\measangleurtone" "⦬" julia-latexsubs)
+(puthash "\\measangleultonw" "⦭" julia-latexsubs)
+(puthash "\\measangledrtose" "⦮" julia-latexsubs)
+(puthash "\\measangledltosw" "⦯" julia-latexsubs)
+(puthash "\\revemptyset" "⦰" julia-latexsubs)
+(puthash "\\emptysetobar" "⦱" julia-latexsubs)
+(puthash "\\emptysetocirc" "⦲" julia-latexsubs)
+(puthash "\\emptysetoarr" "⦳" julia-latexsubs)
+(puthash "\\emptysetoarrl" "⦴" julia-latexsubs)
+(puthash "\\circledparallel" "⦷" julia-latexsubs)
+(puthash "\\obslash" "⦸" julia-latexsubs)
+(puthash "\\odotslashdot" "⦼" julia-latexsubs)
+(puthash "\\circledwhitebullet" "⦾" julia-latexsubs)
+(puthash "\\circledbullet" "⦿" julia-latexsubs)
+(puthash "\\olessthan" "⧀" julia-latexsubs)
+(puthash "\\ogreaterthan" "⧁" julia-latexsubs)
+(puthash "\\boxdiag" "⧄" julia-latexsubs)
+(puthash "\\boxbslash" "⧅" julia-latexsubs)
+(puthash "\\boxast" "⧆" julia-latexsubs)
+(puthash "\\boxcircle" "⧇" julia-latexsubs)
+(puthash "\\Lap" "⧊" julia-latexsubs)
+(puthash "\\defas" "⧋" julia-latexsubs)
+(puthash "\\LeftTriangleBar" "⧏" julia-latexsubs)
+(puthash "\\NotLeftTriangleBar" "⧏̸" julia-latexsubs)
+(puthash "\\RightTriangleBar" "⧐" julia-latexsubs)
+(puthash "\\NotRightTriangleBar" "⧐̸" julia-latexsubs)
+(puthash "\\dualmap" "⧟" julia-latexsubs)
+(puthash "\\lrtriangleeq" "⧡" julia-latexsubs)
+(puthash "\\shuffle" "⧢" julia-latexsubs)
+(puthash "\\eparsl" "⧣" julia-latexsubs)
+(puthash "\\smeparsl" "⧤" julia-latexsubs)
+(puthash "\\eqvparsl" "⧥" julia-latexsubs)
+(puthash "\\blacklozenge" "⧫" julia-latexsubs)
+(puthash "\\RuleDelayed" "⧴" julia-latexsubs)
+(puthash "\\dsol" "⧶" julia-latexsubs)
+(puthash "\\rsolbar" "⧷" julia-latexsubs)
+(puthash "\\doubleplus" "⧺" julia-latexsubs)
+(puthash "\\tripleplus" "⧻" julia-latexsubs)
+(puthash "\\bigodot" "⨀" julia-latexsubs)
+(puthash "\\bigoplus" "⨁" julia-latexsubs)
+(puthash "\\bigotimes" "⨂" julia-latexsubs)
+(puthash "\\bigcupdot" "⨃" julia-latexsubs)
+(puthash "\\biguplus" "⨄" julia-latexsubs)
+(puthash "\\bigsqcap" "⨅" julia-latexsubs)
+(puthash "\\bigsqcup" "⨆" julia-latexsubs)
+(puthash "\\conjquant" "⨇" julia-latexsubs)
+(puthash "\\disjquant" "⨈" julia-latexsubs)
+(puthash "\\bigtimes" "⨉" julia-latexsubs)
+(puthash "\\modtwosum" "⨊" julia-latexsubs)
+(puthash "\\sumint" "⨋" julia-latexsubs)
+(puthash "\\iiiint" "⨌" julia-latexsubs)
+(puthash "\\intbar" "⨍" julia-latexsubs)
+(puthash "\\intBar" "⨎" julia-latexsubs)
+(puthash "\\clockoint" "⨏" julia-latexsubs)
+(puthash "\\cirfnint" "⨐" julia-latexsubs)
+(puthash "\\awint" "⨑" julia-latexsubs)
+(puthash "\\rppolint" "⨒" julia-latexsubs)
+(puthash "\\scpolint" "⨓" julia-latexsubs)
+(puthash "\\npolint" "⨔" julia-latexsubs)
+(puthash "\\pointint" "⨕" julia-latexsubs)
+(puthash "\\sqrint" "⨖" julia-latexsubs)
+(puthash "\\intx" "⨘" julia-latexsubs)
+(puthash "\\intcap" "⨙" julia-latexsubs)
+(puthash "\\intcup" "⨚" julia-latexsubs)
+(puthash "\\upint" "⨛" julia-latexsubs)
+(puthash "\\lowint" "⨜" julia-latexsubs)
+(puthash "\\join" "⨝" julia-latexsubs)
+(puthash "\\Join" "⨝" julia-latexsubs)
+(puthash "\\ringplus" "⨢" julia-latexsubs)
+(puthash "\\plushat" "⨣" julia-latexsubs)
+(puthash "\\simplus" "⨤" julia-latexsubs)
+(puthash "\\plusdot" "⨥" julia-latexsubs)
+(puthash "\\plussim" "⨦" julia-latexsubs)
+(puthash "\\plussubtwo" "⨧" julia-latexsubs)
+(puthash "\\plustrif" "⨨" julia-latexsubs)
+(puthash "\\commaminus" "⨩" julia-latexsubs)
+(puthash "\\minusdot" "⨪" julia-latexsubs)
+(puthash "\\minusfdots" "⨫" julia-latexsubs)
+(puthash "\\minusrdots" "⨬" julia-latexsubs)
+(puthash "\\opluslhrim" "⨭" julia-latexsubs)
+(puthash "\\oplusrhrim" "⨮" julia-latexsubs)
+(puthash "\\Times" "⨯" julia-latexsubs)
+(puthash "\\dottimes" "⨰" julia-latexsubs)
+(puthash "\\timesbar" "⨱" julia-latexsubs)
+(puthash "\\btimes" "⨲" julia-latexsubs)
+(puthash "\\smashtimes" "⨳" julia-latexsubs)
+(puthash "\\otimeslhrim" "⨴" julia-latexsubs)
+(puthash "\\otimesrhrim" "⨵" julia-latexsubs)
+(puthash "\\otimeshat" "⨶" julia-latexsubs)
+(puthash "\\Otimes" "⨷" julia-latexsubs)
+(puthash "\\odiv" "⨸" julia-latexsubs)
+(puthash "\\triangleplus" "⨹" julia-latexsubs)
+(puthash "\\triangleminus" "⨺" julia-latexsubs)
+(puthash "\\triangletimes" "⨻" julia-latexsubs)
+(puthash "\\intprod" "⨼" julia-latexsubs)
+(puthash "\\intprodr" "⨽" julia-latexsubs)
+(puthash "\\amalg" "⨿" julia-latexsubs)
+(puthash "\\capdot" "⩀" julia-latexsubs)
+(puthash "\\uminus" "⩁" julia-latexsubs)
+(puthash "\\barcup" "⩂" julia-latexsubs)
+(puthash "\\barcap" "⩃" julia-latexsubs)
+(puthash "\\capwedge" "⩄" julia-latexsubs)
+(puthash "\\cupvee" "⩅" julia-latexsubs)
+(puthash "\\twocups" "⩊" julia-latexsubs)
+(puthash "\\twocaps" "⩋" julia-latexsubs)
+(puthash "\\closedvarcup" "⩌" julia-latexsubs)
+(puthash "\\closedvarcap" "⩍" julia-latexsubs)
+(puthash "\\Sqcap" "⩎" julia-latexsubs)
+(puthash "\\Sqcup" "⩏" julia-latexsubs)
+(puthash "\\closedvarcupsmashprod" "⩐" julia-latexsubs)
+(puthash "\\wedgeodot" "⩑" julia-latexsubs)
+(puthash "\\veeodot" "⩒" julia-latexsubs)
+(puthash "\\And" "⩓" julia-latexsubs)
+(puthash "\\Or" "⩔" julia-latexsubs)
+(puthash "\\wedgeonwedge" "⩕" julia-latexsubs)
+(puthash "\\ElOr" "⩖" julia-latexsubs)
+(puthash "\\bigslopedvee" "⩗" julia-latexsubs)
+(puthash "\\bigslopedwedge" "⩘" julia-latexsubs)
+(puthash "\\wedgemidvert" "⩚" julia-latexsubs)
+(puthash "\\veemidvert" "⩛" julia-latexsubs)
+(puthash "\\midbarwedge" "⩜" julia-latexsubs)
+(puthash "\\midbarvee" "⩝" julia-latexsubs)
+(puthash "\\perspcorrespond" "⩞" julia-latexsubs)
+(puthash "\\minhat" "⩟" julia-latexsubs)
+(puthash "\\wedgedoublebar" "⩠" julia-latexsubs)
+(puthash "\\varveebar" "⩡" julia-latexsubs)
+(puthash "\\doublebarvee" "⩢" julia-latexsubs)
+(puthash "\\veedoublebar" "⩣" julia-latexsubs)
+(puthash "\\eqdot" "⩦" julia-latexsubs)
+(puthash "\\dotequiv" "⩧" julia-latexsubs)
+(puthash "\\dotsim" "⩪" julia-latexsubs)
+(puthash "\\simrdots" "⩫" julia-latexsubs)
+(puthash "\\simminussim" "⩬" julia-latexsubs)
+(puthash "\\congdot" "⩭" julia-latexsubs)
+(puthash "\\asteq" "⩮" julia-latexsubs)
+(puthash "\\hatapprox" "⩯" julia-latexsubs)
+(puthash "\\approxeqq" "⩰" julia-latexsubs)
+(puthash "\\eqqplus" "⩱" julia-latexsubs)
+(puthash "\\pluseqq" "⩲" julia-latexsubs)
+(puthash "\\eqqsim" "⩳" julia-latexsubs)
+(puthash "\\Coloneq" "⩴" julia-latexsubs)
+(puthash "\\Equal" "⩵" julia-latexsubs)
+(puthash "\\eqeqeq" "⩶" julia-latexsubs)
+(puthash "\\ddotseq" "⩷" julia-latexsubs)
+(puthash "\\equivDD" "⩸" julia-latexsubs)
+(puthash "\\ltcir" "⩹" julia-latexsubs)
+(puthash "\\gtcir" "⩺" julia-latexsubs)
+(puthash "\\ltquest" "⩻" julia-latexsubs)
+(puthash "\\gtquest" "⩼" julia-latexsubs)
+(puthash "\\leqslant" "⩽" julia-latexsubs)
+(puthash "\\nleqslant" "⩽̸" julia-latexsubs)
+(puthash "\\geqslant" "⩾" julia-latexsubs)
+(puthash "\\ngeqslant" "⩾̸" julia-latexsubs)
+(puthash "\\lesdot" "⩿" julia-latexsubs)
+(puthash "\\gesdot" "⪀" julia-latexsubs)
+(puthash "\\lesdoto" "⪁" julia-latexsubs)
+(puthash "\\gesdoto" "⪂" julia-latexsubs)
+(puthash "\\lesdotor" "⪃" julia-latexsubs)
+(puthash "\\gesdotol" "⪄" julia-latexsubs)
+(puthash "\\lessapprox" "⪅" julia-latexsubs)
+(puthash "\\gtrapprox" "⪆" julia-latexsubs)
+(puthash "\\lneq" "⪇" julia-latexsubs)
+(puthash "\\gneq" "⪈" julia-latexsubs)
+(puthash "\\lnapprox" "⪉" julia-latexsubs)
+(puthash "\\gnapprox" "⪊" julia-latexsubs)
+(puthash "\\lesseqqgtr" "⪋" julia-latexsubs)
+(puthash "\\gtreqqless" "⪌" julia-latexsubs)
+(puthash "\\lsime" "⪍" julia-latexsubs)
+(puthash "\\gsime" "⪎" julia-latexsubs)
+(puthash "\\lsimg" "⪏" julia-latexsubs)
+(puthash "\\gsiml" "⪐" julia-latexsubs)
+(puthash "\\lgE" "⪑" julia-latexsubs)
+(puthash "\\glE" "⪒" julia-latexsubs)
+(puthash "\\lesges" "⪓" julia-latexsubs)
+(puthash "\\gesles" "⪔" julia-latexsubs)
+(puthash "\\eqslantless" "⪕" julia-latexsubs)
+(puthash "\\eqslantgtr" "⪖" julia-latexsubs)
+(puthash "\\elsdot" "⪗" julia-latexsubs)
+(puthash "\\egsdot" "⪘" julia-latexsubs)
+(puthash "\\eqqless" "⪙" julia-latexsubs)
+(puthash "\\eqqgtr" "⪚" julia-latexsubs)
+(puthash "\\eqqslantless" "⪛" julia-latexsubs)
+(puthash "\\eqqslantgtr" "⪜" julia-latexsubs)
+(puthash "\\simless" "⪝" julia-latexsubs)
+(puthash "\\simgtr" "⪞" julia-latexsubs)
+(puthash "\\simlE" "⪟" julia-latexsubs)
+(puthash "\\simgE" "⪠" julia-latexsubs)
+(puthash "\\NestedLessLess" "⪡" julia-latexsubs)
+(puthash "\\NotNestedLessLess" "⪡̸" julia-latexsubs)
+(puthash "\\NestedGreaterGreater" "⪢" julia-latexsubs)
+(puthash "\\NotNestedGreaterGreater" "⪢̸" julia-latexsubs)
+(puthash "\\partialmeetcontraction" "⪣" julia-latexsubs)
+(puthash "\\glj" "⪤" julia-latexsubs)
+(puthash "\\gla" "⪥" julia-latexsubs)
+(puthash "\\ltcc" "⪦" julia-latexsubs)
+(puthash "\\gtcc" "⪧" julia-latexsubs)
+(puthash "\\lescc" "⪨" julia-latexsubs)
+(puthash "\\gescc" "⪩" julia-latexsubs)
+(puthash "\\smt" "⪪" julia-latexsubs)
+(puthash "\\lat" "⪫" julia-latexsubs)
+(puthash "\\smte" "⪬" julia-latexsubs)
+(puthash "\\late" "⪭" julia-latexsubs)
+(puthash "\\bumpeqq" "⪮" julia-latexsubs)
+(puthash "\\preceq" "⪯" julia-latexsubs)
+(puthash "\\npreceq" "⪯̸" julia-latexsubs)
+(puthash "\\succeq" "⪰" julia-latexsubs)
+(puthash "\\nsucceq" "⪰̸" julia-latexsubs)
+(puthash "\\precneq" "⪱" julia-latexsubs)
+(puthash "\\succneq" "⪲" julia-latexsubs)
+(puthash "\\preceqq" "⪳" julia-latexsubs)
+(puthash "\\succeqq" "⪴" julia-latexsubs)
+(puthash "\\precneqq" "⪵" julia-latexsubs)
+(puthash "\\succneqq" "⪶" julia-latexsubs)
+(puthash "\\precapprox" "⪷" julia-latexsubs)
+(puthash "\\succapprox" "⪸" julia-latexsubs)
+(puthash "\\precnapprox" "⪹" julia-latexsubs)
+(puthash "\\succnapprox" "⪺" julia-latexsubs)
+(puthash "\\Prec" "⪻" julia-latexsubs)
+(puthash "\\Succ" "⪼" julia-latexsubs)
+(puthash "\\subsetdot" "⪽" julia-latexsubs)
+(puthash "\\supsetdot" "⪾" julia-latexsubs)
+(puthash "\\subsetplus" "⪿" julia-latexsubs)
+(puthash "\\supsetplus" "⫀" julia-latexsubs)
+(puthash "\\submult" "⫁" julia-latexsubs)
+(puthash "\\supmult" "⫂" julia-latexsubs)
+(puthash "\\subedot" "⫃" julia-latexsubs)
+(puthash "\\supedot" "⫄" julia-latexsubs)
+(puthash "\\subseteqq" "⫅" julia-latexsubs)
+(puthash "\\nsubseteqq" "⫅̸" julia-latexsubs)
+(puthash "\\supseteqq" "⫆" julia-latexsubs)
+(puthash "\\nsupseteqq" "⫆̸" julia-latexsubs)
+(puthash "\\subsim" "⫇" julia-latexsubs)
+(puthash "\\supsim" "⫈" julia-latexsubs)
+(puthash "\\subsetapprox" "⫉" julia-latexsubs)
+(puthash "\\supsetapprox" "⫊" julia-latexsubs)
+(puthash "\\subsetneqq" "⫋" julia-latexsubs)
+(puthash "\\supsetneqq" "⫌" julia-latexsubs)
+(puthash "\\lsqhook" "⫍" julia-latexsubs)
+(puthash "\\rsqhook" "⫎" julia-latexsubs)
+(puthash "\\csub" "⫏" julia-latexsubs)
+(puthash "\\csup" "⫐" julia-latexsubs)
+(puthash "\\csube" "⫑" julia-latexsubs)
+(puthash "\\csupe" "⫒" julia-latexsubs)
+(puthash "\\subsup" "⫓" julia-latexsubs)
+(puthash "\\supsub" "⫔" julia-latexsubs)
+(puthash "\\subsub" "⫕" julia-latexsubs)
+(puthash "\\supsup" "⫖" julia-latexsubs)
+(puthash "\\suphsub" "⫗" julia-latexsubs)
+(puthash "\\supdsub" "⫘" julia-latexsubs)
+(puthash "\\forkv" "⫙" julia-latexsubs)
+(puthash "\\mlcp" "⫛" julia-latexsubs)
+(puthash "\\forks" "⫝̸" julia-latexsubs)
+(puthash "\\forksnot" "⫝" julia-latexsubs)
+(puthash "\\dashV" "⫣" julia-latexsubs)
+(puthash "\\Dashv" "⫤" julia-latexsubs)
+(puthash "\\interleave" "⫴" julia-latexsubs)
+(puthash "\\tdcol" "⫶" julia-latexsubs)
+(puthash "\\lllnest" "⫷" julia-latexsubs)
+(puthash "\\gggnest" "⫸" julia-latexsubs)
+(puthash "\\leqqslant" "⫹" julia-latexsubs)
+(puthash "\\geqqslant" "⫺" julia-latexsubs)
+(puthash "\\:arrow_left:" "⬅" julia-latexsubs)
+(puthash "\\:arrow_up:" "⬆" julia-latexsubs)
+(puthash "\\:arrow_down:" "⬇" julia-latexsubs)
+(puthash "\\squaretopblack" "⬒" julia-latexsubs)
+(puthash "\\squarebotblack" "⬓" julia-latexsubs)
+(puthash "\\squareurblack" "⬔" julia-latexsubs)
+(puthash "\\squarellblack" "⬕" julia-latexsubs)
+(puthash "\\diamondleftblack" "⬖" julia-latexsubs)
+(puthash "\\diamondrightblack" "⬗" julia-latexsubs)
+(puthash "\\diamondtopblack" "⬘" julia-latexsubs)
+(puthash "\\diamondbotblack" "⬙" julia-latexsubs)
+(puthash "\\dottedsquare" "⬚" julia-latexsubs)
+(puthash "\\lgblksquare" "⬛" julia-latexsubs)
+(puthash "\\:black_large_square:" "⬛" julia-latexsubs)
+(puthash "\\lgwhtsquare" "⬜" julia-latexsubs)
+(puthash "\\:white_large_square:" "⬜" julia-latexsubs)
+(puthash "\\vysmblksquare" "⬝" julia-latexsubs)
+(puthash "\\vysmwhtsquare" "⬞" julia-latexsubs)
+(puthash "\\pentagonblack" "⬟" julia-latexsubs)
+(puthash "\\pentagon" "⬠" julia-latexsubs)
+(puthash "\\varhexagon" "⬡" julia-latexsubs)
+(puthash "\\varhexagonblack" "⬢" julia-latexsubs)
+(puthash "\\hexagonblack" "⬣" julia-latexsubs)
+(puthash "\\lgblkcircle" "⬤" julia-latexsubs)
+(puthash "\\mdblkdiamond" "⬥" julia-latexsubs)
+(puthash "\\mdwhtdiamond" "⬦" julia-latexsubs)
+(puthash "\\mdblklozenge" "⬧" julia-latexsubs)
+(puthash "\\mdwhtlozenge" "⬨" julia-latexsubs)
+(puthash "\\smblkdiamond" "⬩" julia-latexsubs)
+(puthash "\\smblklozenge" "⬪" julia-latexsubs)
+(puthash "\\smwhtlozenge" "⬫" julia-latexsubs)
+(puthash "\\blkhorzoval" "⬬" julia-latexsubs)
+(puthash "\\whthorzoval" "⬭" julia-latexsubs)
+(puthash "\\blkvertoval" "⬮" julia-latexsubs)
+(puthash "\\whtvertoval" "⬯" julia-latexsubs)
+(puthash "\\circleonleftarrow" "⬰" julia-latexsubs)
+(puthash "\\leftthreearrows" "⬱" julia-latexsubs)
+(puthash "\\leftarrowonoplus" "⬲" julia-latexsubs)
+(puthash "\\longleftsquigarrow" "⬳" julia-latexsubs)
+(puthash "\\nvtwoheadleftarrow" "⬴" julia-latexsubs)
+(puthash "\\nVtwoheadleftarrow" "⬵" julia-latexsubs)
+(puthash "\\twoheadmapsfrom" "⬶" julia-latexsubs)
+(puthash "\\twoheadleftdbkarrow" "⬷" julia-latexsubs)
+(puthash "\\leftdotarrow" "⬸" julia-latexsubs)
+(puthash "\\nvleftarrowtail" "⬹" julia-latexsubs)
+(puthash "\\nVleftarrowtail" "⬺" julia-latexsubs)
+(puthash "\\twoheadleftarrowtail" "⬻" julia-latexsubs)
+(puthash "\\nvtwoheadleftarrowtail" "⬼" julia-latexsubs)
+(puthash "\\nVtwoheadleftarrowtail" "⬽" julia-latexsubs)
+(puthash "\\leftarrowx" "⬾" julia-latexsubs)
+(puthash "\\leftcurvedarrow" "⬿" julia-latexsubs)
+(puthash "\\equalleftarrow" "⭀" julia-latexsubs)
+(puthash "\\bsimilarleftarrow" "⭁" julia-latexsubs)
+(puthash "\\leftarrowbackapprox" "⭂" julia-latexsubs)
+(puthash "\\rightarrowgtr" "⭃" julia-latexsubs)
+(puthash "\\rightarrowsupset" "⭄" julia-latexsubs)
+(puthash "\\LLeftarrow" "⭅" julia-latexsubs)
+(puthash "\\RRightarrow" "⭆" julia-latexsubs)
+(puthash "\\bsimilarrightarrow" "⭇" julia-latexsubs)
+(puthash "\\rightarrowbackapprox" "⭈" julia-latexsubs)
+(puthash "\\similarleftarrow" "⭉" julia-latexsubs)
+(puthash "\\leftarrowapprox" "⭊" julia-latexsubs)
+(puthash "\\leftarrowbsimilar" "⭋" julia-latexsubs)
+(puthash "\\rightarrowbsimilar" "⭌" julia-latexsubs)
+(puthash "\\:star:" "⭐" julia-latexsubs)
+(puthash "\\medwhitestar" "⭐" julia-latexsubs)
+(puthash "\\medblackstar" "⭑" julia-latexsubs)
+(puthash "\\smwhitestar" "⭒" julia-latexsubs)
+(puthash "\\rightpentagonblack" "⭓" julia-latexsubs)
+(puthash "\\rightpentagon" "⭔" julia-latexsubs)
+(puthash "\\:o:" "⭕" julia-latexsubs)
+(puthash "\\_j" "ⱼ" julia-latexsubs)
+(puthash "\\^V" "ⱽ" julia-latexsubs)
+(puthash "\\postalmark" "〒" julia-latexsubs)
+(puthash "\\:wavy_dash:" "〰" julia-latexsubs)
+(puthash "\\:part_alternation_mark:" "〽" julia-latexsubs)
+(puthash "\\:congratulations:" "㊗" julia-latexsubs)
+(puthash "\\:secret:" "㊙" julia-latexsubs)
+(puthash "\\bfA" "𝐀" julia-latexsubs)
+(puthash "\\bfB" "𝐁" julia-latexsubs)
+(puthash "\\bfC" "𝐂" julia-latexsubs)
+(puthash "\\bfD" "𝐃" julia-latexsubs)
+(puthash "\\bfE" "𝐄" julia-latexsubs)
+(puthash "\\bfF" "𝐅" julia-latexsubs)
+(puthash "\\bfG" "𝐆" julia-latexsubs)
+(puthash "\\bfH" "𝐇" julia-latexsubs)
+(puthash "\\bfI" "𝐈" julia-latexsubs)
+(puthash "\\bfJ" "𝐉" julia-latexsubs)
+(puthash "\\bfK" "𝐊" julia-latexsubs)
+(puthash "\\bfL" "𝐋" julia-latexsubs)
+(puthash "\\bfM" "𝐌" julia-latexsubs)
+(puthash "\\bfN" "𝐍" julia-latexsubs)
+(puthash "\\bfO" "𝐎" julia-latexsubs)
+(puthash "\\bfP" "𝐏" julia-latexsubs)
+(puthash "\\bfQ" "𝐐" julia-latexsubs)
+(puthash "\\bfR" "𝐑" julia-latexsubs)
+(puthash "\\bfS" "𝐒" julia-latexsubs)
+(puthash "\\bfT" "𝐓" julia-latexsubs)
+(puthash "\\bfU" "𝐔" julia-latexsubs)
+(puthash "\\bfV" "𝐕" julia-latexsubs)
+(puthash "\\bfW" "𝐖" julia-latexsubs)
+(puthash "\\bfX" "𝐗" julia-latexsubs)
+(puthash "\\bfY" "𝐘" julia-latexsubs)
+(puthash "\\bfZ" "𝐙" julia-latexsubs)
+(puthash "\\bfa" "𝐚" julia-latexsubs)
+(puthash "\\bfb" "𝐛" julia-latexsubs)
+(puthash "\\bfc" "𝐜" julia-latexsubs)
+(puthash "\\bfd" "𝐝" julia-latexsubs)
+(puthash "\\bfe" "𝐞" julia-latexsubs)
+(puthash "\\bff" "𝐟" julia-latexsubs)
+(puthash "\\bfg" "𝐠" julia-latexsubs)
+(puthash "\\bfh" "𝐡" julia-latexsubs)
+(puthash "\\bfi" "𝐢" julia-latexsubs)
+(puthash "\\bfj" "𝐣" julia-latexsubs)
+(puthash "\\bfk" "𝐤" julia-latexsubs)
+(puthash "\\bfl" "𝐥" julia-latexsubs)
+(puthash "\\bfm" "𝐦" julia-latexsubs)
+(puthash "\\bfn" "𝐧" julia-latexsubs)
+(puthash "\\bfo" "𝐨" julia-latexsubs)
+(puthash "\\bfp" "𝐩" julia-latexsubs)
+(puthash "\\bfq" "𝐪" julia-latexsubs)
+(puthash "\\bfr" "𝐫" julia-latexsubs)
+(puthash "\\bfs" "𝐬" julia-latexsubs)
+(puthash "\\bft" "𝐭" julia-latexsubs)
+(puthash "\\bfu" "𝐮" julia-latexsubs)
+(puthash "\\bfv" "𝐯" julia-latexsubs)
+(puthash "\\bfw" "𝐰" julia-latexsubs)
+(puthash "\\bfx" "𝐱" julia-latexsubs)
+(puthash "\\bfy" "𝐲" julia-latexsubs)
+(puthash "\\bfz" "𝐳" julia-latexsubs)
+(puthash "\\itA" "𝐴" julia-latexsubs)
+(puthash "\\itB" "𝐵" julia-latexsubs)
+(puthash "\\itC" "𝐶" julia-latexsubs)
+(puthash "\\itD" "𝐷" julia-latexsubs)
+(puthash "\\itE" "𝐸" julia-latexsubs)
+(puthash "\\itF" "𝐹" julia-latexsubs)
+(puthash "\\itG" "𝐺" julia-latexsubs)
+(puthash "\\itH" "𝐻" julia-latexsubs)
+(puthash "\\itI" "𝐼" julia-latexsubs)
+(puthash "\\itJ" "𝐽" julia-latexsubs)
+(puthash "\\itK" "𝐾" julia-latexsubs)
+(puthash "\\itL" "𝐿" julia-latexsubs)
+(puthash "\\itM" "𝑀" julia-latexsubs)
+(puthash "\\itN" "𝑁" julia-latexsubs)
+(puthash "\\itO" "𝑂" julia-latexsubs)
+(puthash "\\itP" "𝑃" julia-latexsubs)
+(puthash "\\itQ" "𝑄" julia-latexsubs)
+(puthash "\\itR" "𝑅" julia-latexsubs)
+(puthash "\\itS" "𝑆" julia-latexsubs)
+(puthash "\\itT" "𝑇" julia-latexsubs)
+(puthash "\\itU" "𝑈" julia-latexsubs)
+(puthash "\\itV" "𝑉" julia-latexsubs)
+(puthash "\\itW" "𝑊" julia-latexsubs)
+(puthash "\\itX" "𝑋" julia-latexsubs)
+(puthash "\\itY" "𝑌" julia-latexsubs)
+(puthash "\\itZ" "𝑍" julia-latexsubs)
+(puthash "\\ita" "𝑎" julia-latexsubs)
+(puthash "\\itb" "𝑏" julia-latexsubs)
+(puthash "\\itc" "𝑐" julia-latexsubs)
+(puthash "\\itd" "𝑑" julia-latexsubs)
+(puthash "\\ite" "𝑒" julia-latexsubs)
+(puthash "\\itf" "𝑓" julia-latexsubs)
+(puthash "\\itg" "𝑔" julia-latexsubs)
+(puthash "\\iti" "𝑖" julia-latexsubs)
+(puthash "\\itj" "𝑗" julia-latexsubs)
+(puthash "\\itk" "𝑘" julia-latexsubs)
+(puthash "\\itl" "𝑙" julia-latexsubs)
+(puthash "\\itm" "𝑚" julia-latexsubs)
+(puthash "\\itn" "𝑛" julia-latexsubs)
+(puthash "\\ito" "𝑜" julia-latexsubs)
+(puthash "\\itp" "𝑝" julia-latexsubs)
+(puthash "\\itq" "𝑞" julia-latexsubs)
+(puthash "\\itr" "𝑟" julia-latexsubs)
+(puthash "\\its" "𝑠" julia-latexsubs)
+(puthash "\\itt" "𝑡" julia-latexsubs)
+(puthash "\\itu" "𝑢" julia-latexsubs)
+(puthash "\\itv" "𝑣" julia-latexsubs)
+(puthash "\\itw" "𝑤" julia-latexsubs)
+(puthash "\\itx" "𝑥" julia-latexsubs)
+(puthash "\\ity" "𝑦" julia-latexsubs)
+(puthash "\\itz" "𝑧" julia-latexsubs)
+(puthash "\\biA" "𝑨" julia-latexsubs)
+(puthash "\\biB" "𝑩" julia-latexsubs)
+(puthash "\\biC" "𝑪" julia-latexsubs)
+(puthash "\\biD" "𝑫" julia-latexsubs)
+(puthash "\\biE" "𝑬" julia-latexsubs)
+(puthash "\\biF" "𝑭" julia-latexsubs)
+(puthash "\\biG" "𝑮" julia-latexsubs)
+(puthash "\\biH" "𝑯" julia-latexsubs)
+(puthash "\\biI" "𝑰" julia-latexsubs)
+(puthash "\\biJ" "𝑱" julia-latexsubs)
+(puthash "\\biK" "𝑲" julia-latexsubs)
+(puthash "\\biL" "𝑳" julia-latexsubs)
+(puthash "\\biM" "𝑴" julia-latexsubs)
+(puthash "\\biN" "𝑵" julia-latexsubs)
+(puthash "\\biO" "𝑶" julia-latexsubs)
+(puthash "\\biP" "𝑷" julia-latexsubs)
+(puthash "\\biQ" "𝑸" julia-latexsubs)
+(puthash "\\biR" "𝑹" julia-latexsubs)
+(puthash "\\biS" "𝑺" julia-latexsubs)
+(puthash "\\biT" "𝑻" julia-latexsubs)
+(puthash "\\biU" "𝑼" julia-latexsubs)
+(puthash "\\biV" "𝑽" julia-latexsubs)
+(puthash "\\biW" "𝑾" julia-latexsubs)
+(puthash "\\biX" "𝑿" julia-latexsubs)
+(puthash "\\biY" "𝒀" julia-latexsubs)
+(puthash "\\biZ" "𝒁" julia-latexsubs)
+(puthash "\\bia" "𝒂" julia-latexsubs)
+(puthash "\\bib" "𝒃" julia-latexsubs)
+(puthash "\\bic" "𝒄" julia-latexsubs)
+(puthash "\\bid" "𝒅" julia-latexsubs)
+(puthash "\\bie" "𝒆" julia-latexsubs)
+(puthash "\\bif" "𝒇" julia-latexsubs)
+(puthash "\\big" "𝒈" julia-latexsubs)
+(puthash "\\bih" "𝒉" julia-latexsubs)
+(puthash "\\bii" "𝒊" julia-latexsubs)
+(puthash "\\bij" "𝒋" julia-latexsubs)
+(puthash "\\bik" "𝒌" julia-latexsubs)
+(puthash "\\bil" "𝒍" julia-latexsubs)
+(puthash "\\bim" "𝒎" julia-latexsubs)
+(puthash "\\bin" "𝒏" julia-latexsubs)
+(puthash "\\bio" "𝒐" julia-latexsubs)
+(puthash "\\bip" "𝒑" julia-latexsubs)
+(puthash "\\biq" "𝒒" julia-latexsubs)
+(puthash "\\bir" "𝒓" julia-latexsubs)
+(puthash "\\bis" "𝒔" julia-latexsubs)
+(puthash "\\bit" "𝒕" julia-latexsubs)
+(puthash "\\biu" "𝒖" julia-latexsubs)
+(puthash "\\biv" "𝒗" julia-latexsubs)
+(puthash "\\biw" "𝒘" julia-latexsubs)
+(puthash "\\bix" "𝒙" julia-latexsubs)
+(puthash "\\biy" "𝒚" julia-latexsubs)
+(puthash "\\biz" "𝒛" julia-latexsubs)
+(puthash "\\scrA" "𝒜" julia-latexsubs)
+(puthash "\\scrC" "𝒞" julia-latexsubs)
+(puthash "\\scrD" "𝒟" julia-latexsubs)
+(puthash "\\scrG" "𝒢" julia-latexsubs)
+(puthash "\\scrJ" "𝒥" julia-latexsubs)
+(puthash "\\scrK" "𝒦" julia-latexsubs)
+(puthash "\\scrN" "𝒩" julia-latexsubs)
+(puthash "\\scrO" "𝒪" julia-latexsubs)
+(puthash "\\scrP" "𝒫" julia-latexsubs)
+(puthash "\\scrQ" "𝒬" julia-latexsubs)
+(puthash "\\scrS" "𝒮" julia-latexsubs)
+(puthash "\\scrT" "𝒯" julia-latexsubs)
+(puthash "\\scrU" "𝒰" julia-latexsubs)
+(puthash "\\scrV" "𝒱" julia-latexsubs)
+(puthash "\\scrW" "𝒲" julia-latexsubs)
+(puthash "\\scrX" "𝒳" julia-latexsubs)
+(puthash "\\scrY" "𝒴" julia-latexsubs)
+(puthash "\\scrZ" "𝒵" julia-latexsubs)
+(puthash "\\scra" "𝒶" julia-latexsubs)
+(puthash "\\scrb" "𝒷" julia-latexsubs)
+(puthash "\\scrc" "𝒸" julia-latexsubs)
+(puthash "\\scrd" "𝒹" julia-latexsubs)
+(puthash "\\scrf" "𝒻" julia-latexsubs)
+(puthash "\\scrh" "𝒽" julia-latexsubs)
+(puthash "\\scri" "𝒾" julia-latexsubs)
+(puthash "\\scrj" "𝒿" julia-latexsubs)
+(puthash "\\scrk" "𝓀" julia-latexsubs)
+(puthash "\\scrl" "𝓁" julia-latexsubs)
+(puthash "\\scrm" "𝓂" julia-latexsubs)
+(puthash "\\scrn" "𝓃" julia-latexsubs)
+(puthash "\\scrp" "𝓅" julia-latexsubs)
+(puthash "\\scrq" "𝓆" julia-latexsubs)
+(puthash "\\scrr" "𝓇" julia-latexsubs)
+(puthash "\\scrs" "𝓈" julia-latexsubs)
+(puthash "\\scrt" "𝓉" julia-latexsubs)
+(puthash "\\scru" "𝓊" julia-latexsubs)
+(puthash "\\scrv" "𝓋" julia-latexsubs)
+(puthash "\\scrw" "𝓌" julia-latexsubs)
+(puthash "\\scrx" "𝓍" julia-latexsubs)
+(puthash "\\scry" "𝓎" julia-latexsubs)
+(puthash "\\scrz" "𝓏" julia-latexsubs)
+(puthash "\\bscrA" "𝓐" julia-latexsubs)
+(puthash "\\bscrB" "𝓑" julia-latexsubs)
+(puthash "\\bscrC" "𝓒" julia-latexsubs)
+(puthash "\\bscrD" "𝓓" julia-latexsubs)
+(puthash "\\bscrE" "𝓔" julia-latexsubs)
+(puthash "\\bscrF" "𝓕" julia-latexsubs)
+(puthash "\\bscrG" "𝓖" julia-latexsubs)
+(puthash "\\bscrH" "𝓗" julia-latexsubs)
+(puthash "\\bscrI" "𝓘" julia-latexsubs)
+(puthash "\\bscrJ" "𝓙" julia-latexsubs)
+(puthash "\\bscrK" "𝓚" julia-latexsubs)
+(puthash "\\bscrL" "𝓛" julia-latexsubs)
+(puthash "\\bscrM" "𝓜" julia-latexsubs)
+(puthash "\\bscrN" "𝓝" julia-latexsubs)
+(puthash "\\bscrO" "𝓞" julia-latexsubs)
+(puthash "\\bscrP" "𝓟" julia-latexsubs)
+(puthash "\\bscrQ" "𝓠" julia-latexsubs)
+(puthash "\\bscrR" "𝓡" julia-latexsubs)
+(puthash "\\bscrS" "𝓢" julia-latexsubs)
+(puthash "\\bscrT" "𝓣" julia-latexsubs)
+(puthash "\\bscrU" "𝓤" julia-latexsubs)
+(puthash "\\bscrV" "𝓥" julia-latexsubs)
+(puthash "\\bscrW" "𝓦" julia-latexsubs)
+(puthash "\\bscrX" "𝓧" julia-latexsubs)
+(puthash "\\bscrY" "𝓨" julia-latexsubs)
+(puthash "\\bscrZ" "𝓩" julia-latexsubs)
+(puthash "\\bscra" "𝓪" julia-latexsubs)
+(puthash "\\bscrb" "𝓫" julia-latexsubs)
+(puthash "\\bscrc" "𝓬" julia-latexsubs)
+(puthash "\\bscrd" "𝓭" julia-latexsubs)
+(puthash "\\bscre" "𝓮" julia-latexsubs)
+(puthash "\\bscrf" "𝓯" julia-latexsubs)
+(puthash "\\bscrg" "𝓰" julia-latexsubs)
+(puthash "\\bscrh" "𝓱" julia-latexsubs)
+(puthash "\\bscri" "𝓲" julia-latexsubs)
+(puthash "\\bscrj" "𝓳" julia-latexsubs)
+(puthash "\\bscrk" "𝓴" julia-latexsubs)
+(puthash "\\bscrl" "𝓵" julia-latexsubs)
+(puthash "\\bscrm" "𝓶" julia-latexsubs)
+(puthash "\\bscrn" "𝓷" julia-latexsubs)
+(puthash "\\bscro" "𝓸" julia-latexsubs)
+(puthash "\\bscrp" "𝓹" julia-latexsubs)
+(puthash "\\bscrq" "𝓺" julia-latexsubs)
+(puthash "\\bscrr" "𝓻" julia-latexsubs)
+(puthash "\\bscrs" "𝓼" julia-latexsubs)
+(puthash "\\bscrt" "𝓽" julia-latexsubs)
+(puthash "\\bscru" "𝓾" julia-latexsubs)
+(puthash "\\bscrv" "𝓿" julia-latexsubs)
+(puthash "\\bscrw" "𝔀" julia-latexsubs)
+(puthash "\\bscrx" "𝔁" julia-latexsubs)
+(puthash "\\bscry" "𝔂" julia-latexsubs)
+(puthash "\\bscrz" "𝔃" julia-latexsubs)
+(puthash "\\frakA" "𝔄" julia-latexsubs)
+(puthash "\\frakB" "𝔅" julia-latexsubs)
+(puthash "\\frakD" "𝔇" julia-latexsubs)
+(puthash "\\frakE" "𝔈" julia-latexsubs)
+(puthash "\\frakF" "𝔉" julia-latexsubs)
+(puthash "\\frakG" "𝔊" julia-latexsubs)
+(puthash "\\frakJ" "𝔍" julia-latexsubs)
+(puthash "\\frakK" "𝔎" julia-latexsubs)
+(puthash "\\frakL" "𝔏" julia-latexsubs)
+(puthash "\\frakM" "𝔐" julia-latexsubs)
+(puthash "\\frakN" "𝔑" julia-latexsubs)
+(puthash "\\frakO" "𝔒" julia-latexsubs)
+(puthash "\\frakP" "𝔓" julia-latexsubs)
+(puthash "\\frakQ" "𝔔" julia-latexsubs)
+(puthash "\\frakS" "𝔖" julia-latexsubs)
+(puthash "\\frakT" "𝔗" julia-latexsubs)
+(puthash "\\frakU" "𝔘" julia-latexsubs)
+(puthash "\\frakV" "𝔙" julia-latexsubs)
+(puthash "\\frakW" "𝔚" julia-latexsubs)
+(puthash "\\frakX" "𝔛" julia-latexsubs)
+(puthash "\\frakY" "𝔜" julia-latexsubs)
+(puthash "\\fraka" "𝔞" julia-latexsubs)
+(puthash "\\frakb" "𝔟" julia-latexsubs)
+(puthash "\\frakc" "𝔠" julia-latexsubs)
+(puthash "\\frakd" "𝔡" julia-latexsubs)
+(puthash "\\frake" "𝔢" julia-latexsubs)
+(puthash "\\frakf" "𝔣" julia-latexsubs)
+(puthash "\\frakg" "𝔤" julia-latexsubs)
+(puthash "\\frakh" "𝔥" julia-latexsubs)
+(puthash "\\fraki" "𝔦" julia-latexsubs)
+(puthash "\\frakj" "𝔧" julia-latexsubs)
+(puthash "\\frakk" "𝔨" julia-latexsubs)
+(puthash "\\frakl" "𝔩" julia-latexsubs)
+(puthash "\\frakm" "𝔪" julia-latexsubs)
+(puthash "\\frakn" "𝔫" julia-latexsubs)
+(puthash "\\frako" "𝔬" julia-latexsubs)
+(puthash "\\frakp" "𝔭" julia-latexsubs)
+(puthash "\\frakq" "𝔮" julia-latexsubs)
+(puthash "\\frakr" "𝔯" julia-latexsubs)
+(puthash "\\fraks" "𝔰" julia-latexsubs)
+(puthash "\\frakt" "𝔱" julia-latexsubs)
+(puthash "\\fraku" "𝔲" julia-latexsubs)
+(puthash "\\frakv" "𝔳" julia-latexsubs)
+(puthash "\\frakw" "𝔴" julia-latexsubs)
+(puthash "\\frakx" "𝔵" julia-latexsubs)
+(puthash "\\fraky" "𝔶" julia-latexsubs)
+(puthash "\\frakz" "𝔷" julia-latexsubs)
+(puthash "\\bbA" "𝔸" julia-latexsubs)
+(puthash "\\bbB" "𝔹" julia-latexsubs)
+(puthash "\\bbD" "𝔻" julia-latexsubs)
+(puthash "\\bbE" "𝔼" julia-latexsubs)
+(puthash "\\bbF" "𝔽" julia-latexsubs)
+(puthash "\\bbG" "𝔾" julia-latexsubs)
+(puthash "\\bbI" "𝕀" julia-latexsubs)
+(puthash "\\bbJ" "𝕁" julia-latexsubs)
+(puthash "\\bbK" "𝕂" julia-latexsubs)
+(puthash "\\bbL" "𝕃" julia-latexsubs)
+(puthash "\\bbM" "𝕄" julia-latexsubs)
+(puthash "\\bbO" "𝕆" julia-latexsubs)
+(puthash "\\bbS" "𝕊" julia-latexsubs)
+(puthash "\\bbT" "𝕋" julia-latexsubs)
+(puthash "\\bbU" "𝕌" julia-latexsubs)
+(puthash "\\bbV" "𝕍" julia-latexsubs)
+(puthash "\\bbW" "𝕎" julia-latexsubs)
+(puthash "\\bbX" "𝕏" julia-latexsubs)
+(puthash "\\bbY" "𝕐" julia-latexsubs)
+(puthash "\\bba" "𝕒" julia-latexsubs)
+(puthash "\\bbb" "𝕓" julia-latexsubs)
+(puthash "\\bbc" "𝕔" julia-latexsubs)
+(puthash "\\bbd" "𝕕" julia-latexsubs)
+(puthash "\\bbe" "𝕖" julia-latexsubs)
+(puthash "\\bbf" "𝕗" julia-latexsubs)
+(puthash "\\bbg" "𝕘" julia-latexsubs)
+(puthash "\\bbh" "𝕙" julia-latexsubs)
+(puthash "\\bbi" "𝕚" julia-latexsubs)
+(puthash "\\bbj" "𝕛" julia-latexsubs)
+(puthash "\\bbk" "𝕜" julia-latexsubs)
+(puthash "\\bbl" "𝕝" julia-latexsubs)
+(puthash "\\bbm" "𝕞" julia-latexsubs)
+(puthash "\\bbn" "𝕟" julia-latexsubs)
+(puthash "\\bbo" "𝕠" julia-latexsubs)
+(puthash "\\bbp" "𝕡" julia-latexsubs)
+(puthash "\\bbq" "𝕢" julia-latexsubs)
+(puthash "\\bbr" "𝕣" julia-latexsubs)
+(puthash "\\bbs" "𝕤" julia-latexsubs)
+(puthash "\\bbt" "𝕥" julia-latexsubs)
+(puthash "\\bbu" "𝕦" julia-latexsubs)
+(puthash "\\bbv" "𝕧" julia-latexsubs)
+(puthash "\\bbw" "𝕨" julia-latexsubs)
+(puthash "\\bbx" "𝕩" julia-latexsubs)
+(puthash "\\bby" "𝕪" julia-latexsubs)
+(puthash "\\bbz" "𝕫" julia-latexsubs)
+(puthash "\\bfrakA" "𝕬" julia-latexsubs)
+(puthash "\\bfrakB" "𝕭" julia-latexsubs)
+(puthash "\\bfrakC" "𝕮" julia-latexsubs)
+(puthash "\\bfrakD" "𝕯" julia-latexsubs)
+(puthash "\\bfrakE" "𝕰" julia-latexsubs)
+(puthash "\\bfrakF" "𝕱" julia-latexsubs)
+(puthash "\\bfrakG" "𝕲" julia-latexsubs)
+(puthash "\\bfrakH" "𝕳" julia-latexsubs)
+(puthash "\\bfrakI" "𝕴" julia-latexsubs)
+(puthash "\\bfrakJ" "𝕵" julia-latexsubs)
+(puthash "\\bfrakK" "𝕶" julia-latexsubs)
+(puthash "\\bfrakL" "𝕷" julia-latexsubs)
+(puthash "\\bfrakM" "𝕸" julia-latexsubs)
+(puthash "\\bfrakN" "𝕹" julia-latexsubs)
+(puthash "\\bfrakO" "𝕺" julia-latexsubs)
+(puthash "\\bfrakP" "𝕻" julia-latexsubs)
+(puthash "\\bfrakQ" "𝕼" julia-latexsubs)
+(puthash "\\bfrakR" "𝕽" julia-latexsubs)
+(puthash "\\bfrakS" "𝕾" julia-latexsubs)
+(puthash "\\bfrakT" "𝕿" julia-latexsubs)
+(puthash "\\bfrakU" "𝖀" julia-latexsubs)
+(puthash "\\bfrakV" "𝖁" julia-latexsubs)
+(puthash "\\bfrakW" "𝖂" julia-latexsubs)
+(puthash "\\bfrakX" "𝖃" julia-latexsubs)
+(puthash "\\bfrakY" "𝖄" julia-latexsubs)
+(puthash "\\bfrakZ" "𝖅" julia-latexsubs)
+(puthash "\\bfraka" "𝖆" julia-latexsubs)
+(puthash "\\bfrakb" "𝖇" julia-latexsubs)
+(puthash "\\bfrakc" "𝖈" julia-latexsubs)
+(puthash "\\bfrakd" "𝖉" julia-latexsubs)
+(puthash "\\bfrake" "𝖊" julia-latexsubs)
+(puthash "\\bfrakf" "𝖋" julia-latexsubs)
+(puthash "\\bfrakg" "𝖌" julia-latexsubs)
+(puthash "\\bfrakh" "𝖍" julia-latexsubs)
+(puthash "\\bfraki" "𝖎" julia-latexsubs)
+(puthash "\\bfrakj" "𝖏" julia-latexsubs)
+(puthash "\\bfrakk" "𝖐" julia-latexsubs)
+(puthash "\\bfrakl" "𝖑" julia-latexsubs)
+(puthash "\\bfrakm" "𝖒" julia-latexsubs)
+(puthash "\\bfrakn" "𝖓" julia-latexsubs)
+(puthash "\\bfrako" "𝖔" julia-latexsubs)
+(puthash "\\bfrakp" "𝖕" julia-latexsubs)
+(puthash "\\bfrakq" "𝖖" julia-latexsubs)
+(puthash "\\bfrakr" "𝖗" julia-latexsubs)
+(puthash "\\bfraks" "𝖘" julia-latexsubs)
+(puthash "\\bfrakt" "𝖙" julia-latexsubs)
+(puthash "\\bfraku" "𝖚" julia-latexsubs)
+(puthash "\\bfrakv" "𝖛" julia-latexsubs)
+(puthash "\\bfrakw" "𝖜" julia-latexsubs)
+(puthash "\\bfrakx" "𝖝" julia-latexsubs)
+(puthash "\\bfraky" "𝖞" julia-latexsubs)
+(puthash "\\bfrakz" "𝖟" julia-latexsubs)
+(puthash "\\sansA" "𝖠" julia-latexsubs)
+(puthash "\\sansB" "𝖡" julia-latexsubs)
+(puthash "\\sansC" "𝖢" julia-latexsubs)
+(puthash "\\sansD" "𝖣" julia-latexsubs)
+(puthash "\\sansE" "𝖤" julia-latexsubs)
+(puthash "\\sansF" "𝖥" julia-latexsubs)
+(puthash "\\sansG" "𝖦" julia-latexsubs)
+(puthash "\\sansH" "𝖧" julia-latexsubs)
+(puthash "\\sansI" "𝖨" julia-latexsubs)
+(puthash "\\sansJ" "𝖩" julia-latexsubs)
+(puthash "\\sansK" "𝖪" julia-latexsubs)
+(puthash "\\sansL" "𝖫" julia-latexsubs)
+(puthash "\\sansM" "𝖬" julia-latexsubs)
+(puthash "\\sansN" "𝖭" julia-latexsubs)
+(puthash "\\sansO" "𝖮" julia-latexsubs)
+(puthash "\\sansP" "𝖯" julia-latexsubs)
+(puthash "\\sansQ" "𝖰" julia-latexsubs)
+(puthash "\\sansR" "𝖱" julia-latexsubs)
+(puthash "\\sansS" "𝖲" julia-latexsubs)
+(puthash "\\sansT" "𝖳" julia-latexsubs)
+(puthash "\\sansU" "𝖴" julia-latexsubs)
+(puthash "\\sansV" "𝖵" julia-latexsubs)
+(puthash "\\sansW" "𝖶" julia-latexsubs)
+(puthash "\\sansX" "𝖷" julia-latexsubs)
+(puthash "\\sansY" "𝖸" julia-latexsubs)
+(puthash "\\sansZ" "𝖹" julia-latexsubs)
+(puthash "\\sansa" "𝖺" julia-latexsubs)
+(puthash "\\sansb" "𝖻" julia-latexsubs)
+(puthash "\\sansc" "𝖼" julia-latexsubs)
+(puthash "\\sansd" "𝖽" julia-latexsubs)
+(puthash "\\sanse" "𝖾" julia-latexsubs)
+(puthash "\\sansf" "𝖿" julia-latexsubs)
+(puthash "\\sansg" "𝗀" julia-latexsubs)
+(puthash "\\sansh" "𝗁" julia-latexsubs)
+(puthash "\\sansi" "𝗂" julia-latexsubs)
+(puthash "\\sansj" "𝗃" julia-latexsubs)
+(puthash "\\sansk" "𝗄" julia-latexsubs)
+(puthash "\\sansl" "𝗅" julia-latexsubs)
+(puthash "\\sansm" "𝗆" julia-latexsubs)
+(puthash "\\sansn" "𝗇" julia-latexsubs)
+(puthash "\\sanso" "𝗈" julia-latexsubs)
+(puthash "\\sansp" "𝗉" julia-latexsubs)
+(puthash "\\sansq" "𝗊" julia-latexsubs)
+(puthash "\\sansr" "𝗋" julia-latexsubs)
+(puthash "\\sanss" "𝗌" julia-latexsubs)
+(puthash "\\sanst" "𝗍" julia-latexsubs)
+(puthash "\\sansu" "𝗎" julia-latexsubs)
+(puthash "\\sansv" "𝗏" julia-latexsubs)
+(puthash "\\sansw" "𝗐" julia-latexsubs)
+(puthash "\\sansx" "𝗑" julia-latexsubs)
+(puthash "\\sansy" "𝗒" julia-latexsubs)
+(puthash "\\sansz" "𝗓" julia-latexsubs)
+(puthash "\\bsansA" "𝗔" julia-latexsubs)
+(puthash "\\bsansB" "𝗕" julia-latexsubs)
+(puthash "\\bsansC" "𝗖" julia-latexsubs)
+(puthash "\\bsansD" "𝗗" julia-latexsubs)
+(puthash "\\bsansE" "𝗘" julia-latexsubs)
+(puthash "\\bsansF" "𝗙" julia-latexsubs)
+(puthash "\\bsansG" "𝗚" julia-latexsubs)
+(puthash "\\bsansH" "𝗛" julia-latexsubs)
+(puthash "\\bsansI" "𝗜" julia-latexsubs)
+(puthash "\\bsansJ" "𝗝" julia-latexsubs)
+(puthash "\\bsansK" "𝗞" julia-latexsubs)
+(puthash "\\bsansL" "𝗟" julia-latexsubs)
+(puthash "\\bsansM" "𝗠" julia-latexsubs)
+(puthash "\\bsansN" "𝗡" julia-latexsubs)
+(puthash "\\bsansO" "𝗢" julia-latexsubs)
+(puthash "\\bsansP" "𝗣" julia-latexsubs)
+(puthash "\\bsansQ" "𝗤" julia-latexsubs)
+(puthash "\\bsansR" "𝗥" julia-latexsubs)
+(puthash "\\bsansS" "𝗦" julia-latexsubs)
+(puthash "\\bsansT" "𝗧" julia-latexsubs)
+(puthash "\\bsansU" "𝗨" julia-latexsubs)
+(puthash "\\bsansV" "𝗩" julia-latexsubs)
+(puthash "\\bsansW" "𝗪" julia-latexsubs)
+(puthash "\\bsansX" "𝗫" julia-latexsubs)
+(puthash "\\bsansY" "𝗬" julia-latexsubs)
+(puthash "\\bsansZ" "𝗭" julia-latexsubs)
+(puthash "\\bsansa" "𝗮" julia-latexsubs)
+(puthash "\\bsansb" "𝗯" julia-latexsubs)
+(puthash "\\bsansc" "𝗰" julia-latexsubs)
+(puthash "\\bsansd" "𝗱" julia-latexsubs)
+(puthash "\\bsanse" "𝗲" julia-latexsubs)
+(puthash "\\bsansf" "𝗳" julia-latexsubs)
+(puthash "\\bsansg" "𝗴" julia-latexsubs)
+(puthash "\\bsansh" "𝗵" julia-latexsubs)
+(puthash "\\bsansi" "𝗶" julia-latexsubs)
+(puthash "\\bsansj" "𝗷" julia-latexsubs)
+(puthash "\\bsansk" "𝗸" julia-latexsubs)
+(puthash "\\bsansl" "𝗹" julia-latexsubs)
+(puthash "\\bsansm" "𝗺" julia-latexsubs)
+(puthash "\\bsansn" "𝗻" julia-latexsubs)
+(puthash "\\bsanso" "𝗼" julia-latexsubs)
+(puthash "\\bsansp" "𝗽" julia-latexsubs)
+(puthash "\\bsansq" "𝗾" julia-latexsubs)
+(puthash "\\bsansr" "𝗿" julia-latexsubs)
+(puthash "\\bsanss" "𝘀" julia-latexsubs)
+(puthash "\\bsanst" "𝘁" julia-latexsubs)
+(puthash "\\bsansu" "𝘂" julia-latexsubs)
+(puthash "\\bsansv" "𝘃" julia-latexsubs)
+(puthash "\\bsansw" "𝘄" julia-latexsubs)
+(puthash "\\bsansx" "𝘅" julia-latexsubs)
+(puthash "\\bsansy" "𝘆" julia-latexsubs)
+(puthash "\\bsansz" "𝘇" julia-latexsubs)
+(puthash "\\isansA" "𝘈" julia-latexsubs)
+(puthash "\\isansB" "𝘉" julia-latexsubs)
+(puthash "\\isansC" "𝘊" julia-latexsubs)
+(puthash "\\isansD" "𝘋" julia-latexsubs)
+(puthash "\\isansE" "𝘌" julia-latexsubs)
+(puthash "\\isansF" "𝘍" julia-latexsubs)
+(puthash "\\isansG" "𝘎" julia-latexsubs)
+(puthash "\\isansH" "𝘏" julia-latexsubs)
+(puthash "\\isansI" "𝘐" julia-latexsubs)
+(puthash "\\isansJ" "𝘑" julia-latexsubs)
+(puthash "\\isansK" "𝘒" julia-latexsubs)
+(puthash "\\isansL" "𝘓" julia-latexsubs)
+(puthash "\\isansM" "𝘔" julia-latexsubs)
+(puthash "\\isansN" "𝘕" julia-latexsubs)
+(puthash "\\isansO" "𝘖" julia-latexsubs)
+(puthash "\\isansP" "𝘗" julia-latexsubs)
+(puthash "\\isansQ" "𝘘" julia-latexsubs)
+(puthash "\\isansR" "𝘙" julia-latexsubs)
+(puthash "\\isansS" "𝘚" julia-latexsubs)
+(puthash "\\isansT" "𝘛" julia-latexsubs)
+(puthash "\\isansU" "𝘜" julia-latexsubs)
+(puthash "\\isansV" "𝘝" julia-latexsubs)
+(puthash "\\isansW" "𝘞" julia-latexsubs)
+(puthash "\\isansX" "𝘟" julia-latexsubs)
+(puthash "\\isansY" "𝘠" julia-latexsubs)
+(puthash "\\isansZ" "𝘡" julia-latexsubs)
+(puthash "\\isansa" "𝘢" julia-latexsubs)
+(puthash "\\isansb" "𝘣" julia-latexsubs)
+(puthash "\\isansc" "𝘤" julia-latexsubs)
+(puthash "\\isansd" "𝘥" julia-latexsubs)
+(puthash "\\isanse" "𝘦" julia-latexsubs)
+(puthash "\\isansf" "𝘧" julia-latexsubs)
+(puthash "\\isansg" "𝘨" julia-latexsubs)
+(puthash "\\isansh" "𝘩" julia-latexsubs)
+(puthash "\\isansi" "𝘪" julia-latexsubs)
+(puthash "\\isansj" "𝘫" julia-latexsubs)
+(puthash "\\isansk" "𝘬" julia-latexsubs)
+(puthash "\\isansl" "𝘭" julia-latexsubs)
+(puthash "\\isansm" "𝘮" julia-latexsubs)
+(puthash "\\isansn" "𝘯" julia-latexsubs)
+(puthash "\\isanso" "𝘰" julia-latexsubs)
+(puthash "\\isansp" "𝘱" julia-latexsubs)
+(puthash "\\isansq" "𝘲" julia-latexsubs)
+(puthash "\\isansr" "𝘳" julia-latexsubs)
+(puthash "\\isanss" "𝘴" julia-latexsubs)
+(puthash "\\isanst" "𝘵" julia-latexsubs)
+(puthash "\\isansu" "𝘶" julia-latexsubs)
+(puthash "\\isansv" "𝘷" julia-latexsubs)
+(puthash "\\isansw" "𝘸" julia-latexsubs)
+(puthash "\\isansx" "𝘹" julia-latexsubs)
+(puthash "\\isansy" "𝘺" julia-latexsubs)
+(puthash "\\isansz" "𝘻" julia-latexsubs)
+(puthash "\\bisansA" "𝘼" julia-latexsubs)
+(puthash "\\bisansB" "𝘽" julia-latexsubs)
+(puthash "\\bisansC" "𝘾" julia-latexsubs)
+(puthash "\\bisansD" "𝘿" julia-latexsubs)
+(puthash "\\bisansE" "𝙀" julia-latexsubs)
+(puthash "\\bisansF" "𝙁" julia-latexsubs)
+(puthash "\\bisansG" "𝙂" julia-latexsubs)
+(puthash "\\bisansH" "𝙃" julia-latexsubs)
+(puthash "\\bisansI" "𝙄" julia-latexsubs)
+(puthash "\\bisansJ" "𝙅" julia-latexsubs)
+(puthash "\\bisansK" "𝙆" julia-latexsubs)
+(puthash "\\bisansL" "𝙇" julia-latexsubs)
+(puthash "\\bisansM" "𝙈" julia-latexsubs)
+(puthash "\\bisansN" "𝙉" julia-latexsubs)
+(puthash "\\bisansO" "𝙊" julia-latexsubs)
+(puthash "\\bisansP" "𝙋" julia-latexsubs)
+(puthash "\\bisansQ" "𝙌" julia-latexsubs)
+(puthash "\\bisansR" "𝙍" julia-latexsubs)
+(puthash "\\bisansS" "𝙎" julia-latexsubs)
+(puthash "\\bisansT" "𝙏" julia-latexsubs)
+(puthash "\\bisansU" "𝙐" julia-latexsubs)
+(puthash "\\bisansV" "𝙑" julia-latexsubs)
+(puthash "\\bisansW" "𝙒" julia-latexsubs)
+(puthash "\\bisansX" "𝙓" julia-latexsubs)
+(puthash "\\bisansY" "𝙔" julia-latexsubs)
+(puthash "\\bisansZ" "𝙕" julia-latexsubs)
+(puthash "\\bisansa" "𝙖" julia-latexsubs)
+(puthash "\\bisansb" "𝙗" julia-latexsubs)
+(puthash "\\bisansc" "𝙘" julia-latexsubs)
+(puthash "\\bisansd" "𝙙" julia-latexsubs)
+(puthash "\\bisanse" "𝙚" julia-latexsubs)
+(puthash "\\bisansf" "𝙛" julia-latexsubs)
+(puthash "\\bisansg" "𝙜" julia-latexsubs)
+(puthash "\\bisansh" "𝙝" julia-latexsubs)
+(puthash "\\bisansi" "𝙞" julia-latexsubs)
+(puthash "\\bisansj" "𝙟" julia-latexsubs)
+(puthash "\\bisansk" "𝙠" julia-latexsubs)
+(puthash "\\bisansl" "𝙡" julia-latexsubs)
+(puthash "\\bisansm" "𝙢" julia-latexsubs)
+(puthash "\\bisansn" "𝙣" julia-latexsubs)
+(puthash "\\bisanso" "𝙤" julia-latexsubs)
+(puthash "\\bisansp" "𝙥" julia-latexsubs)
+(puthash "\\bisansq" "𝙦" julia-latexsubs)
+(puthash "\\bisansr" "𝙧" julia-latexsubs)
+(puthash "\\bisanss" "𝙨" julia-latexsubs)
+(puthash "\\bisanst" "𝙩" julia-latexsubs)
+(puthash "\\bisansu" "𝙪" julia-latexsubs)
+(puthash "\\bisansv" "𝙫" julia-latexsubs)
+(puthash "\\bisansw" "𝙬" julia-latexsubs)
+(puthash "\\bisansx" "𝙭" julia-latexsubs)
+(puthash "\\bisansy" "𝙮" julia-latexsubs)
+(puthash "\\bisansz" "𝙯" julia-latexsubs)
+(puthash "\\ttA" "𝙰" julia-latexsubs)
+(puthash "\\ttB" "𝙱" julia-latexsubs)
+(puthash "\\ttC" "𝙲" julia-latexsubs)
+(puthash "\\ttD" "𝙳" julia-latexsubs)
+(puthash "\\ttE" "𝙴" julia-latexsubs)
+(puthash "\\ttF" "𝙵" julia-latexsubs)
+(puthash "\\ttG" "𝙶" julia-latexsubs)
+(puthash "\\ttH" "𝙷" julia-latexsubs)
+(puthash "\\ttI" "𝙸" julia-latexsubs)
+(puthash "\\ttJ" "𝙹" julia-latexsubs)
+(puthash "\\ttK" "𝙺" julia-latexsubs)
+(puthash "\\ttL" "𝙻" julia-latexsubs)
+(puthash "\\ttM" "𝙼" julia-latexsubs)
+(puthash "\\ttN" "𝙽" julia-latexsubs)
+(puthash "\\ttO" "𝙾" julia-latexsubs)
+(puthash "\\ttP" "𝙿" julia-latexsubs)
+(puthash "\\ttQ" "𝚀" julia-latexsubs)
+(puthash "\\ttR" "𝚁" julia-latexsubs)
+(puthash "\\ttS" "𝚂" julia-latexsubs)
+(puthash "\\ttT" "𝚃" julia-latexsubs)
+(puthash "\\ttU" "𝚄" julia-latexsubs)
+(puthash "\\ttV" "𝚅" julia-latexsubs)
+(puthash "\\ttW" "𝚆" julia-latexsubs)
+(puthash "\\ttX" "𝚇" julia-latexsubs)
+(puthash "\\ttY" "𝚈" julia-latexsubs)
+(puthash "\\ttZ" "𝚉" julia-latexsubs)
+(puthash "\\tta" "𝚊" julia-latexsubs)
+(puthash "\\ttb" "𝚋" julia-latexsubs)
+(puthash "\\ttc" "𝚌" julia-latexsubs)
+(puthash "\\ttd" "𝚍" julia-latexsubs)
+(puthash "\\tte" "𝚎" julia-latexsubs)
+(puthash "\\ttf" "𝚏" julia-latexsubs)
+(puthash "\\ttg" "𝚐" julia-latexsubs)
+(puthash "\\tth" "𝚑" julia-latexsubs)
+(puthash "\\tti" "𝚒" julia-latexsubs)
+(puthash "\\ttj" "𝚓" julia-latexsubs)
+(puthash "\\ttk" "𝚔" julia-latexsubs)
+(puthash "\\ttl" "𝚕" julia-latexsubs)
+(puthash "\\ttm" "𝚖" julia-latexsubs)
+(puthash "\\ttn" "𝚗" julia-latexsubs)
+(puthash "\\tto" "𝚘" julia-latexsubs)
+(puthash "\\ttp" "𝚙" julia-latexsubs)
+(puthash "\\ttq" "𝚚" julia-latexsubs)
+(puthash "\\ttr" "𝚛" julia-latexsubs)
+(puthash "\\tts" "𝚜" julia-latexsubs)
+(puthash "\\ttt" "𝚝" julia-latexsubs)
+(puthash "\\ttu" "𝚞" julia-latexsubs)
+(puthash "\\ttv" "𝚟" julia-latexsubs)
+(puthash "\\ttw" "𝚠" julia-latexsubs)
+(puthash "\\ttx" "𝚡" julia-latexsubs)
+(puthash "\\tty" "𝚢" julia-latexsubs)
+(puthash "\\ttz" "𝚣" julia-latexsubs)
+(puthash "\\itimath" "𝚤" julia-latexsubs)
+(puthash "\\itjmath" "𝚥" julia-latexsubs)
+(puthash "\\bfAlpha" "𝚨" julia-latexsubs)
+(puthash "\\bfBeta" "𝚩" julia-latexsubs)
+(puthash "\\bfGamma" "𝚪" julia-latexsubs)
+(puthash "\\bfDelta" "𝚫" julia-latexsubs)
+(puthash "\\bfEpsilon" "𝚬" julia-latexsubs)
+(puthash "\\bfZeta" "𝚭" julia-latexsubs)
+(puthash "\\bfEta" "𝚮" julia-latexsubs)
+(puthash "\\bfTheta" "𝚯" julia-latexsubs)
+(puthash "\\bfIota" "𝚰" julia-latexsubs)
+(puthash "\\bfKappa" "𝚱" julia-latexsubs)
+(puthash "\\bfLambda" "𝚲" julia-latexsubs)
+(puthash "\\bfMu" "𝚳" julia-latexsubs)
+(puthash "\\bfNu" "𝚴" julia-latexsubs)
+(puthash "\\bfXi" "𝚵" julia-latexsubs)
+(puthash "\\bfOmicron" "𝚶" julia-latexsubs)
+(puthash "\\bfPi" "𝚷" julia-latexsubs)
+(puthash "\\bfRho" "𝚸" julia-latexsubs)
+(puthash "\\bfvarTheta" "𝚹" julia-latexsubs)
+(puthash "\\bfSigma" "𝚺" julia-latexsubs)
+(puthash "\\bfTau" "𝚻" julia-latexsubs)
+(puthash "\\bfUpsilon" "𝚼" julia-latexsubs)
+(puthash "\\bfPhi" "𝚽" julia-latexsubs)
+(puthash "\\bfChi" "𝚾" julia-latexsubs)
+(puthash "\\bfPsi" "𝚿" julia-latexsubs)
+(puthash "\\bfOmega" "𝛀" julia-latexsubs)
+(puthash "\\bfnabla" "𝛁" julia-latexsubs)
+(puthash "\\bfalpha" "𝛂" julia-latexsubs)
+(puthash "\\bfbeta" "𝛃" julia-latexsubs)
+(puthash "\\bfgamma" "𝛄" julia-latexsubs)
+(puthash "\\bfdelta" "𝛅" julia-latexsubs)
+(puthash "\\bfepsilon" "𝛆" julia-latexsubs)
+(puthash "\\bfzeta" "𝛇" julia-latexsubs)
+(puthash "\\bfeta" "𝛈" julia-latexsubs)
+(puthash "\\bftheta" "𝛉" julia-latexsubs)
+(puthash "\\bfiota" "𝛊" julia-latexsubs)
+(puthash "\\bfkappa" "𝛋" julia-latexsubs)
+(puthash "\\bflambda" "𝛌" julia-latexsubs)
+(puthash "\\bfmu" "𝛍" julia-latexsubs)
+(puthash "\\bfnu" "𝛎" julia-latexsubs)
+(puthash "\\bfxi" "𝛏" julia-latexsubs)
+(puthash "\\bfomicron" "𝛐" julia-latexsubs)
+(puthash "\\bfpi" "𝛑" julia-latexsubs)
+(puthash "\\bfrho" "𝛒" julia-latexsubs)
+(puthash "\\bfvarsigma" "𝛓" julia-latexsubs)
+(puthash "\\bfsigma" "𝛔" julia-latexsubs)
+(puthash "\\bftau" "𝛕" julia-latexsubs)
+(puthash "\\bfupsilon" "𝛖" julia-latexsubs)
+(puthash "\\bfvarphi" "𝛗" julia-latexsubs)
+(puthash "\\bfchi" "𝛘" julia-latexsubs)
+(puthash "\\bfpsi" "𝛙" julia-latexsubs)
+(puthash "\\bfomega" "𝛚" julia-latexsubs)
+(puthash "\\bfpartial" "𝛛" julia-latexsubs)
+(puthash "\\bfvarepsilon" "𝛜" julia-latexsubs)
+(puthash "\\bfvartheta" "𝛝" julia-latexsubs)
+(puthash "\\bfvarkappa" "𝛞" julia-latexsubs)
+(puthash "\\bfphi" "𝛟" julia-latexsubs)
+(puthash "\\bfvarrho" "𝛠" julia-latexsubs)
+(puthash "\\bfvarpi" "𝛡" julia-latexsubs)
+(puthash "\\itAlpha" "𝛢" julia-latexsubs)
+(puthash "\\itBeta" "𝛣" julia-latexsubs)
+(puthash "\\itGamma" "𝛤" julia-latexsubs)
+(puthash "\\itDelta" "𝛥" julia-latexsubs)
+(puthash "\\itEpsilon" "𝛦" julia-latexsubs)
+(puthash "\\itZeta" "𝛧" julia-latexsubs)
+(puthash "\\itEta" "𝛨" julia-latexsubs)
+(puthash "\\itTheta" "𝛩" julia-latexsubs)
+(puthash "\\itIota" "𝛪" julia-latexsubs)
+(puthash "\\itKappa" "𝛫" julia-latexsubs)
+(puthash "\\itLambda" "𝛬" julia-latexsubs)
+(puthash "\\itMu" "𝛭" julia-latexsubs)
+(puthash "\\itNu" "𝛮" julia-latexsubs)
+(puthash "\\itXi" "𝛯" julia-latexsubs)
+(puthash "\\itOmicron" "𝛰" julia-latexsubs)
+(puthash "\\itPi" "𝛱" julia-latexsubs)
+(puthash "\\itRho" "𝛲" julia-latexsubs)
+(puthash "\\itvarTheta" "𝛳" julia-latexsubs)
+(puthash "\\itSigma" "𝛴" julia-latexsubs)
+(puthash "\\itTau" "𝛵" julia-latexsubs)
+(puthash "\\itUpsilon" "𝛶" julia-latexsubs)
+(puthash "\\itPhi" "𝛷" julia-latexsubs)
+(puthash "\\itChi" "𝛸" julia-latexsubs)
+(puthash "\\itPsi" "𝛹" julia-latexsubs)
+(puthash "\\itOmega" "𝛺" julia-latexsubs)
+(puthash "\\itnabla" "𝛻" julia-latexsubs)
+(puthash "\\italpha" "𝛼" julia-latexsubs)
+(puthash "\\itbeta" "𝛽" julia-latexsubs)
+(puthash "\\itgamma" "𝛾" julia-latexsubs)
+(puthash "\\itdelta" "𝛿" julia-latexsubs)
+(puthash "\\itepsilon" "𝜀" julia-latexsubs)
+(puthash "\\itzeta" "𝜁" julia-latexsubs)
+(puthash "\\iteta" "𝜂" julia-latexsubs)
+(puthash "\\ittheta" "𝜃" julia-latexsubs)
+(puthash "\\itiota" "𝜄" julia-latexsubs)
+(puthash "\\itkappa" "𝜅" julia-latexsubs)
+(puthash "\\itlambda" "𝜆" julia-latexsubs)
+(puthash "\\itmu" "𝜇" julia-latexsubs)
+(puthash "\\itnu" "𝜈" julia-latexsubs)
+(puthash "\\itxi" "𝜉" julia-latexsubs)
+(puthash "\\itomicron" "𝜊" julia-latexsubs)
+(puthash "\\itpi" "𝜋" julia-latexsubs)
+(puthash "\\itrho" "𝜌" julia-latexsubs)
+(puthash "\\itvarsigma" "𝜍" julia-latexsubs)
+(puthash "\\itsigma" "𝜎" julia-latexsubs)
+(puthash "\\ittau" "𝜏" julia-latexsubs)
+(puthash "\\itupsilon" "𝜐" julia-latexsubs)
+(puthash "\\itphi" "𝜑" julia-latexsubs)
+(puthash "\\itchi" "𝜒" julia-latexsubs)
+(puthash "\\itpsi" "𝜓" julia-latexsubs)
+(puthash "\\itomega" "𝜔" julia-latexsubs)
+(puthash "\\itpartial" "𝜕" julia-latexsubs)
+(puthash "\\itvarepsilon" "𝜖" julia-latexsubs)
+(puthash "\\itvartheta" "𝜗" julia-latexsubs)
+(puthash "\\itvarkappa" "𝜘" julia-latexsubs)
+(puthash "\\itvarphi" "𝜙" julia-latexsubs)
+(puthash "\\itvarrho" "𝜚" julia-latexsubs)
+(puthash "\\itvarpi" "𝜛" julia-latexsubs)
+(puthash "\\biAlpha" "𝜜" julia-latexsubs)
+(puthash "\\biBeta" "𝜝" julia-latexsubs)
+(puthash "\\biGamma" "𝜞" julia-latexsubs)
+(puthash "\\biDelta" "𝜟" julia-latexsubs)
+(puthash "\\biEpsilon" "𝜠" julia-latexsubs)
+(puthash "\\biZeta" "𝜡" julia-latexsubs)
+(puthash "\\biEta" "𝜢" julia-latexsubs)
+(puthash "\\biTheta" "𝜣" julia-latexsubs)
+(puthash "\\biIota" "𝜤" julia-latexsubs)
+(puthash "\\biKappa" "𝜥" julia-latexsubs)
+(puthash "\\biLambda" "𝜦" julia-latexsubs)
+(puthash "\\biMu" "𝜧" julia-latexsubs)
+(puthash "\\biNu" "𝜨" julia-latexsubs)
+(puthash "\\biXi" "𝜩" julia-latexsubs)
+(puthash "\\biOmicron" "𝜪" julia-latexsubs)
+(puthash "\\biPi" "𝜫" julia-latexsubs)
+(puthash "\\biRho" "𝜬" julia-latexsubs)
+(puthash "\\bivarTheta" "𝜭" julia-latexsubs)
+(puthash "\\biSigma" "𝜮" julia-latexsubs)
+(puthash "\\biTau" "𝜯" julia-latexsubs)
+(puthash "\\biUpsilon" "𝜰" julia-latexsubs)
+(puthash "\\biPhi" "𝜱" julia-latexsubs)
+(puthash "\\biChi" "𝜲" julia-latexsubs)
+(puthash "\\biPsi" "𝜳" julia-latexsubs)
+(puthash "\\biOmega" "𝜴" julia-latexsubs)
+(puthash "\\binabla" "𝜵" julia-latexsubs)
+(puthash "\\bialpha" "𝜶" julia-latexsubs)
+(puthash "\\bibeta" "𝜷" julia-latexsubs)
+(puthash "\\bigamma" "𝜸" julia-latexsubs)
+(puthash "\\bidelta" "𝜹" julia-latexsubs)
+(puthash "\\biepsilon" "𝜺" julia-latexsubs)
+(puthash "\\bizeta" "𝜻" julia-latexsubs)
+(puthash "\\bieta" "𝜼" julia-latexsubs)
+(puthash "\\bitheta" "𝜽" julia-latexsubs)
+(puthash "\\biiota" "𝜾" julia-latexsubs)
+(puthash "\\bikappa" "𝜿" julia-latexsubs)
+(puthash "\\bilambda" "𝝀" julia-latexsubs)
+(puthash "\\bimu" "𝝁" julia-latexsubs)
+(puthash "\\binu" "𝝂" julia-latexsubs)
+(puthash "\\bixi" "𝝃" julia-latexsubs)
+(puthash "\\biomicron" "𝝄" julia-latexsubs)
+(puthash "\\bipi" "𝝅" julia-latexsubs)
+(puthash "\\birho" "𝝆" julia-latexsubs)
+(puthash "\\bivarsigma" "𝝇" julia-latexsubs)
+(puthash "\\bisigma" "𝝈" julia-latexsubs)
+(puthash "\\bitau" "𝝉" julia-latexsubs)
+(puthash "\\biupsilon" "𝝊" julia-latexsubs)
+(puthash "\\biphi" "𝝋" julia-latexsubs)
+(puthash "\\bichi" "𝝌" julia-latexsubs)
+(puthash "\\bipsi" "𝝍" julia-latexsubs)
+(puthash "\\biomega" "𝝎" julia-latexsubs)
+(puthash "\\bipartial" "𝝏" julia-latexsubs)
+(puthash "\\bivarepsilon" "𝝐" julia-latexsubs)
+(puthash "\\bivartheta" "𝝑" julia-latexsubs)
+(puthash "\\bivarkappa" "𝝒" julia-latexsubs)
+(puthash "\\bivarphi" "𝝓" julia-latexsubs)
+(puthash "\\bivarrho" "𝝔" julia-latexsubs)
+(puthash "\\bivarpi" "𝝕" julia-latexsubs)
+(puthash "\\bsansAlpha" "𝝖" julia-latexsubs)
+(puthash "\\bsansBeta" "𝝗" julia-latexsubs)
+(puthash "\\bsansGamma" "𝝘" julia-latexsubs)
+(puthash "\\bsansDelta" "𝝙" julia-latexsubs)
+(puthash "\\bsansEpsilon" "𝝚" julia-latexsubs)
+(puthash "\\bsansZeta" "𝝛" julia-latexsubs)
+(puthash "\\bsansEta" "𝝜" julia-latexsubs)
+(puthash "\\bsansTheta" "𝝝" julia-latexsubs)
+(puthash "\\bsansIota" "𝝞" julia-latexsubs)
+(puthash "\\bsansKappa" "𝝟" julia-latexsubs)
+(puthash "\\bsansLambda" "𝝠" julia-latexsubs)
+(puthash "\\bsansMu" "𝝡" julia-latexsubs)
+(puthash "\\bsansNu" "𝝢" julia-latexsubs)
+(puthash "\\bsansXi" "𝝣" julia-latexsubs)
+(puthash "\\bsansOmicron" "𝝤" julia-latexsubs)
+(puthash "\\bsansPi" "𝝥" julia-latexsubs)
+(puthash "\\bsansRho" "𝝦" julia-latexsubs)
+(puthash "\\bsansvarTheta" "𝝧" julia-latexsubs)
+(puthash "\\bsansSigma" "𝝨" julia-latexsubs)
+(puthash "\\bsansTau" "𝝩" julia-latexsubs)
+(puthash "\\bsansUpsilon" "𝝪" julia-latexsubs)
+(puthash "\\bsansPhi" "𝝫" julia-latexsubs)
+(puthash "\\bsansChi" "𝝬" julia-latexsubs)
+(puthash "\\bsansPsi" "𝝭" julia-latexsubs)
+(puthash "\\bsansOmega" "𝝮" julia-latexsubs)
+(puthash "\\bsansnabla" "𝝯" julia-latexsubs)
+(puthash "\\bsansalpha" "𝝰" julia-latexsubs)
+(puthash "\\bsansbeta" "𝝱" julia-latexsubs)
+(puthash "\\bsansgamma" "𝝲" julia-latexsubs)
+(puthash "\\bsansdelta" "𝝳" julia-latexsubs)
+(puthash "\\bsansepsilon" "𝝴" julia-latexsubs)
+(puthash "\\bsanszeta" "𝝵" julia-latexsubs)
+(puthash "\\bsanseta" "𝝶" julia-latexsubs)
+(puthash "\\bsanstheta" "𝝷" julia-latexsubs)
+(puthash "\\bsansiota" "𝝸" julia-latexsubs)
+(puthash "\\bsanskappa" "𝝹" julia-latexsubs)
+(puthash "\\bsanslambda" "𝝺" julia-latexsubs)
+(puthash "\\bsansmu" "𝝻" julia-latexsubs)
+(puthash "\\bsansnu" "𝝼" julia-latexsubs)
+(puthash "\\bsansxi" "𝝽" julia-latexsubs)
+(puthash "\\bsansomicron" "𝝾" julia-latexsubs)
+(puthash "\\bsanspi" "𝝿" julia-latexsubs)
+(puthash "\\bsansrho" "𝞀" julia-latexsubs)
+(puthash "\\bsansvarsigma" "𝞁" julia-latexsubs)
+(puthash "\\bsanssigma" "𝞂" julia-latexsubs)
+(puthash "\\bsanstau" "𝞃" julia-latexsubs)
+(puthash "\\bsansupsilon" "𝞄" julia-latexsubs)
+(puthash "\\bsansphi" "𝞅" julia-latexsubs)
+(puthash "\\bsanschi" "𝞆" julia-latexsubs)
+(puthash "\\bsanspsi" "𝞇" julia-latexsubs)
+(puthash "\\bsansomega" "𝞈" julia-latexsubs)
+(puthash "\\bsanspartial" "𝞉" julia-latexsubs)
+(puthash "\\bsansvarepsilon" "𝞊" julia-latexsubs)
+(puthash "\\bsansvartheta" "𝞋" julia-latexsubs)
+(puthash "\\bsansvarkappa" "𝞌" julia-latexsubs)
+(puthash "\\bsansvarphi" "𝞍" julia-latexsubs)
+(puthash "\\bsansvarrho" "𝞎" julia-latexsubs)
+(puthash "\\bsansvarpi" "𝞏" julia-latexsubs)
+(puthash "\\bisansAlpha" "𝞐" julia-latexsubs)
+(puthash "\\bisansBeta" "𝞑" julia-latexsubs)
+(puthash "\\bisansGamma" "𝞒" julia-latexsubs)
+(puthash "\\bisansDelta" "𝞓" julia-latexsubs)
+(puthash "\\bisansEpsilon" "𝞔" julia-latexsubs)
+(puthash "\\bisansZeta" "𝞕" julia-latexsubs)
+(puthash "\\bisansEta" "𝞖" julia-latexsubs)
+(puthash "\\bisansTheta" "𝞗" julia-latexsubs)
+(puthash "\\bisansIota" "𝞘" julia-latexsubs)
+(puthash "\\bisansKappa" "𝞙" julia-latexsubs)
+(puthash "\\bisansLambda" "𝞚" julia-latexsubs)
+(puthash "\\bisansMu" "𝞛" julia-latexsubs)
+(puthash "\\bisansNu" "𝞜" julia-latexsubs)
+(puthash "\\bisansXi" "𝞝" julia-latexsubs)
+(puthash "\\bisansOmicron" "𝞞" julia-latexsubs)
+(puthash "\\bisansPi" "𝞟" julia-latexsubs)
+(puthash "\\bisansRho" "𝞠" julia-latexsubs)
+(puthash "\\bisansvarTheta" "𝞡" julia-latexsubs)
+(puthash "\\bisansSigma" "𝞢" julia-latexsubs)
+(puthash "\\bisansTau" "𝞣" julia-latexsubs)
+(puthash "\\bisansUpsilon" "𝞤" julia-latexsubs)
+(puthash "\\bisansPhi" "𝞥" julia-latexsubs)
+(puthash "\\bisansChi" "𝞦" julia-latexsubs)
+(puthash "\\bisansPsi" "𝞧" julia-latexsubs)
+(puthash "\\bisansOmega" "𝞨" julia-latexsubs)
+(puthash "\\bisansnabla" "𝞩" julia-latexsubs)
+(puthash "\\bisansalpha" "𝞪" julia-latexsubs)
+(puthash "\\bisansbeta" "𝞫" julia-latexsubs)
+(puthash "\\bisansgamma" "𝞬" julia-latexsubs)
+(puthash "\\bisansdelta" "𝞭" julia-latexsubs)
+(puthash "\\bisansepsilon" "𝞮" julia-latexsubs)
+(puthash "\\bisanszeta" "𝞯" julia-latexsubs)
+(puthash "\\bisanseta" "𝞰" julia-latexsubs)
+(puthash "\\bisanstheta" "𝞱" julia-latexsubs)
+(puthash "\\bisansiota" "𝞲" julia-latexsubs)
+(puthash "\\bisanskappa" "𝞳" julia-latexsubs)
+(puthash "\\bisanslambda" "𝞴" julia-latexsubs)
+(puthash "\\bisansmu" "𝞵" julia-latexsubs)
+(puthash "\\bisansnu" "𝞶" julia-latexsubs)
+(puthash "\\bisansxi" "𝞷" julia-latexsubs)
+(puthash "\\bisansomicron" "𝞸" julia-latexsubs)
+(puthash "\\bisanspi" "𝞹" julia-latexsubs)
+(puthash "\\bisansrho" "𝞺" julia-latexsubs)
+(puthash "\\bisansvarsigma" "𝞻" julia-latexsubs)
+(puthash "\\bisanssigma" "𝞼" julia-latexsubs)
+(puthash "\\bisanstau" "𝞽" julia-latexsubs)
+(puthash "\\bisansupsilon" "𝞾" julia-latexsubs)
+(puthash "\\bisansphi" "𝞿" julia-latexsubs)
+(puthash "\\bisanschi" "𝟀" julia-latexsubs)
+(puthash "\\bisanspsi" "𝟁" julia-latexsubs)
+(puthash "\\bisansomega" "𝟂" julia-latexsubs)
+(puthash "\\bisanspartial" "𝟃" julia-latexsubs)
+(puthash "\\bisansvarepsilon" "𝟄" julia-latexsubs)
+(puthash "\\bisansvartheta" "𝟅" julia-latexsubs)
+(puthash "\\bisansvarkappa" "𝟆" julia-latexsubs)
+(puthash "\\bisansvarphi" "𝟇" julia-latexsubs)
+(puthash "\\bisansvarrho" "𝟈" julia-latexsubs)
+(puthash "\\bisansvarpi" "𝟉" julia-latexsubs)
+(puthash "\\bfDigamma" "𝟊" julia-latexsubs)
+(puthash "\\bfdigamma" "𝟋" julia-latexsubs)
+(puthash "\\bfzero" "𝟎" julia-latexsubs)
+(puthash "\\bfone" "𝟏" julia-latexsubs)
+(puthash "\\bftwo" "𝟐" julia-latexsubs)
+(puthash "\\bfthree" "𝟑" julia-latexsubs)
+(puthash "\\bffour" "𝟒" julia-latexsubs)
+(puthash "\\bffive" "𝟓" julia-latexsubs)
+(puthash "\\bfsix" "𝟔" julia-latexsubs)
+(puthash "\\bfseven" "𝟕" julia-latexsubs)
+(puthash "\\bfeight" "𝟖" julia-latexsubs)
+(puthash "\\bfnine" "𝟗" julia-latexsubs)
+(puthash "\\bbzero" "𝟘" julia-latexsubs)
+(puthash "\\bbone" "𝟙" julia-latexsubs)
+(puthash "\\bbtwo" "𝟚" julia-latexsubs)
+(puthash "\\bbthree" "𝟛" julia-latexsubs)
+(puthash "\\bbfour" "𝟜" julia-latexsubs)
+(puthash "\\bbfive" "𝟝" julia-latexsubs)
+(puthash "\\bbsix" "𝟞" julia-latexsubs)
+(puthash "\\bbseven" "𝟟" julia-latexsubs)
+(puthash "\\bbeight" "𝟠" julia-latexsubs)
+(puthash "\\bbnine" "𝟡" julia-latexsubs)
+(puthash "\\sanszero" "𝟢" julia-latexsubs)
+(puthash "\\sansone" "𝟣" julia-latexsubs)
+(puthash "\\sanstwo" "𝟤" julia-latexsubs)
+(puthash "\\sansthree" "𝟥" julia-latexsubs)
+(puthash "\\sansfour" "𝟦" julia-latexsubs)
+(puthash "\\sansfive" "𝟧" julia-latexsubs)
+(puthash "\\sanssix" "𝟨" julia-latexsubs)
+(puthash "\\sansseven" "𝟩" julia-latexsubs)
+(puthash "\\sanseight" "𝟪" julia-latexsubs)
+(puthash "\\sansnine" "𝟫" julia-latexsubs)
+(puthash "\\bsanszero" "𝟬" julia-latexsubs)
+(puthash "\\bsansone" "𝟭" julia-latexsubs)
+(puthash "\\bsanstwo" "𝟮" julia-latexsubs)
+(puthash "\\bsansthree" "𝟯" julia-latexsubs)
+(puthash "\\bsansfour" "𝟰" julia-latexsubs)
+(puthash "\\bsansfive" "𝟱" julia-latexsubs)
+(puthash "\\bsanssix" "𝟲" julia-latexsubs)
+(puthash "\\bsansseven" "𝟳" julia-latexsubs)
+(puthash "\\bsanseight" "𝟴" julia-latexsubs)
+(puthash "\\bsansnine" "𝟵" julia-latexsubs)
+(puthash "\\ttzero" "𝟶" julia-latexsubs)
+(puthash "\\ttone" "𝟷" julia-latexsubs)
+(puthash "\\tttwo" "𝟸" julia-latexsubs)
+(puthash "\\ttthree" "𝟹" julia-latexsubs)
+(puthash "\\ttfour" "𝟺" julia-latexsubs)
+(puthash "\\ttfive" "𝟻" julia-latexsubs)
+(puthash "\\ttsix" "𝟼" julia-latexsubs)
+(puthash "\\ttseven" "𝟽" julia-latexsubs)
+(puthash "\\tteight" "𝟾" julia-latexsubs)
+(puthash "\\ttnine" "𝟿" julia-latexsubs)
+(puthash "\\:mahjong:" "🀄" julia-latexsubs)
+(puthash "\\:black_joker:" "🃏" julia-latexsubs)
+(puthash "\\:a:" "🅰" julia-latexsubs)
+(puthash "\\:b:" "🅱" julia-latexsubs)
+(puthash "\\:o2:" "🅾" julia-latexsubs)
+(puthash "\\:parking:" "🅿" julia-latexsubs)
+(puthash "\\:ab:" "🆎" julia-latexsubs)
+(puthash "\\:cl:" "🆑" julia-latexsubs)
+(puthash "\\:cool:" "🆒" julia-latexsubs)
+(puthash "\\:free:" "🆓" julia-latexsubs)
+(puthash "\\:id:" "🆔" julia-latexsubs)
+(puthash "\\:new:" "🆕" julia-latexsubs)
+(puthash "\\:ng:" "🆖" julia-latexsubs)
+(puthash "\\:ok:" "🆗" julia-latexsubs)
+(puthash "\\:sos:" "🆘" julia-latexsubs)
+(puthash "\\:up:" "🆙" julia-latexsubs)
+(puthash "\\:vs:" "🆚" julia-latexsubs)
+(puthash "\\:koko:" "🈁" julia-latexsubs)
+(puthash "\\:sa:" "🈂" julia-latexsubs)
+(puthash "\\:u7121:" "🈚" julia-latexsubs)
+(puthash "\\:u6307:" "🈯" julia-latexsubs)
+(puthash "\\:u7981:" "🈲" julia-latexsubs)
+(puthash "\\:u7a7a:" "🈳" julia-latexsubs)
+(puthash "\\:u5408:" "🈴" julia-latexsubs)
+(puthash "\\:u6e80:" "🈵" julia-latexsubs)
+(puthash "\\:u6709:" "🈶" julia-latexsubs)
+(puthash "\\:u6708:" "🈷" julia-latexsubs)
+(puthash "\\:u7533:" "🈸" julia-latexsubs)
+(puthash "\\:u5272:" "🈹" julia-latexsubs)
+(puthash "\\:u55b6:" "🈺" julia-latexsubs)
+(puthash "\\:ideograph_advantage:" "🉐" julia-latexsubs)
+(puthash "\\:accept:" "🉑" julia-latexsubs)
+(puthash "\\:cyclone:" "🌀" julia-latexsubs)
+(puthash "\\:foggy:" "🌁" julia-latexsubs)
+(puthash "\\:closed_umbrella:" "🌂" julia-latexsubs)
+(puthash "\\:night_with_stars:" "🌃" julia-latexsubs)
+(puthash "\\:sunrise_over_mountains:" "🌄" julia-latexsubs)
+(puthash "\\:sunrise:" "🌅" julia-latexsubs)
+(puthash "\\:city_sunset:" "🌆" julia-latexsubs)
+(puthash "\\:city_sunrise:" "🌇" julia-latexsubs)
+(puthash "\\:rainbow:" "🌈" julia-latexsubs)
+(puthash "\\:bridge_at_night:" "🌉" julia-latexsubs)
+(puthash "\\:ocean:" "🌊" julia-latexsubs)
+(puthash "\\:volcano:" "🌋" julia-latexsubs)
+(puthash "\\:milky_way:" "🌌" julia-latexsubs)
+(puthash "\\:earth_africa:" "🌍" julia-latexsubs)
+(puthash "\\:earth_americas:" "🌎" julia-latexsubs)
+(puthash "\\:earth_asia:" "🌏" julia-latexsubs)
+(puthash "\\:globe_with_meridians:" "🌐" julia-latexsubs)
+(puthash "\\:new_moon:" "🌑" julia-latexsubs)
+(puthash "\\:waxing_crescent_moon:" "🌒" julia-latexsubs)
+(puthash "\\:first_quarter_moon:" "🌓" julia-latexsubs)
+(puthash "\\:moon:" "🌔" julia-latexsubs)
+(puthash "\\:full_moon:" "🌕" julia-latexsubs)
+(puthash "\\:waning_gibbous_moon:" "🌖" julia-latexsubs)
+(puthash "\\:last_quarter_moon:" "🌗" julia-latexsubs)
+(puthash "\\:waning_crescent_moon:" "🌘" julia-latexsubs)
+(puthash "\\:crescent_moon:" "🌙" julia-latexsubs)
+(puthash "\\:new_moon_with_face:" "🌚" julia-latexsubs)
+(puthash "\\:first_quarter_moon_with_face:" "🌛" julia-latexsubs)
+(puthash "\\:last_quarter_moon_with_face:" "🌜" julia-latexsubs)
+(puthash "\\:full_moon_with_face:" "🌝" julia-latexsubs)
+(puthash "\\:sun_with_face:" "🌞" julia-latexsubs)
+(puthash "\\:star2:" "🌟" julia-latexsubs)
+(puthash "\\:stars:" "🌠" julia-latexsubs)
+(puthash "\\:chestnut:" "🌰" julia-latexsubs)
+(puthash "\\:seedling:" "🌱" julia-latexsubs)
+(puthash "\\:evergreen_tree:" "🌲" julia-latexsubs)
+(puthash "\\:deciduous_tree:" "🌳" julia-latexsubs)
+(puthash "\\:palm_tree:" "🌴" julia-latexsubs)
+(puthash "\\:cactus:" "🌵" julia-latexsubs)
+(puthash "\\:tulip:" "🌷" julia-latexsubs)
+(puthash "\\:cherry_blossom:" "🌸" julia-latexsubs)
+(puthash "\\:rose:" "🌹" julia-latexsubs)
+(puthash "\\:hibiscus:" "🌺" julia-latexsubs)
+(puthash "\\:sunflower:" "🌻" julia-latexsubs)
+(puthash "\\:blossom:" "🌼" julia-latexsubs)
+(puthash "\\:corn:" "🌽" julia-latexsubs)
+(puthash "\\:ear_of_rice:" "🌾" julia-latexsubs)
+(puthash "\\:herb:" "🌿" julia-latexsubs)
+(puthash "\\:four_leaf_clover:" "🍀" julia-latexsubs)
+(puthash "\\:maple_leaf:" "🍁" julia-latexsubs)
+(puthash "\\:fallen_leaf:" "🍂" julia-latexsubs)
+(puthash "\\:leaves:" "🍃" julia-latexsubs)
+(puthash "\\:mushroom:" "🍄" julia-latexsubs)
+(puthash "\\:tomato:" "🍅" julia-latexsubs)
+(puthash "\\:eggplant:" "🍆" julia-latexsubs)
+(puthash "\\:grapes:" "🍇" julia-latexsubs)
+(puthash "\\:melon:" "🍈" julia-latexsubs)
+(puthash "\\:watermelon:" "🍉" julia-latexsubs)
+(puthash "\\:tangerine:" "🍊" julia-latexsubs)
+(puthash "\\:lemon:" "🍋" julia-latexsubs)
+(puthash "\\:banana:" "🍌" julia-latexsubs)
+(puthash "\\:pineapple:" "🍍" julia-latexsubs)
+(puthash "\\:apple:" "🍎" julia-latexsubs)
+(puthash "\\:green_apple:" "🍏" julia-latexsubs)
+(puthash "\\:pear:" "🍐" julia-latexsubs)
+(puthash "\\:peach:" "🍑" julia-latexsubs)
+(puthash "\\:cherries:" "🍒" julia-latexsubs)
+(puthash "\\:strawberry:" "🍓" julia-latexsubs)
+(puthash "\\:hamburger:" "🍔" julia-latexsubs)
+(puthash "\\:pizza:" "🍕" julia-latexsubs)
+(puthash "\\:meat_on_bone:" "🍖" julia-latexsubs)
+(puthash "\\:poultry_leg:" "🍗" julia-latexsubs)
+(puthash "\\:rice_cracker:" "🍘" julia-latexsubs)
+(puthash "\\:rice_ball:" "🍙" julia-latexsubs)
+(puthash "\\:rice:" "🍚" julia-latexsubs)
+(puthash "\\:curry:" "🍛" julia-latexsubs)
+(puthash "\\:ramen:" "🍜" julia-latexsubs)
+(puthash "\\:spaghetti:" "🍝" julia-latexsubs)
+(puthash "\\:bread:" "🍞" julia-latexsubs)
+(puthash "\\:fries:" "🍟" julia-latexsubs)
+(puthash "\\:sweet_potato:" "🍠" julia-latexsubs)
+(puthash "\\:dango:" "🍡" julia-latexsubs)
+(puthash "\\:oden:" "🍢" julia-latexsubs)
+(puthash "\\:sushi:" "🍣" julia-latexsubs)
+(puthash "\\:fried_shrimp:" "🍤" julia-latexsubs)
+(puthash "\\:fish_cake:" "🍥" julia-latexsubs)
+(puthash "\\:icecream:" "🍦" julia-latexsubs)
+(puthash "\\:shaved_ice:" "🍧" julia-latexsubs)
+(puthash "\\:ice_cream:" "🍨" julia-latexsubs)
+(puthash "\\:doughnut:" "🍩" julia-latexsubs)
+(puthash "\\:cookie:" "🍪" julia-latexsubs)
+(puthash "\\:chocolate_bar:" "🍫" julia-latexsubs)
+(puthash "\\:candy:" "🍬" julia-latexsubs)
+(puthash "\\:lollipop:" "🍭" julia-latexsubs)
+(puthash "\\:custard:" "🍮" julia-latexsubs)
+(puthash "\\:honey_pot:" "🍯" julia-latexsubs)
+(puthash "\\:cake:" "🍰" julia-latexsubs)
+(puthash "\\:bento:" "🍱" julia-latexsubs)
+(puthash "\\:stew:" "🍲" julia-latexsubs)
+(puthash "\\:egg:" "🍳" julia-latexsubs)
+(puthash "\\:fork_and_knife:" "🍴" julia-latexsubs)
+(puthash "\\:tea:" "🍵" julia-latexsubs)
+(puthash "\\:sake:" "🍶" julia-latexsubs)
+(puthash "\\:wine_glass:" "🍷" julia-latexsubs)
+(puthash "\\:cocktail:" "🍸" julia-latexsubs)
+(puthash "\\:tropical_drink:" "🍹" julia-latexsubs)
+(puthash "\\:beer:" "🍺" julia-latexsubs)
+(puthash "\\:beers:" "🍻" julia-latexsubs)
+(puthash "\\:baby_bottle:" "🍼" julia-latexsubs)
+(puthash "\\:ribbon:" "🎀" julia-latexsubs)
+(puthash "\\:gift:" "🎁" julia-latexsubs)
+(puthash "\\:birthday:" "🎂" julia-latexsubs)
+(puthash "\\:jack_o_lantern:" "🎃" julia-latexsubs)
+(puthash "\\:christmas_tree:" "🎄" julia-latexsubs)
+(puthash "\\:santa:" "🎅" julia-latexsubs)
+(puthash "\\:fireworks:" "🎆" julia-latexsubs)
+(puthash "\\:sparkler:" "🎇" julia-latexsubs)
+(puthash "\\:balloon:" "🎈" julia-latexsubs)
+(puthash "\\:tada:" "🎉" julia-latexsubs)
+(puthash "\\:confetti_ball:" "🎊" julia-latexsubs)
+(puthash "\\:tanabata_tree:" "🎋" julia-latexsubs)
+(puthash "\\:crossed_flags:" "🎌" julia-latexsubs)
+(puthash "\\:bamboo:" "🎍" julia-latexsubs)
+(puthash "\\:dolls:" "🎎" julia-latexsubs)
+(puthash "\\:flags:" "🎏" julia-latexsubs)
+(puthash "\\:wind_chime:" "🎐" julia-latexsubs)
+(puthash "\\:rice_scene:" "🎑" julia-latexsubs)
+(puthash "\\:school_satchel:" "🎒" julia-latexsubs)
+(puthash "\\:mortar_board:" "🎓" julia-latexsubs)
+(puthash "\\:carousel_horse:" "🎠" julia-latexsubs)
+(puthash "\\:ferris_wheel:" "🎡" julia-latexsubs)
+(puthash "\\:roller_coaster:" "🎢" julia-latexsubs)
+(puthash "\\:fishing_pole_and_fish:" "🎣" julia-latexsubs)
+(puthash "\\:microphone:" "🎤" julia-latexsubs)
+(puthash "\\:movie_camera:" "🎥" julia-latexsubs)
+(puthash "\\:cinema:" "🎦" julia-latexsubs)
+(puthash "\\:headphones:" "🎧" julia-latexsubs)
+(puthash "\\:art:" "🎨" julia-latexsubs)
+(puthash "\\:tophat:" "🎩" julia-latexsubs)
+(puthash "\\:circus_tent:" "🎪" julia-latexsubs)
+(puthash "\\:ticket:" "🎫" julia-latexsubs)
+(puthash "\\:clapper:" "🎬" julia-latexsubs)
+(puthash "\\:performing_arts:" "🎭" julia-latexsubs)
+(puthash "\\:video_game:" "🎮" julia-latexsubs)
+(puthash "\\:dart:" "🎯" julia-latexsubs)
+(puthash "\\:slot_machine:" "🎰" julia-latexsubs)
+(puthash "\\:8ball:" "🎱" julia-latexsubs)
+(puthash "\\:game_die:" "🎲" julia-latexsubs)
+(puthash "\\:bowling:" "🎳" julia-latexsubs)
+(puthash "\\:flower_playing_cards:" "🎴" julia-latexsubs)
+(puthash "\\:musical_note:" "🎵" julia-latexsubs)
+(puthash "\\:notes:" "🎶" julia-latexsubs)
+(puthash "\\:saxophone:" "🎷" julia-latexsubs)
+(puthash "\\:guitar:" "🎸" julia-latexsubs)
+(puthash "\\:musical_keyboard:" "🎹" julia-latexsubs)
+(puthash "\\:trumpet:" "🎺" julia-latexsubs)
+(puthash "\\:violin:" "🎻" julia-latexsubs)
+(puthash "\\:musical_score:" "🎼" julia-latexsubs)
+(puthash "\\:running_shirt_with_sash:" "🎽" julia-latexsubs)
+(puthash "\\:tennis:" "🎾" julia-latexsubs)
+(puthash "\\:ski:" "🎿" julia-latexsubs)
+(puthash "\\:basketball:" "🏀" julia-latexsubs)
+(puthash "\\:checkered_flag:" "🏁" julia-latexsubs)
+(puthash "\\:snowboarder:" "🏂" julia-latexsubs)
+(puthash "\\:runner:" "🏃" julia-latexsubs)
+(puthash "\\:surfer:" "🏄" julia-latexsubs)
+(puthash "\\:trophy:" "🏆" julia-latexsubs)
+(puthash "\\:horse_racing:" "🏇" julia-latexsubs)
+(puthash "\\:football:" "🏈" julia-latexsubs)
+(puthash "\\:rugby_football:" "🏉" julia-latexsubs)
+(puthash "\\:swimmer:" "🏊" julia-latexsubs)
+(puthash "\\:house:" "🏠" julia-latexsubs)
+(puthash "\\:house_with_garden:" "🏡" julia-latexsubs)
+(puthash "\\:office:" "🏢" julia-latexsubs)
+(puthash "\\:post_office:" "🏣" julia-latexsubs)
+(puthash "\\:european_post_office:" "🏤" julia-latexsubs)
+(puthash "\\:hospital:" "🏥" julia-latexsubs)
+(puthash "\\:bank:" "🏦" julia-latexsubs)
+(puthash "\\:atm:" "🏧" julia-latexsubs)
+(puthash "\\:hotel:" "🏨" julia-latexsubs)
+(puthash "\\:love_hotel:" "🏩" julia-latexsubs)
+(puthash "\\:convenience_store:" "🏪" julia-latexsubs)
+(puthash "\\:school:" "🏫" julia-latexsubs)
+(puthash "\\:department_store:" "🏬" julia-latexsubs)
+(puthash "\\:factory:" "🏭" julia-latexsubs)
+(puthash "\\:izakaya_lantern:" "🏮" julia-latexsubs)
+(puthash "\\:japanese_castle:" "🏯" julia-latexsubs)
+(puthash "\\:european_castle:" "🏰" julia-latexsubs)
+(puthash "\\:skin-tone-2:" "🏻" julia-latexsubs)
+(puthash "\\:skin-tone-3:" "🏼" julia-latexsubs)
+(puthash "\\:skin-tone-4:" "🏽" julia-latexsubs)
+(puthash "\\:skin-tone-5:" "🏾" julia-latexsubs)
+(puthash "\\:skin-tone-6:" "🏿" julia-latexsubs)
+(puthash "\\:rat:" "🐀" julia-latexsubs)
+(puthash "\\:mouse2:" "🐁" julia-latexsubs)
+(puthash "\\:ox:" "🐂" julia-latexsubs)
+(puthash "\\:water_buffalo:" "🐃" julia-latexsubs)
+(puthash "\\:cow2:" "🐄" julia-latexsubs)
+(puthash "\\:tiger2:" "🐅" julia-latexsubs)
+(puthash "\\:leopard:" "🐆" julia-latexsubs)
+(puthash "\\:rabbit2:" "🐇" julia-latexsubs)
+(puthash "\\:cat2:" "🐈" julia-latexsubs)
+(puthash "\\:dragon:" "🐉" julia-latexsubs)
+(puthash "\\:crocodile:" "🐊" julia-latexsubs)
+(puthash "\\:whale2:" "🐋" julia-latexsubs)
+(puthash "\\:snail:" "🐌" julia-latexsubs)
+(puthash "\\:snake:" "🐍" julia-latexsubs)
+(puthash "\\:racehorse:" "🐎" julia-latexsubs)
+(puthash "\\:ram:" "🐏" julia-latexsubs)
+(puthash "\\:goat:" "🐐" julia-latexsubs)
+(puthash "\\:sheep:" "🐑" julia-latexsubs)
+(puthash "\\:monkey:" "🐒" julia-latexsubs)
+(puthash "\\:rooster:" "🐓" julia-latexsubs)
+(puthash "\\:chicken:" "🐔" julia-latexsubs)
+(puthash "\\:dog2:" "🐕" julia-latexsubs)
+(puthash "\\:pig2:" "🐖" julia-latexsubs)
+(puthash "\\:boar:" "🐗" julia-latexsubs)
+(puthash "\\:elephant:" "🐘" julia-latexsubs)
+(puthash "\\:octopus:" "🐙" julia-latexsubs)
+(puthash "\\:shell:" "🐚" julia-latexsubs)
+(puthash "\\:bug:" "🐛" julia-latexsubs)
+(puthash "\\:ant:" "🐜" julia-latexsubs)
+(puthash "\\:bee:" "🐝" julia-latexsubs)
+(puthash "\\:beetle:" "🐞" julia-latexsubs)
+(puthash "\\:fish:" "🐟" julia-latexsubs)
+(puthash "\\:tropical_fish:" "🐠" julia-latexsubs)
+(puthash "\\:blowfish:" "🐡" julia-latexsubs)
+(puthash "\\:turtle:" "🐢" julia-latexsubs)
+(puthash "\\:hatching_chick:" "🐣" julia-latexsubs)
+(puthash "\\:baby_chick:" "🐤" julia-latexsubs)
+(puthash "\\:hatched_chick:" "🐥" julia-latexsubs)
+(puthash "\\:bird:" "🐦" julia-latexsubs)
+(puthash "\\:penguin:" "🐧" julia-latexsubs)
+(puthash "\\:koala:" "🐨" julia-latexsubs)
+(puthash "\\:poodle:" "🐩" julia-latexsubs)
+(puthash "\\:dromedary_camel:" "🐪" julia-latexsubs)
+(puthash "\\:camel:" "🐫" julia-latexsubs)
+(puthash "\\:dolphin:" "🐬" julia-latexsubs)
+(puthash "\\:mouse:" "🐭" julia-latexsubs)
+(puthash "\\:cow:" "🐮" julia-latexsubs)
+(puthash "\\:tiger:" "🐯" julia-latexsubs)
+(puthash "\\:rabbit:" "🐰" julia-latexsubs)
+(puthash "\\:cat:" "🐱" julia-latexsubs)
+(puthash "\\:dragon_face:" "🐲" julia-latexsubs)
+(puthash "\\:whale:" "🐳" julia-latexsubs)
+(puthash "\\:horse:" "🐴" julia-latexsubs)
+(puthash "\\:monkey_face:" "🐵" julia-latexsubs)
+(puthash "\\:dog:" "🐶" julia-latexsubs)
+(puthash "\\:pig:" "🐷" julia-latexsubs)
+(puthash "\\:frog:" "🐸" julia-latexsubs)
+(puthash "\\:hamster:" "🐹" julia-latexsubs)
+(puthash "\\:wolf:" "🐺" julia-latexsubs)
+(puthash "\\:bear:" "🐻" julia-latexsubs)
+(puthash "\\:panda_face:" "🐼" julia-latexsubs)
+(puthash "\\:pig_nose:" "🐽" julia-latexsubs)
+(puthash "\\:feet:" "🐾" julia-latexsubs)
+(puthash "\\:eyes:" "👀" julia-latexsubs)
+(puthash "\\:ear:" "👂" julia-latexsubs)
+(puthash "\\:nose:" "👃" julia-latexsubs)
+(puthash "\\:lips:" "👄" julia-latexsubs)
+(puthash "\\:tongue:" "👅" julia-latexsubs)
+(puthash "\\:point_up_2:" "👆" julia-latexsubs)
+(puthash "\\:point_down:" "👇" julia-latexsubs)
+(puthash "\\:point_left:" "👈" julia-latexsubs)
+(puthash "\\:point_right:" "👉" julia-latexsubs)
+(puthash "\\:facepunch:" "👊" julia-latexsubs)
+(puthash "\\:wave:" "👋" julia-latexsubs)
+(puthash "\\:ok_hand:" "👌" julia-latexsubs)
+(puthash "\\:+1:" "👍" julia-latexsubs)
+(puthash "\\:-1:" "👎" julia-latexsubs)
+(puthash "\\:clap:" "👏" julia-latexsubs)
+(puthash "\\:open_hands:" "👐" julia-latexsubs)
+(puthash "\\:crown:" "👑" julia-latexsubs)
+(puthash "\\:womans_hat:" "👒" julia-latexsubs)
+(puthash "\\:eyeglasses:" "👓" julia-latexsubs)
+(puthash "\\:necktie:" "👔" julia-latexsubs)
+(puthash "\\:shirt:" "👕" julia-latexsubs)
+(puthash "\\:jeans:" "👖" julia-latexsubs)
+(puthash "\\:dress:" "👗" julia-latexsubs)
+(puthash "\\:kimono:" "👘" julia-latexsubs)
+(puthash "\\:bikini:" "👙" julia-latexsubs)
+(puthash "\\:womans_clothes:" "👚" julia-latexsubs)
+(puthash "\\:purse:" "👛" julia-latexsubs)
+(puthash "\\:handbag:" "👜" julia-latexsubs)
+(puthash "\\:pouch:" "👝" julia-latexsubs)
+(puthash "\\:mans_shoe:" "👞" julia-latexsubs)
+(puthash "\\:athletic_shoe:" "👟" julia-latexsubs)
+(puthash "\\:high_heel:" "👠" julia-latexsubs)
+(puthash "\\:sandal:" "👡" julia-latexsubs)
+(puthash "\\:boot:" "👢" julia-latexsubs)
+(puthash "\\:footprints:" "👣" julia-latexsubs)
+(puthash "\\:bust_in_silhouette:" "👤" julia-latexsubs)
+(puthash "\\:busts_in_silhouette:" "👥" julia-latexsubs)
+(puthash "\\:boy:" "👦" julia-latexsubs)
+(puthash "\\:girl:" "👧" julia-latexsubs)
+(puthash "\\:man:" "👨" julia-latexsubs)
+(puthash "\\:woman:" "👩" julia-latexsubs)
+(puthash "\\:family:" "👪" julia-latexsubs)
+(puthash "\\:couple:" "👫" julia-latexsubs)
+(puthash "\\:two_men_holding_hands:" "👬" julia-latexsubs)
+(puthash "\\:two_women_holding_hands:" "👭" julia-latexsubs)
+(puthash "\\:cop:" "👮" julia-latexsubs)
+(puthash "\\:dancers:" "👯" julia-latexsubs)
+(puthash "\\:bride_with_veil:" "👰" julia-latexsubs)
+(puthash "\\:person_with_blond_hair:" "👱" julia-latexsubs)
+(puthash "\\:man_with_gua_pi_mao:" "👲" julia-latexsubs)
+(puthash "\\:man_with_turban:" "👳" julia-latexsubs)
+(puthash "\\:older_man:" "👴" julia-latexsubs)
+(puthash "\\:older_woman:" "👵" julia-latexsubs)
+(puthash "\\:baby:" "👶" julia-latexsubs)
+(puthash "\\:construction_worker:" "👷" julia-latexsubs)
+(puthash "\\:princess:" "👸" julia-latexsubs)
+(puthash "\\:japanese_ogre:" "👹" julia-latexsubs)
+(puthash "\\:japanese_goblin:" "👺" julia-latexsubs)
+(puthash "\\:ghost:" "👻" julia-latexsubs)
+(puthash "\\:angel:" "👼" julia-latexsubs)
+(puthash "\\:alien:" "👽" julia-latexsubs)
+(puthash "\\:space_invader:" "👾" julia-latexsubs)
+(puthash "\\:imp:" "👿" julia-latexsubs)
+(puthash "\\:skull:" "💀" julia-latexsubs)
+(puthash "\\:information_desk_person:" "💁" julia-latexsubs)
+(puthash "\\:guardsman:" "💂" julia-latexsubs)
+(puthash "\\:dancer:" "💃" julia-latexsubs)
+(puthash "\\:lipstick:" "💄" julia-latexsubs)
+(puthash "\\:nail_care:" "💅" julia-latexsubs)
+(puthash "\\:massage:" "💆" julia-latexsubs)
+(puthash "\\:haircut:" "💇" julia-latexsubs)
+(puthash "\\:barber:" "💈" julia-latexsubs)
+(puthash "\\:syringe:" "💉" julia-latexsubs)
+(puthash "\\:pill:" "💊" julia-latexsubs)
+(puthash "\\:kiss:" "💋" julia-latexsubs)
+(puthash "\\:love_letter:" "💌" julia-latexsubs)
+(puthash "\\:ring:" "💍" julia-latexsubs)
+(puthash "\\:gem:" "💎" julia-latexsubs)
+(puthash "\\:couplekiss:" "💏" julia-latexsubs)
+(puthash "\\:bouquet:" "💐" julia-latexsubs)
+(puthash "\\:couple_with_heart:" "💑" julia-latexsubs)
+(puthash "\\:wedding:" "💒" julia-latexsubs)
+(puthash "\\:heartbeat:" "💓" julia-latexsubs)
+(puthash "\\:broken_heart:" "💔" julia-latexsubs)
+(puthash "\\:two_hearts:" "💕" julia-latexsubs)
+(puthash "\\:sparkling_heart:" "💖" julia-latexsubs)
+(puthash "\\:heartpulse:" "💗" julia-latexsubs)
+(puthash "\\:cupid:" "💘" julia-latexsubs)
+(puthash "\\:blue_heart:" "💙" julia-latexsubs)
+(puthash "\\:green_heart:" "💚" julia-latexsubs)
+(puthash "\\:yellow_heart:" "💛" julia-latexsubs)
+(puthash "\\:purple_heart:" "💜" julia-latexsubs)
+(puthash "\\:gift_heart:" "💝" julia-latexsubs)
+(puthash "\\:revolving_hearts:" "💞" julia-latexsubs)
+(puthash "\\:heart_decoration:" "💟" julia-latexsubs)
+(puthash "\\:diamond_shape_with_a_dot_inside:" "💠" julia-latexsubs)
+(puthash "\\:bulb:" "💡" julia-latexsubs)
+(puthash "\\:anger:" "💢" julia-latexsubs)
+(puthash "\\:bomb:" "💣" julia-latexsubs)
+(puthash "\\:zzz:" "💤" julia-latexsubs)
+(puthash "\\:boom:" "💥" julia-latexsubs)
+(puthash "\\:sweat_drops:" "💦" julia-latexsubs)
+(puthash "\\:droplet:" "💧" julia-latexsubs)
+(puthash "\\:dash:" "💨" julia-latexsubs)
+(puthash "\\:hankey:" "💩" julia-latexsubs)
+(puthash "\\:muscle:" "💪" julia-latexsubs)
+(puthash "\\:dizzy:" "💫" julia-latexsubs)
+(puthash "\\:speech_balloon:" "💬" julia-latexsubs)
+(puthash "\\:thought_balloon:" "💭" julia-latexsubs)
+(puthash "\\:white_flower:" "💮" julia-latexsubs)
+(puthash "\\:100:" "💯" julia-latexsubs)
+(puthash "\\:moneybag:" "💰" julia-latexsubs)
+(puthash "\\:currency_exchange:" "💱" julia-latexsubs)
+(puthash "\\:heavy_dollar_sign:" "💲" julia-latexsubs)
+(puthash "\\:credit_card:" "💳" julia-latexsubs)
+(puthash "\\:yen:" "💴" julia-latexsubs)
+(puthash "\\:dollar:" "💵" julia-latexsubs)
+(puthash "\\:euro:" "💶" julia-latexsubs)
+(puthash "\\:pound:" "💷" julia-latexsubs)
+(puthash "\\:money_with_wings:" "💸" julia-latexsubs)
+(puthash "\\:chart:" "💹" julia-latexsubs)
+(puthash "\\:seat:" "💺" julia-latexsubs)
+(puthash "\\:computer:" "💻" julia-latexsubs)
+(puthash "\\:briefcase:" "💼" julia-latexsubs)
+(puthash "\\:minidisc:" "💽" julia-latexsubs)
+(puthash "\\:floppy_disk:" "💾" julia-latexsubs)
+(puthash "\\:cd:" "💿" julia-latexsubs)
+(puthash "\\:dvd:" "📀" julia-latexsubs)
+(puthash "\\:file_folder:" "📁" julia-latexsubs)
+(puthash "\\:open_file_folder:" "📂" julia-latexsubs)
+(puthash "\\:page_with_curl:" "📃" julia-latexsubs)
+(puthash "\\:page_facing_up:" "📄" julia-latexsubs)
+(puthash "\\:date:" "📅" julia-latexsubs)
+(puthash "\\:calendar:" "📆" julia-latexsubs)
+(puthash "\\:card_index:" "📇" julia-latexsubs)
+(puthash "\\:chart_with_upwards_trend:" "📈" julia-latexsubs)
+(puthash "\\:chart_with_downwards_trend:" "📉" julia-latexsubs)
+(puthash "\\:bar_chart:" "📊" julia-latexsubs)
+(puthash "\\:clipboard:" "📋" julia-latexsubs)
+(puthash "\\:pushpin:" "📌" julia-latexsubs)
+(puthash "\\:round_pushpin:" "📍" julia-latexsubs)
+(puthash "\\:paperclip:" "📎" julia-latexsubs)
+(puthash "\\:straight_ruler:" "📏" julia-latexsubs)
+(puthash "\\:triangular_ruler:" "📐" julia-latexsubs)
+(puthash "\\:bookmark_tabs:" "📑" julia-latexsubs)
+(puthash "\\:ledger:" "📒" julia-latexsubs)
+(puthash "\\:notebook:" "📓" julia-latexsubs)
+(puthash "\\:notebook_with_decorative_cover:" "📔" julia-latexsubs)
+(puthash "\\:closed_book:" "📕" julia-latexsubs)
+(puthash "\\:book:" "📖" julia-latexsubs)
+(puthash "\\:green_book:" "📗" julia-latexsubs)
+(puthash "\\:blue_book:" "📘" julia-latexsubs)
+(puthash "\\:orange_book:" "📙" julia-latexsubs)
+(puthash "\\:books:" "📚" julia-latexsubs)
+(puthash "\\:name_badge:" "📛" julia-latexsubs)
+(puthash "\\:scroll:" "📜" julia-latexsubs)
+(puthash "\\:memo:" "📝" julia-latexsubs)
+(puthash "\\:telephone_receiver:" "📞" julia-latexsubs)
+(puthash "\\:pager:" "📟" julia-latexsubs)
+(puthash "\\:fax:" "📠" julia-latexsubs)
+(puthash "\\:satellite:" "📡" julia-latexsubs)
+(puthash "\\:loudspeaker:" "📢" julia-latexsubs)
+(puthash "\\:mega:" "📣" julia-latexsubs)
+(puthash "\\:outbox_tray:" "📤" julia-latexsubs)
+(puthash "\\:inbox_tray:" "📥" julia-latexsubs)
+(puthash "\\:package:" "📦" julia-latexsubs)
+(puthash "\\:e-mail:" "📧" julia-latexsubs)
+(puthash "\\:incoming_envelope:" "📨" julia-latexsubs)
+(puthash "\\:envelope_with_arrow:" "📩" julia-latexsubs)
+(puthash "\\:mailbox_closed:" "📪" julia-latexsubs)
+(puthash "\\:mailbox:" "📫" julia-latexsubs)
+(puthash "\\:mailbox_with_mail:" "📬" julia-latexsubs)
+(puthash "\\:mailbox_with_no_mail:" "📭" julia-latexsubs)
+(puthash "\\:postbox:" "📮" julia-latexsubs)
+(puthash "\\:postal_horn:" "📯" julia-latexsubs)
+(puthash "\\:newspaper:" "📰" julia-latexsubs)
+(puthash "\\:iphone:" "📱" julia-latexsubs)
+(puthash "\\:calling:" "📲" julia-latexsubs)
+(puthash "\\:vibration_mode:" "📳" julia-latexsubs)
+(puthash "\\:mobile_phone_off:" "📴" julia-latexsubs)
+(puthash "\\:no_mobile_phones:" "📵" julia-latexsubs)
+(puthash "\\:signal_strength:" "📶" julia-latexsubs)
+(puthash "\\:camera:" "📷" julia-latexsubs)
+(puthash "\\:video_camera:" "📹" julia-latexsubs)
+(puthash "\\:tv:" "📺" julia-latexsubs)
+(puthash "\\:radio:" "📻" julia-latexsubs)
+(puthash "\\:vhs:" "📼" julia-latexsubs)
+(puthash "\\:twisted_rightwards_arrows:" "🔀" julia-latexsubs)
+(puthash "\\:repeat:" "🔁" julia-latexsubs)
+(puthash "\\:repeat_one:" "🔂" julia-latexsubs)
+(puthash "\\:arrows_clockwise:" "🔃" julia-latexsubs)
+(puthash "\\:arrows_counterclockwise:" "🔄" julia-latexsubs)
+(puthash "\\:low_brightness:" "🔅" julia-latexsubs)
+(puthash "\\:high_brightness:" "🔆" julia-latexsubs)
+(puthash "\\:mute:" "🔇" julia-latexsubs)
+(puthash "\\:speaker:" "🔈" julia-latexsubs)
+(puthash "\\:sound:" "🔉" julia-latexsubs)
+(puthash "\\:loud_sound:" "🔊" julia-latexsubs)
+(puthash "\\:battery:" "🔋" julia-latexsubs)
+(puthash "\\:electric_plug:" "🔌" julia-latexsubs)
+(puthash "\\:mag:" "🔍" julia-latexsubs)
+(puthash "\\:mag_right:" "🔎" julia-latexsubs)
+(puthash "\\:lock_with_ink_pen:" "🔏" julia-latexsubs)
+(puthash "\\:closed_lock_with_key:" "🔐" julia-latexsubs)
+(puthash "\\:key:" "🔑" julia-latexsubs)
+(puthash "\\:lock:" "🔒" julia-latexsubs)
+(puthash "\\:unlock:" "🔓" julia-latexsubs)
+(puthash "\\:bell:" "🔔" julia-latexsubs)
+(puthash "\\:no_bell:" "🔕" julia-latexsubs)
+(puthash "\\:bookmark:" "🔖" julia-latexsubs)
+(puthash "\\:link:" "🔗" julia-latexsubs)
+(puthash "\\:radio_button:" "🔘" julia-latexsubs)
+(puthash "\\:back:" "🔙" julia-latexsubs)
+(puthash "\\:end:" "🔚" julia-latexsubs)
+(puthash "\\:on:" "🔛" julia-latexsubs)
+(puthash "\\:soon:" "🔜" julia-latexsubs)
+(puthash "\\:top:" "🔝" julia-latexsubs)
+(puthash "\\:underage:" "🔞" julia-latexsubs)
+(puthash "\\:keycap_ten:" "🔟" julia-latexsubs)
+(puthash "\\:capital_abcd:" "🔠" julia-latexsubs)
+(puthash "\\:abcd:" "🔡" julia-latexsubs)
+(puthash "\\:1234:" "🔢" julia-latexsubs)
+(puthash "\\:symbols:" "🔣" julia-latexsubs)
+(puthash "\\:abc:" "🔤" julia-latexsubs)
+(puthash "\\:fire:" "🔥" julia-latexsubs)
+(puthash "\\:flashlight:" "🔦" julia-latexsubs)
+(puthash "\\:wrench:" "🔧" julia-latexsubs)
+(puthash "\\:hammer:" "🔨" julia-latexsubs)
+(puthash "\\:nut_and_bolt:" "🔩" julia-latexsubs)
+(puthash "\\:hocho:" "🔪" julia-latexsubs)
+(puthash "\\:gun:" "🔫" julia-latexsubs)
+(puthash "\\:microscope:" "🔬" julia-latexsubs)
+(puthash "\\:telescope:" "🔭" julia-latexsubs)
+(puthash "\\:crystal_ball:" "🔮" julia-latexsubs)
+(puthash "\\:six_pointed_star:" "🔯" julia-latexsubs)
+(puthash "\\:beginner:" "🔰" julia-latexsubs)
+(puthash "\\:trident:" "🔱" julia-latexsubs)
+(puthash "\\:black_square_button:" "🔲" julia-latexsubs)
+(puthash "\\:white_square_button:" "🔳" julia-latexsubs)
+(puthash "\\:red_circle:" "🔴" julia-latexsubs)
+(puthash "\\:large_blue_circle:" "🔵" julia-latexsubs)
+(puthash "\\:large_orange_diamond:" "🔶" julia-latexsubs)
+(puthash "\\:large_blue_diamond:" "🔷" julia-latexsubs)
+(puthash "\\:small_orange_diamond:" "🔸" julia-latexsubs)
+(puthash "\\:small_blue_diamond:" "🔹" julia-latexsubs)
+(puthash "\\:small_red_triangle:" "🔺" julia-latexsubs)
+(puthash "\\:small_red_triangle_down:" "🔻" julia-latexsubs)
+(puthash "\\:arrow_up_small:" "🔼" julia-latexsubs)
+(puthash "\\:arrow_down_small:" "🔽" julia-latexsubs)
+(puthash "\\:clock1:" "🕐" julia-latexsubs)
+(puthash "\\:clock2:" "🕑" julia-latexsubs)
+(puthash "\\:clock3:" "🕒" julia-latexsubs)
+(puthash "\\:clock4:" "🕓" julia-latexsubs)
+(puthash "\\:clock5:" "🕔" julia-latexsubs)
+(puthash "\\:clock6:" "🕕" julia-latexsubs)
+(puthash "\\:clock7:" "🕖" julia-latexsubs)
+(puthash "\\:clock8:" "🕗" julia-latexsubs)
+(puthash "\\:clock9:" "🕘" julia-latexsubs)
+(puthash "\\:clock10:" "🕙" julia-latexsubs)
+(puthash "\\:clock11:" "🕚" julia-latexsubs)
+(puthash "\\:clock12:" "🕛" julia-latexsubs)
+(puthash "\\:clock130:" "🕜" julia-latexsubs)
+(puthash "\\:clock230:" "🕝" julia-latexsubs)
+(puthash "\\:clock330:" "🕞" julia-latexsubs)
+(puthash "\\:clock430:" "🕟" julia-latexsubs)
+(puthash "\\:clock530:" "🕠" julia-latexsubs)
+(puthash "\\:clock630:" "🕡" julia-latexsubs)
+(puthash "\\:clock730:" "🕢" julia-latexsubs)
+(puthash "\\:clock830:" "🕣" julia-latexsubs)
+(puthash "\\:clock930:" "🕤" julia-latexsubs)
+(puthash "\\:clock1030:" "🕥" julia-latexsubs)
+(puthash "\\:clock1130:" "🕦" julia-latexsubs)
+(puthash "\\:clock1230:" "🕧" julia-latexsubs)
+(puthash "\\:mount_fuji:" "🗻" julia-latexsubs)
+(puthash "\\:tokyo_tower:" "🗼" julia-latexsubs)
+(puthash "\\:statue_of_liberty:" "🗽" julia-latexsubs)
+(puthash "\\:japan:" "🗾" julia-latexsubs)
+(puthash "\\:moyai:" "🗿" julia-latexsubs)
+(puthash "\\:grinning:" "😀" julia-latexsubs)
+(puthash "\\:grin:" "😁" julia-latexsubs)
+(puthash "\\:joy:" "😂" julia-latexsubs)
+(puthash "\\:smiley:" "😃" julia-latexsubs)
+(puthash "\\:smile:" "😄" julia-latexsubs)
+(puthash "\\:sweat_smile:" "😅" julia-latexsubs)
+(puthash "\\:laughing:" "😆" julia-latexsubs)
+(puthash "\\:innocent:" "😇" julia-latexsubs)
+(puthash "\\:smiling_imp:" "😈" julia-latexsubs)
+(puthash "\\:wink:" "😉" julia-latexsubs)
+(puthash "\\:blush:" "😊" julia-latexsubs)
+(puthash "\\:yum:" "😋" julia-latexsubs)
+(puthash "\\:relieved:" "😌" julia-latexsubs)
+(puthash "\\:heart_eyes:" "😍" julia-latexsubs)
+(puthash "\\:sunglasses:" "😎" julia-latexsubs)
+(puthash "\\:smirk:" "😏" julia-latexsubs)
+(puthash "\\:neutral_face:" "😐" julia-latexsubs)
+(puthash "\\:expressionless:" "😑" julia-latexsubs)
+(puthash "\\:unamused:" "😒" julia-latexsubs)
+(puthash "\\:sweat:" "😓" julia-latexsubs)
+(puthash "\\:pensive:" "😔" julia-latexsubs)
+(puthash "\\:confused:" "😕" julia-latexsubs)
+(puthash "\\:confounded:" "😖" julia-latexsubs)
+(puthash "\\:kissing:" "😗" julia-latexsubs)
+(puthash "\\:kissing_heart:" "😘" julia-latexsubs)
+(puthash "\\:kissing_smiling_eyes:" "😙" julia-latexsubs)
+(puthash "\\:kissing_closed_eyes:" "😚" julia-latexsubs)
+(puthash "\\:stuck_out_tongue:" "😛" julia-latexsubs)
+(puthash "\\:stuck_out_tongue_winking_eye:" "😜" julia-latexsubs)
+(puthash "\\:stuck_out_tongue_closed_eyes:" "😝" julia-latexsubs)
+(puthash "\\:disappointed:" "😞" julia-latexsubs)
+(puthash "\\:worried:" "😟" julia-latexsubs)
+(puthash "\\:angry:" "😠" julia-latexsubs)
+(puthash "\\:rage:" "😡" julia-latexsubs)
+(puthash "\\:cry:" "😢" julia-latexsubs)
+(puthash "\\:persevere:" "😣" julia-latexsubs)
+(puthash "\\:triumph:" "😤" julia-latexsubs)
+(puthash "\\:disappointed_relieved:" "😥" julia-latexsubs)
+(puthash "\\:frowning:" "😦" julia-latexsubs)
+(puthash "\\:anguished:" "😧" julia-latexsubs)
+(puthash "\\:fearful:" "😨" julia-latexsubs)
+(puthash "\\:weary:" "😩" julia-latexsubs)
+(puthash "\\:sleepy:" "😪" julia-latexsubs)
+(puthash "\\:tired_face:" "😫" julia-latexsubs)
+(puthash "\\:grimacing:" "😬" julia-latexsubs)
+(puthash "\\:sob:" "😭" julia-latexsubs)
+(puthash "\\:open_mouth:" "😮" julia-latexsubs)
+(puthash "\\:hushed:" "😯" julia-latexsubs)
+(puthash "\\:cold_sweat:" "😰" julia-latexsubs)
+(puthash "\\:scream:" "😱" julia-latexsubs)
+(puthash "\\:astonished:" "😲" julia-latexsubs)
+(puthash "\\:flushed:" "😳" julia-latexsubs)
+(puthash "\\:sleeping:" "😴" julia-latexsubs)
+(puthash "\\:dizzy_face:" "😵" julia-latexsubs)
+(puthash "\\:no_mouth:" "😶" julia-latexsubs)
+(puthash "\\:mask:" "😷" julia-latexsubs)
+(puthash "\\:smile_cat:" "😸" julia-latexsubs)
+(puthash "\\:joy_cat:" "😹" julia-latexsubs)
+(puthash "\\:smiley_cat:" "😺" julia-latexsubs)
+(puthash "\\:heart_eyes_cat:" "😻" julia-latexsubs)
+(puthash "\\:smirk_cat:" "😼" julia-latexsubs)
+(puthash "\\:kissing_cat:" "😽" julia-latexsubs)
+(puthash "\\:pouting_cat:" "😾" julia-latexsubs)
+(puthash "\\:crying_cat_face:" "😿" julia-latexsubs)
+(puthash "\\:scream_cat:" "🙀" julia-latexsubs)
+(puthash "\\:no_good:" "🙅" julia-latexsubs)
+(puthash "\\:ok_woman:" "🙆" julia-latexsubs)
+(puthash "\\:bow:" "🙇" julia-latexsubs)
+(puthash "\\:see_no_evil:" "🙈" julia-latexsubs)
+(puthash "\\:hear_no_evil:" "🙉" julia-latexsubs)
+(puthash "\\:speak_no_evil:" "🙊" julia-latexsubs)
+(puthash "\\:raising_hand:" "🙋" julia-latexsubs)
+(puthash "\\:raised_hands:" "🙌" julia-latexsubs)
+(puthash "\\:person_frowning:" "🙍" julia-latexsubs)
+(puthash "\\:person_with_pouting_face:" "🙎" julia-latexsubs)
+(puthash "\\:pray:" "🙏" julia-latexsubs)
+(puthash "\\:rocket:" "🚀" julia-latexsubs)
+(puthash "\\:helicopter:" "🚁" julia-latexsubs)
+(puthash "\\:steam_locomotive:" "🚂" julia-latexsubs)
+(puthash "\\:railway_car:" "🚃" julia-latexsubs)
+(puthash "\\:bullettrain_side:" "🚄" julia-latexsubs)
+(puthash "\\:bullettrain_front:" "🚅" julia-latexsubs)
+(puthash "\\:train2:" "🚆" julia-latexsubs)
+(puthash "\\:metro:" "🚇" julia-latexsubs)
+(puthash "\\:light_rail:" "🚈" julia-latexsubs)
+(puthash "\\:station:" "🚉" julia-latexsubs)
+(puthash "\\:tram:" "🚊" julia-latexsubs)
+(puthash "\\:train:" "🚋" julia-latexsubs)
+(puthash "\\:bus:" "🚌" julia-latexsubs)
+(puthash "\\:oncoming_bus:" "🚍" julia-latexsubs)
+(puthash "\\:trolleybus:" "🚎" julia-latexsubs)
+(puthash "\\:busstop:" "🚏" julia-latexsubs)
+(puthash "\\:minibus:" "🚐" julia-latexsubs)
+(puthash "\\:ambulance:" "🚑" julia-latexsubs)
+(puthash "\\:fire_engine:" "🚒" julia-latexsubs)
+(puthash "\\:police_car:" "🚓" julia-latexsubs)
+(puthash "\\:oncoming_police_car:" "🚔" julia-latexsubs)
+(puthash "\\:taxi:" "🚕" julia-latexsubs)
+(puthash "\\:oncoming_taxi:" "🚖" julia-latexsubs)
+(puthash "\\:car:" "🚗" julia-latexsubs)
+(puthash "\\:oncoming_automobile:" "🚘" julia-latexsubs)
+(puthash "\\:blue_car:" "🚙" julia-latexsubs)
+(puthash "\\:truck:" "🚚" julia-latexsubs)
+(puthash "\\:articulated_lorry:" "🚛" julia-latexsubs)
+(puthash "\\:tractor:" "🚜" julia-latexsubs)
+(puthash "\\:monorail:" "🚝" julia-latexsubs)
+(puthash "\\:mountain_railway:" "🚞" julia-latexsubs)
+(puthash "\\:suspension_railway:" "🚟" julia-latexsubs)
+(puthash "\\:mountain_cableway:" "🚠" julia-latexsubs)
+(puthash "\\:aerial_tramway:" "🚡" julia-latexsubs)
+(puthash "\\:ship:" "🚢" julia-latexsubs)
+(puthash "\\:rowboat:" "🚣" julia-latexsubs)
+(puthash "\\:speedboat:" "🚤" julia-latexsubs)
+(puthash "\\:traffic_light:" "🚥" julia-latexsubs)
+(puthash "\\:vertical_traffic_light:" "🚦" julia-latexsubs)
+(puthash "\\:construction:" "🚧" julia-latexsubs)
+(puthash "\\:rotating_light:" "🚨" julia-latexsubs)
+(puthash "\\:triangular_flag_on_post:" "🚩" julia-latexsubs)
+(puthash "\\:door:" "🚪" julia-latexsubs)
+(puthash "\\:no_entry_sign:" "🚫" julia-latexsubs)
+(puthash "\\:smoking:" "🚬" julia-latexsubs)
+(puthash "\\:no_smoking:" "🚭" julia-latexsubs)
+(puthash "\\:put_litter_in_its_place:" "🚮" julia-latexsubs)
+(puthash "\\:do_not_litter:" "🚯" julia-latexsubs)
+(puthash "\\:potable_water:" "🚰" julia-latexsubs)
+(puthash "\\:non-potable_water:" "🚱" julia-latexsubs)
+(puthash "\\:bike:" "🚲" julia-latexsubs)
+(puthash "\\:no_bicycles:" "🚳" julia-latexsubs)
+(puthash "\\:bicyclist:" "🚴" julia-latexsubs)
+(puthash "\\:mountain_bicyclist:" "🚵" julia-latexsubs)
+(puthash "\\:walking:" "🚶" julia-latexsubs)
+(puthash "\\:no_pedestrians:" "🚷" julia-latexsubs)
+(puthash "\\:children_crossing:" "🚸" julia-latexsubs)
+(puthash "\\:mens:" "🚹" julia-latexsubs)
+(puthash "\\:womens:" "🚺" julia-latexsubs)
+(puthash "\\:restroom:" "🚻" julia-latexsubs)
+(puthash "\\:baby_symbol:" "🚼" julia-latexsubs)
+(puthash "\\:toilet:" "🚽" julia-latexsubs)
+(puthash "\\:wc:" "🚾" julia-latexsubs)
+(puthash "\\:shower:" "🚿" julia-latexsubs)
+(puthash "\\:bath:" "🛀" julia-latexsubs)
+(puthash "\\:bathtub:" "🛁" julia-latexsubs)
+(puthash "\\:passport_control:" "🛂" julia-latexsubs)
+(puthash "\\:customs:" "🛃" julia-latexsubs)
+(puthash "\\:baggage_claim:" "🛄" julia-latexsubs)
+(puthash "\\:left_luggage:" "🛅" julia-latexsubs)
--- /dev/null
+;;; julia-mode-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
+\f
+;;;### (autoloads nil "julia-mode" "julia-mode.el" (23997 31535 905690
+;;;;;; 231000))
+;;; Generated autoloads from julia-mode.el
+
+(add-to-list 'auto-mode-alist '("\\.jl\\'" . julia-mode))
+
+(autoload 'julia-mode "julia-mode" "\
+Major mode for editing julia code.
+
+\(fn)" t nil)
+
+(autoload 'inferior-julia "julia-mode" "\
+Run an inferior instance of `julia' inside Emacs.
+
+\(fn)" t nil)
+
+(defalias 'run-julia #'inferior-julia "\
+Run an inferior instance of `julia' inside Emacs.")
+
+;;;***
+\f
+;;;### (autoloads nil nil ("julia-latexsubs.el" "julia-mode-pkg.el")
+;;;;;; (23997 31535 909479 733000))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; julia-mode-autoloads.el ends here
--- /dev/null
+(define-package "julia-mode" "20191002.1352" "Major mode for editing Julia source code" 'nil :keywords
+ '("languages")
+ :url "https://github.com/JuliaLang/julia")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; julia-mode.el --- Major mode for editing Julia source code
+
+;; Copyright (C) 2009-2014 Julia contributors
+;; URL: https://github.com/JuliaLang/julia
+;; Version: 0.3
+;; Keywords: languages
+
+;;; Usage:
+;; Put the following code in your .emacs, site-load.el, or other relevant file
+;; (add-to-list 'load-path "path-to-julia-mode")
+;; (require 'julia-mode)
+
+;;; Commentary:
+;; This is the official Emacs mode for editing Julia programs.
+
+;;; License:
+;; Permission is hereby granted, free of charge, to any person obtaining
+;; a copy of this software and associated documentation files (the
+;; "Software"), to deal in the Software without restriction, including
+;; without limitation the rights to use, copy, modify, merge, publish,
+;; distribute, sublicense, and/or sell copies of the Software, and to
+;; permit persons to whom the Software is furnished to do so, subject to
+;; the following conditions:
+;;
+;; The above copyright notice and this permission notice shall be
+;; included in all copies or substantial portions of the Software.
+;;
+;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+;; LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+;; OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+;; WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+;;; Code:
+
+;; We can't use cl-lib whilst supporting Emacs 23 users who don't use
+;; ELPA.
+(with-no-warnings
+ (require 'cl)) ;; incf, decf, plusp
+
+(defvar julia-mode-hook nil)
+
+(defgroup julia ()
+ "Major mode for the julia programming language."
+ :group 'languages
+ :prefix "julia-")
+
+(defcustom julia-indent-offset 4
+ "Number of spaces per indentation level."
+ :type 'integer
+ :group 'julia)
+
+(defface julia-macro-face
+ '((t :inherit font-lock-preprocessor-face))
+ "Face for Julia macro invocations."
+ :group 'julia-mode)
+
+(defface julia-quoted-symbol-face
+ '((t :inherit font-lock-preprocessor-face))
+ "Face for quoted Julia symbols, e.g. :foo."
+ :group 'julia-mode)
+
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.jl\\'" . julia-mode))
+
+;; define ignore-errors macro if it isn't present
+;; (necessary for emacs 22 compatibility)
+(when (not (fboundp 'ignore-errors))
+ (defmacro ignore-errors (body) `(condition-case nil ,body (error nil))))
+
+(defun julia--regexp-opt (strings &optional paren)
+ "Emacs 23 provides `regexp-opt', but it does not support PAREN taking the value 'symbols.
+This function provides equivalent functionality, but makes no efforts to optimise the regexp."
+ (cond
+ ((>= emacs-major-version 24)
+ (regexp-opt strings paren))
+ ((not (eq paren 'symbols))
+ (regexp-opt strings paren))
+ ((null strings)
+ "")
+ ('t
+ (rx-to-string `(seq symbol-start (or ,@strings) symbol-end)))))
+
+(defvar julia-mode-syntax-table
+ (let ((table (make-syntax-table)))
+ (modify-syntax-entry ?_ "_" table)
+ (modify-syntax-entry ?@ "_" table)
+ (modify-syntax-entry ?! "_" table)
+ (modify-syntax-entry ?# "< 14" table) ; # single-line and multiline start
+ (modify-syntax-entry ?= ". 23bn" table)
+ (modify-syntax-entry ?\n ">" table) ; \n single-line comment end
+ (modify-syntax-entry ?\{ "(} " table)
+ (modify-syntax-entry ?\} "){ " table)
+ (modify-syntax-entry ?\[ "(] " table)
+ (modify-syntax-entry ?\] ")[ " table)
+ (modify-syntax-entry ?\( "() " table)
+ (modify-syntax-entry ?\) ")( " table)
+ ;; Here, we treat ' as punctuation (when it's used for transpose),
+ ;; see our use of `julia-char-regex' for handling ' as a character
+ ;; delimiter
+ (modify-syntax-entry ?' "." table)
+ (modify-syntax-entry ?\" "\"" table)
+ (modify-syntax-entry ?` "\"" table)
+ (modify-syntax-entry ?\\ "\\" table)
+
+ (modify-syntax-entry ?. "." table)
+ (modify-syntax-entry ?? "." table)
+ (modify-syntax-entry ?$ "." table)
+ (modify-syntax-entry ?& "." table)
+ (modify-syntax-entry ?* "." table)
+ (modify-syntax-entry ?/ "." table)
+ (modify-syntax-entry ?+ "." table)
+ (modify-syntax-entry ?- "." table)
+ (modify-syntax-entry ?< "." table)
+ (modify-syntax-entry ?> "." table)
+ (modify-syntax-entry ?% "." table)
+
+ (modify-syntax-entry ?′ "w" table) ; \prime is a word constituent
+ table)
+ "Syntax table for `julia-mode'.")
+
+(eval-when-compile
+ (defconst julia-char-regex
+ (rx (or (any "-" ";" "\\" "^" "!" "|" "?" "*" "<" "%" "," "=" ">" "+" "/" "&" "$" "~" ":")
+ (syntax open-parenthesis)
+ (syntax whitespace)
+ bol)
+ (group "'")
+ (group
+ (or (repeat 0 8 (not (any "'"))) (not (any "\\"))
+ "\\\\"))
+ (group "'"))))
+
+(defconst julia-hanging-operator-regexp
+ ;; taken from julia-parser.scm
+ (concat "^[^#\n]+ "
+ (regexp-opt
+ '( ;; conditional
+ "?"
+ ;; assignment
+ "=" ":=" "+=" "-=" "*=" "/=" "//=" ".//=" ".*=" "./=" "\\=" ".\\="
+ "^=" ".^=" "÷=" ".÷=" "%=" ".%=" "|=" "&=" "$=" "=>" "<<=" ">>="
+ ">>>=" "~" ".+=" ".-="
+ ;; arrow
+ "--" "-->" "←" "→" "↔" "↚" "↛" "↠" "↣" "↦" "↮" "⇎" "⇏" "⇒" "⇔" "⇴"
+ "⇶" "⇷" "⇸" "⇹" "⇺" "⇻" "⇼" "⇽" "⇾" "⇿" "⟵" "⟶" "⟷" "⟷" "⟹"
+ "⟺" "⟻" "⟼" "⟽" "⟾" "⟿" "⤀" "⤁" "⤂" "⤃" "⤄" "⤅" "⤆" "⤇" "⤌"
+ "⤍" "⤎" "⤏" "⤐" "⤑" "⤔" "⤕" "⤖" "⤗" "⤘" "⤝" "⤞" "⤟" "⤠" "⥄" "⥅"
+ "⥆" "⥇" "⥈" "⥊" "⥋" "⥎" "⥐" "⥒" "⥓" "⥖" "⥗" "⥚" "⥛" "⥞" "⥟" "⥢"
+ "⥤" "⥦" "⥧" "⥨" "⥩" "⥪" "⥫" "⥬" "⥭" "⥰" "⧴" "⬱" "⬰" "⬲" "⬳" "⬴"
+ "⬵" "⬶" "⬷" "⬸" "⬹" "⬺" "⬻" "⬼" "⬽" "⬾" "⬿" "⭀" "⭁" "⭂" "⭃" "⭄"
+ "⭇" "⭈" "⭉" "⭊" "⭋" "⭌" "←" "→"
+ ;; or and and
+ "&&" "||"
+ ;; comparison
+ ">" "<" ">=" "≥" "<=" "≤" "==" "===" "≡" "!=" "≠" "!==" "≢" ".>"
+ ".<" ".>=" ".≥" ".<=" ".≤" ".==" ".!=" ".≠" ".=" ".!" "<:" ">:" "∈"
+ "∉" "∋" "∌" "⊆" "⊈" "⊂" "⊄" "⊊" "∝" "∊" "∍" "∥" "∦" "∷" "∺" "∻" "∽"
+ "∾" "≁" "≃" "≄" "≅" "≆" "≇" "≈" "≉" "≊" "≋" "≌" "≍" "≎" "≐" "≑" "≒"
+ "≓" "≔" "≕" "≖" "≗" "≘" "≙" "≚" "≛" "≜" "≝" "≞" "≟" "≣" "≦" "≧" "≨"
+ "≩" "≪" "≫" "≬" "≭" "≮" "≯" "≰" "≱" "≲" "≳" "≴" "≵" "≶" "≷" "≸" "≹"
+ "≺" "≻" "≼" "≽" "≾" "≿" "⊀" "⊁" "⊃" "⊅" "⊇" "⊉" "⊋" "⊏" "⊐" "⊑" "⊒"
+ "⊜" "⊩" "⊬" "⊮" "⊰" "⊱" "⊲" "⊳" "⊴" "⊵" "⊶" "⊷" "⋍" "⋐" "⋑" "⋕" "⋖"
+ "⋗" "⋘" "⋙" "⋚" "⋛" "⋜" "⋝" "⋞" "⋟" "⋠" "⋡" "⋢" "⋣" "⋤" "⋥" "⋦" "⋧"
+ "⋨" "⋩" "⋪" "⋫" "⋬" "⋭" "⋲" "⋳" "⋴" "⋵" "⋶" "⋷" "⋸" "⋹" "⋺" "⋻" "⋼"
+ "⋽" "⋾" "⋿" "⟈" "⟉" "⟒" "⦷" "⧀" "⧁" "⧡" "⧣" "⧤" "⧥" "⩦" "⩧" "⩪" "⩫"
+ "⩬" "⩭" "⩮" "⩯" "⩰" "⩱" "⩲" "⩳" "⩴" "⩵" "⩶" "⩷" "⩸" "⩹" "⩺" "⩻" "⩼"
+ "⩽" "⩾" "⩿" "⪀" "⪁" "⪂" "⪃" "⪄" "⪅" "⪆" "⪇" "⪈" "⪉" "⪊" "⪋" "⪌" "⪍"
+ "⪎" "⪏" "⪐" "⪑" "⪒" "⪓" "⪔" "⪕" "⪖" "⪗" "⪘" "⪙" "⪚" "⪛" "⪜" "⪝" "⪞"
+ "⪟" "⪠" "⪡" "⪢" "⪣" "⪤" "⪥" "⪦" "⪧" "⪨" "⪩" "⪪" "⪫" "⪬" "⪭" "⪮" "⪯"
+ "⪰" "⪱" "⪲" "⪳" "⪴" "⪵" "⪶" "⪷" "⪸" "⪹" "⪺" "⪻" "⪼" "⪽" "⪾" "⪿" "⫀"
+ "⫁" "⫂" "⫃" "⫄" "⫅" "⫆" "⫇" "⫈" "⫉" "⫊" "⫋" "⫌" "⫍" "⫎" "⫏" "⫐" "⫑"
+ "⫒" "⫓" "⫔" "⫕" "⫖" "⫗" "⫘" "⫙" "⫷" "⫸" "⫹" "⫺" "⊢" "⊣"
+ ;; pipe, colon
+ "|>" "<|" ":" ".."
+ ;; plus
+ "+" "-" "⊕" "⊖" "⊞" "⊟" ".+" ".-" "++" "|" "∪" "∨" "$" "⊔" "±" "∓"
+ "∔" "∸" "≂" "≏" "⊎" "⊻" "⊽" "⋎" "⋓" "⧺" "⧻" "⨈" "⨢" "⨣" "⨤" "⨥" "⨦"
+ "⨧" "⨨" "⨩" "⨪" "⨫" "⨬" "⨭" "⨮" "⨹" "⨺" "⩁" "⩂" "⩅" "⩊" "⩌" "⩏" "⩐"
+ "⩒" "⩔" "⩖" "⩗" "⩛" "⩝" "⩡" "⩢" "⩣"
+ ;; bitshift
+ "<<" ">>" ">>>" ".<<" ".>>" ".>>>"
+ ;; times
+ "*" "/" "./" "÷" ".÷" "%" "⋅" "∘" "×" ".%" ".*" "\\"
+ ".\\" "&" "∩" "∧" "⊗" "⊘" "⊙" "⊚" "⊛" "⊠" "⊡" "⊓" "∗" "∙" "∤" "⅋"
+ "≀" "⊼" "⋄" "⋆" "⋇" "⋉" "⋊" "⋋" "⋌" "⋏" "⋒" "⟑" "⦸" "⦼" "⦾" "⦿" "⧶"
+ "⧷" "⨇" "⨰" "⨱" "⨲" "⨳" "⨴" "⨵" "⨶" "⨷" "⨸" "⨻" "⨼" "⨽" "⩀" "⩃" "⩄"
+ "⩋" "⩍" "⩎" "⩑" "⩓" "⩕" "⩘" "⩚" "⩜" "⩞" "⩟" "⩠" "⫛" "⊍" "▷" "⨝" "⟕"
+ "⟖" "⟗"
+ ;; rational
+ "//" ".//"
+ ;; power
+ "^" ".^" "↑" "↓" "⇵" "⟰" "⟱" "⤈" "⤉" "⤊" "⤋" "⤒" "⤓" "⥉" "⥌" "⥍"
+ "⥏" "⥑" "⥔" "⥕" "⥘" "⥙" "⥜" "⥝" "⥠" "⥡" "⥣" "⥥" "⥮" "⥯" "↑" "↓"
+ ;; decl, dot
+ "::" "."))
+ (regexp-opt '(" #" " \n" "#" "\n"))))
+
+(defconst julia-triple-quoted-string-regex
+ ;; We deliberately put a group on the first and last delimiter, so
+ ;; we can mark these as string delimiters for font-lock.
+ (rx (group "\"")
+ (group "\"\""
+ ;; After the delimiter, we're a sequence of
+ ;; non-backslashes or blackslashes paired with something.
+ (*? (or (not (any "\\"))
+ (seq "\\" anything)))
+ "\"\"")
+ (group "\"")))
+
+(defconst julia-unquote-regex
+ "\\(\\s(\\|\\s-\\|-\\|[,%=<>\\+*/?&|!\\^~\\\\;:]\\|^\\)\\($[a-zA-Z0-9_]+\\)")
+
+(defconst julia-forloop-in-regex
+ "for +.*[^
+].* \\(in\\)\\(\\s-\\|$\\)+")
+
+(defconst julia-function-regex
+ (rx line-start (* (or space "@inline" "@noinline")) symbol-start
+ "function"
+ (1+ space)
+ ;; Don't highlight module names in function declarations:
+ (* (seq (1+ (or word (syntax symbol))) "."))
+ ;; The function name itself
+ (group (1+ (or word (syntax symbol))))))
+
+(defconst julia-function-assignment-regex
+ (rx line-start (* (or space "@inline" "@noinline")) symbol-start
+ (* (seq (1+ (or word (syntax symbol))) ".")) ; module name
+ (group (1+ (or word (syntax symbol))))
+ (? "{" (* (not (any "}"))) "}")
+ "(" (* (or
+ (seq "(" (* (not (any "(" ")"))) ")")
+ (not (any "(" ")"))))
+ ")"
+ (* space)
+ (? "::" (* space) (1+ (not (any space))))
+ (* space)
+ (* (seq "where" (or "{" (+ space)) (+ (not (any "=")))))
+ "="
+ (not (any "="))))
+
+(defconst julia-type-regex
+ (rx symbol-start (or ;;"immutable" "type" ;; remove after 0.6
+ "abstract type" "primitive type" "struct" "mutable struct")
+ (1+ space) (group (1+ (or word (syntax symbol))))))
+
+(defconst julia-type-annotation-regex
+ (rx "::" (0+ space) (group (1+ (or word (syntax symbol))))))
+
+;;(defconst julia-type-parameter-regex
+;; (rx symbol-start (1+ (or (or word (syntax symbol)) ?_)) "{" (group (1+ (or (or word (syntax symbol)) ?_))) "}"))
+
+(defconst julia-subtype-regex
+ (rx "<:" (0+ space) (group (1+ (or word (syntax symbol)))) (0+ space) (or "\n" "{" "}" "end")))
+
+(defconst julia-macro-regex
+ (rx symbol-start (group "@" (1+ (or word (syntax symbol))))))
+
+(defconst julia-keyword-regex
+ (julia--regexp-opt
+ '("if" "else" "elseif" "while" "for" "begin" "end" "quote"
+ "try" "catch" "return" "local" "function" "macro" "ccall"
+ "finally" "break" "continue" "global" "where"
+ "module" "using" "import" "export" "const" "let" "do" "in"
+ "baremodule"
+ ;; "importall" ;; deprecated in 0.7
+ ;; "immutable" "type" "bitstype" "abstract" "typealias" ;; removed in 1.0
+ "abstract type" "primitive type" "struct" "mutable struct")
+ 'symbols))
+
+(defconst julia-builtin-regex
+ (julia--regexp-opt
+ ;;'("error" "throw")
+ '()
+ 'symbols))
+
+(defconst julia-builtin-types-regex
+ (julia--regexp-opt
+ '("Number" "Real" "BigInt" "Integer"
+ "UInt" "UInt8" "UInt16" "UInt32" "UInt64" "UInt128"
+ "Int" "Int8" "Int16" "Int32" "Int64" "Int128"
+ "BigFloat" "AbstractFloat" "Float16" "Float32" "Float64"
+ ;;"Complex128" "Complex64" ;; replaced in 1.0
+ "ComplexF32" "ComplexF64"
+ "Bool"
+ "Cuchar" "Cshort" "Cushort" "Cint" "Cuint" "Clonglong" "Culonglong" "Cintmax_t" "Cuintmax_t"
+ "Cfloat" "Cdouble" "Cptrdiff_t" "Cssize_t" "Csize_t"
+ "Cchar" "Clong" "Culong" "Cwchar_t" "Cvoid"
+ "Cstring" "Cwstring" ;; C strings made of ordinary and wide characters
+ "Char" "String" "SubString"
+ "Array" "DArray" "AbstractArray" "AbstractVector" "AbstractMatrix" "AbstractSparseMatrix" "SubArray" "StridedArray" "StridedVector" "StridedMatrix" "VecOrMat" "StridedVecOrMat" "DenseArray" "SparseMatrixCSC" "BitArray"
+ "AbstractRange" "OrdinalRange" "StepRange" "UnitRange" "FloatRange"
+ "Tuple" "NTuple" "Vararg"
+ "DataType" "Symbol" "Function" "Vector" "Matrix" "Union" "Type" "Any" "Complex" "AbstractString" "Ptr" "Nothing" "Exception" "Task" "Signed" "Unsigned" "AbstractDict" "Dict" "IO" "IOStream" "Rational" "Regex" "RegexMatch" "Set" "BitSet" "Expr" "WeakRef" "ObjectIdDict"
+ "AbstractRNG" "MersenneTwister"
+ )
+ 'symbols))
+
+(defconst julia-quoted-symbol-regex
+ ;; :foo and :foo2 are valid, but :123 is not.
+ (rx (or bol whitespace "(" "[" "," "=")
+ (group ":" (or letter (syntax symbol)) (0+ (or word (syntax symbol))))))
+
+(defconst julia-font-lock-keywords
+ (list
+ ;; Ensure :: and <: aren't highlighted, so we don't confuse ::Foo with :foo.
+ ;; (in Emacs, keywords don't overlap).
+ (cons (rx (or "::" "<:")) ''default)
+ ;; Highlight quoted symbols before keywords, so :function is not
+ ;; highlighted as a keyword.
+ (list julia-quoted-symbol-regex 1 ''julia-quoted-symbol-face)
+ (cons julia-builtin-types-regex 'font-lock-type-face)
+ (cons julia-keyword-regex 'font-lock-keyword-face)
+ (cons julia-macro-regex ''julia-macro-face)
+ (cons
+ (julia--regexp-opt
+ '("true" "false" "C_NULL" "Inf" "NaN" "Inf32" "NaN32" "nothing" "undef")
+ 'symbols)
+ 'font-lock-constant-face)
+ (list julia-unquote-regex 2 'font-lock-constant-face)
+ (list julia-forloop-in-regex 1 'font-lock-keyword-face)
+ (list julia-function-regex 1 'font-lock-function-name-face)
+ (list julia-function-assignment-regex 1 'font-lock-function-name-face)
+ (list julia-type-regex 1 'font-lock-type-face)
+ (list julia-type-annotation-regex 1 'font-lock-type-face)
+ ;;(list julia-type-parameter-regex 1 'font-lock-type-face)
+ (list julia-subtype-regex 1 'font-lock-type-face)
+ (list julia-builtin-regex 1 'font-lock-builtin-face)
+ ))
+
+(defconst julia-block-start-keywords
+ (list "if" "while" "for" "begin" "try" "function" "let" "macro"
+ "quote" "do" "module"
+ ;; "immutable" "type" ;; remove after 0.6
+ "abstract type" "primitive type" "struct" "mutable struct"))
+
+;; For keywords that begin a block without additional indentation
+(defconst julia-block-start-keywords-no-indent
+ (list "module"))
+
+(defconst julia-block-end-keywords
+ (list "end" "else" "elseif" "catch" "finally"))
+
+(defun julia-stringify-triple-quote ()
+ "Put `syntax-table' property on triple-quoted string delimiters.
+
+Based on `python-syntax-stringify'."
+ (let* ((string-start-pos (- (point) 3))
+ (string-end-pos (point))
+ (ppss (prog2
+ (backward-char 3)
+ (syntax-ppss)
+ (forward-char 3)))
+ (in-comment (nth 4 ppss))
+ (in-string (nth 8 ppss)))
+ (unless in-comment
+ (if in-string
+ ;; We're in a string, so this must be the closing triple-quote.
+ ;; Put | on the last " character.
+ (put-text-property (1- string-end-pos) string-end-pos
+ 'syntax-table (string-to-syntax "|"))
+ ;; We're not in a string, so this is the opening triple-quote.
+ ;; Put | on the first " character.
+ (put-text-property string-start-pos (1+ string-start-pos)
+ 'syntax-table (string-to-syntax "|"))))))
+
+(defconst julia-syntax-propertize-function
+ (unless (< emacs-major-version 24)
+ (syntax-propertize-rules
+ ("\"\"\""
+ (0 (ignore (julia-stringify-triple-quote))))
+ (julia-char-regex
+ (1 "\"") ; Treat ' as a string delimiter.
+ (2 ".") ; Don't highlight anything between.
+ (3 "\""))))) ; Treat the last " in """ as a string delimiter.
+
+(defun julia-in-comment (&optional syntax-ppss)
+ "Return non-nil if point is inside a comment using SYNTAX-PPSS.
+Handles both single-line and multi-line comments."
+ (nth 4 (or syntax-ppss (syntax-ppss))))
+
+(defun julia-in-string (&optional syntax-ppss)
+ "Return non-nil if point is inside a string using SYNTAX-PPSS.
+Note this is Emacs' notion of what is highlighted as a string.
+As a result, it is true inside \"foo\", `foo` and 'f'."
+ (nth 3 (or syntax-ppss (syntax-ppss))))
+
+(defun julia-in-brackets ()
+ "Return non-nil if point is inside square brackets."
+ (let ((start-pos (point))
+ (open-count 0))
+ ;; Count all the [ and ] characters on the current line.
+ (save-excursion
+ (beginning-of-line)
+
+ (while (< (point) start-pos)
+ ;; Don't count [ or ] inside strings, characters or comments.
+ (unless (or (julia-in-string) (julia-in-comment))
+
+ (when (looking-at (rx "["))
+ (incf open-count))
+ (when (looking-at (rx "]"))
+ (decf open-count)))
+
+ (forward-char 1)))
+
+ ;; If we've opened more than we've closed, we're inside brackets.
+ (plusp open-count)))
+
+(defun julia-at-keyword (kw-list)
+ "Return the word at point if it matches any keyword in KW-LIST.
+KW-LIST is a list of strings. The word at point is not considered
+a keyword if used as a field name, X.word, or quoted, :word."
+ (and (or (= (point) 1)
+ (and (not (equal (char-before (point)) ?.))
+ (not (equal (char-before (point)) ?:))))
+ (not (looking-at "(")) ; handle "function(" when on (
+ (member (current-word t) kw-list)
+ ;; 'end' is not a keyword when used for indexing, e.g. foo[end-2]
+ (or (not (equal (current-word t) "end"))
+ (not (julia-in-brackets)))
+ (not (julia-in-comment))))
+
+;; if backward-sexp gives an error, move back 1 char to move over the '('
+(defun julia-safe-backward-sexp ()
+ (if (condition-case nil (backward-sexp) (error t))
+ (ignore-errors (backward-char))))
+
+(defun julia-following-import-export-using ()
+ "If the current line follows an `export` or `import` keyword
+with valid syntax, return the position of the keyword, otherwise
+`nil`. Works by stepping backwards through comma-separated
+symbol, gives up when this is not true."
+ ;; Implementation accepts a single Module: right after the keyword, and saves
+ ;; the module name for future use, but does not enforce that `export` has no
+ ;; module name.
+ (let ((done nil) ; find keyword or give up
+ (module nil)) ; found "Module:"
+ (save-excursion
+ (beginning-of-line)
+ (while (and (not done) (< (point-min) (point)))
+ (julia-safe-backward-sexp)
+ (cond
+ ((looking-at (rx (or "import" "export" "using")))
+ (setf done (point)))
+ ((looking-at (rx (group (* (or word (syntax symbol)))) (0+ space) ":"))
+ (if module
+ (setf done 'broken)
+ (setf module (match-string-no-properties 1))))
+ ((looking-at (rx (* (or word (syntax symbol))) (0+ space) ","))
+ (when module (setf done 'broken)))
+ (t (setf done 'broken)))))
+ (if (eq done 'broken)
+ nil
+ done)))
+
+(defun julia-last-open-block-pos (min)
+ "Return the position of the last open block, if one found.
+Do not move back beyond position MIN."
+ (save-excursion
+ (let ((count 0))
+ (while (not (or (> count 0) (<= (point) min)))
+ (julia-safe-backward-sexp)
+ (setq count
+ (cond ((julia-at-keyword julia-block-start-keywords)
+ (+ count 1))
+ ((and (equal (current-word t) "end")
+ (not (julia-in-comment)))
+ (- count 1))
+ (t count))))
+ (if (> count 0)
+ (point)
+ nil))))
+
+(defun julia-last-open-block (min)
+ "Move back and return indentation level for last open block.
+Do not move back beyond MIN."
+ ;; Ensure MIN is not before the start of the buffer.
+ (setq min (max min (point-min)))
+ (let ((pos (julia-last-open-block-pos min)))
+ (and pos
+ (progn
+ (goto-char pos)
+ (+ julia-indent-offset (current-indentation))))))
+
+(defsubst julia--safe-backward-char ()
+ "Move back one character, but don't error if we're at the
+beginning of the buffer."
+ (unless (eq (point) (point-min))
+ (backward-char)))
+
+(defcustom julia-max-block-lookback 5000
+ "When indenting, don't look back more than this
+many characters to see if there are unclosed blocks.
+
+This variable has a moderate effect on indent performance if set too
+high, but stops indenting in the middle of long blocks if set too low."
+ :type 'integer
+ :group 'julia)
+
+(defun julia-paren-indent ()
+ "Return the column of the text following the innermost
+containing paren before point, so we can align succeeding code
+with it. Returns nil if we're not within nested parens."
+ (save-excursion
+ (beginning-of-line)
+ (let ((parser-state (syntax-ppss)))
+ (cond ((nth 3 parser-state) nil) ;; strings
+ ((= (nth 0 parser-state) 0) nil) ;; top level
+ (t
+ (ignore-errors ;; return nil if any of these movements fail
+ (beginning-of-line)
+ (skip-syntax-forward " ")
+ (let ((possibly-close-paren-point (point)))
+ (backward-up-list)
+ (let ((open-paren-point (point)))
+ (forward-char)
+ (skip-syntax-forward " ")
+ (if (eolp)
+ (progn
+ (up-list)
+ (backward-char)
+ (let ((paren-closed (= (point) possibly-close-paren-point)))
+ (goto-char open-paren-point)
+ (beginning-of-line)
+ (skip-syntax-forward " ")
+ (+ (current-column)
+ (if paren-closed
+ 0
+ julia-indent-offset))))
+ (current-column))))))))))
+
+(defun julia-prev-line-skip-blank-or-comment ()
+ "Move point to beginning of previous line skipping blank lines
+and lines including only comments. Returns number of lines moved.
+A return of -1 signals that we moved to the first line of
+the (possibly narrowed) buffer, so there is nowhere else to go."
+ (catch 'result
+ (let ((moved 0) this-move)
+ (while t
+ (setq this-move (forward-line -1))
+ (cond
+ ;; moved into comment or blank
+ ((and (= 0 this-move)
+ (or (looking-at-p "^\\s-*\\(?:#.*\\)*$")
+ (julia-in-comment)))
+ (incf moved))
+ ;; success
+ ((= 0 this-move)
+ (throw 'result (1+ moved)))
+ ;; on first line and in comment
+ ((and (bobp)
+ (or (looking-at-p "^\\s-*\\(?:#.*\\)*$")
+ (julia-in-comment)))
+ (throw 'result -1))
+ ((bobp)
+ (throw 'result moved))
+ (t
+ (throw 'result 0)))))))
+
+(defun julia-indent-hanging ()
+ "Calculate indentation for lines that follow \"hanging\"
+operators (operators that end the previous line) as defined in
+`julia-hanging-operator-regexp'. An assignment operator ending
+the previous line increases the indent as do the other operators
+unless another operator is found two lines up. Previous line
+means previous line after skipping blank lines and lines with
+only comments."
+ (let (prev-indent)
+ (save-excursion
+ (when (> (julia-prev-line-skip-blank-or-comment) 0)
+ (setq prev-indent (current-indentation))
+ (when (looking-at-p julia-hanging-operator-regexp)
+ (if (and (> (julia-prev-line-skip-blank-or-comment) 0)
+ (looking-at-p julia-hanging-operator-regexp))
+ ;; two preceding hanging operators => indent same as line
+ ;; above
+ prev-indent
+ ;; one preceding hanging operator => increase indent from line
+ ;; above
+ (+ julia-indent-offset prev-indent)))))))
+
+(defun julia-indent-in-string ()
+ "Indentation inside strings with newlines is \"manual\",
+meaning always increase indent on TAB and decrease on S-TAB."
+ (save-excursion
+ (beginning-of-line)
+ (when (julia-in-string)
+ (if (member this-command '(julia-latexsub-or-indent
+ ess-indent-or-complete))
+ (+ julia-indent-offset (current-indentation))
+ ;; return the current indentation to prevent other functions from
+ ;; indenting inside strings
+ (current-indentation)))))
+
+(defun julia-indent-import-export-using ()
+ "Indent offset for lines that follow `import` or `export`, otherwise nil."
+ (when (julia-following-import-export-using)
+ julia-indent-offset))
+
+(defun julia-indent-line ()
+ "Indent current line of julia code."
+ (interactive)
+ (let* ((point-offset (- (current-column) (current-indentation))))
+ (indent-line-to
+ (or
+ ;; note: if this first function returns nil the beginning of the line
+ ;; cannot be in a string
+ (julia-indent-in-string)
+ ;; If we're inside an open paren, indent to line up arguments. After this,
+ ;; we cannot be inside parens which includes brackets
+ (julia-paren-indent)
+ ;; indent due to hanging operators (lines ending in an operator)
+ (julia-indent-hanging)
+ ;; indent for import and export
+ (julia-indent-import-export-using)
+ ;; Indent according to how many nested blocks we are in.
+ (save-excursion
+ (beginning-of-line)
+ ;; jump out of any comments
+ (let ((state (syntax-ppss)))
+ (when (nth 4 state)
+ (goto-char (nth 8 state))))
+ (forward-to-indentation 0)
+ (let ((endtok (julia-at-keyword julia-block-end-keywords))
+ (last-open-block (julia-last-open-block (- (point) julia-max-block-lookback))))
+ (max 0 (+ (or last-open-block 0)
+ (if (or endtok
+ (julia-at-keyword julia-block-start-keywords-no-indent))
+ (- julia-indent-offset) 0)))))))
+ ;; Point is now at the beginning of indentation, restore it
+ ;; to its original position (relative to indentation).
+ (when (>= point-offset 0)
+ (move-to-column (+ (current-indentation) point-offset)))))
+
+(defalias 'julia-mode-prog-mode
+ (if (fboundp 'prog-mode)
+ 'prog-mode
+ 'fundamental-mode))
+
+\f
+;;; Navigation
+;; based off python.el
+(defconst julia-beginning-of-defun-regex
+ (concat julia-function-regex "\\|"
+ julia-function-assignment-regex "\\|"
+ "\\_<macro\\_>")
+ "Regex matching beginning of Julia function or macro.")
+
+(defun julia-syntax-context-type (&optional syntax-ppss)
+ "Return the context type using SYNTAX-PPSS.
+TYPE can be `comment', `string' or `paren'."
+ (let ((ppss (or syntax-ppss (syntax-ppss))))
+ (cond
+ ((nth 8 ppss) (if (nth 4 ppss) 'comment 'string))
+ ((nth 1 ppss) 'paren))))
+
+(defsubst julia-syntax-comment-or-string-p (&optional syntax-ppss)
+ "Return non-nil if SYNTAX-PPSS is inside string or comment."
+ (nth 8 (or syntax-ppss (syntax-ppss))))
+
+(defun julia-looking-at-beginning-of-defun (&optional syntax-ppss)
+ "Check if point is at `beginning-of-defun' using SYNTAX-PPSS."
+ (and (not (julia-syntax-comment-or-string-p (or syntax-ppss (syntax-ppss))))
+ (save-excursion
+ (beginning-of-line 1)
+ (looking-at julia-beginning-of-defun-regex))))
+
+(defun julia--beginning-of-defun (&optional arg)
+ "Internal implementation of `julia-beginning-of-defun'.
+With positive ARG search backwards, else search forwards."
+ (when (or (null arg) (= arg 0)) (setq arg 1))
+ (let* ((re-search-fn (if (> arg 0)
+ #'re-search-backward
+ #'re-search-forward))
+ (line-beg-pos (line-beginning-position))
+ (line-content-start (+ line-beg-pos (current-indentation)))
+ (pos (point-marker))
+ (beg-indentation
+ (and (> arg 0)
+ (save-excursion
+ (while (and (not (julia-looking-at-beginning-of-defun))
+ ;; f(x) = ... function bodies may span multiple lines
+ (or (and (julia-indent-hanging)
+ (forward-line -1))
+ ;; inside dangling parameter list
+ (and (eq 'paren (julia-syntax-context-type))
+ (backward-up-list))
+ (julia-last-open-block (point-min)))))
+ (or (and (julia-looking-at-beginning-of-defun)
+ (+ (current-indentation) julia-indent-offset))
+ 0))))
+ (found
+ (progn
+ (when (and (< arg 0)
+ (julia-looking-at-beginning-of-defun))
+ (end-of-line 1))
+ (while (and (funcall re-search-fn
+ julia-beginning-of-defun-regex nil t)
+ (or (julia-syntax-comment-or-string-p)
+ ;; handle nested defuns when moving backwards
+ ;; by checking matching indentation
+ (and (> arg 0)
+ (not (= (current-indentation) 0))
+ (>= (current-indentation) beg-indentation)))))
+ (and (julia-looking-at-beginning-of-defun)
+ (or (not (= (line-number-at-pos pos)
+ (line-number-at-pos)))
+ (and (>= (point) line-beg-pos)
+ (<= (point) line-content-start)
+ (> pos line-content-start)))))))
+ (if found
+ (or (beginning-of-line 1) (point))
+ (and (goto-char pos) nil))))
+
+(defun julia-beginning-of-defun (&optional arg)
+ "Move point to `beginning-of-defun'.
+With positive ARG search backwards else search forward.
+ARG nil or 0 defaults to 1. When searching backwards,
+nested defuns are handled depending on current point position.
+Return non-nil (point) if point moved to `beginning-of-defun'."
+ (when (or (null arg) (= arg 0)) (setq arg 1))
+ (let ((found))
+ (while (and (not (= arg 0))
+ (let ((keep-searching-p
+ (julia--beginning-of-defun arg)))
+ (when (and keep-searching-p (null found))
+ (setq found t))
+ keep-searching-p))
+ (setq arg (if (> arg 0) (1- arg) (1+ arg))))
+ found))
+
+(defun julia-end-of-defun (&optional arg)
+ "Move point to the end of the current function.
+Return nil if point is not in a function, otherwise point."
+ (interactive)
+ (let ((beg-defun-indent)
+ (beg-pos (point)))
+ (when (or (julia-looking-at-beginning-of-defun)
+ (julia-beginning-of-defun 1)
+ (julia-beginning-of-defun -1))
+ (beginning-of-line)
+ (if (looking-at-p julia-function-assignment-regex)
+ ;; f(x) = ...
+ (progn
+ ;; skip any dangling lines
+ (while (and (forward-line)
+ (not (eobp))
+ (or (julia-indent-hanging)
+ ;; dangling closing paren
+ (and (eq 'paren (julia-syntax-context-type))
+ (search-forward ")"))))))
+ ;; otherwise skip forward to matching indentation (not in string/comment)
+ (setq beg-defun-indent (current-indentation))
+ (while (and (not (eobp))
+ (forward-line 1)
+ (or (julia-syntax-comment-or-string-p)
+ (> (current-indentation) beg-defun-indent)))))
+ (end-of-line)
+ (point))))
+
+;;; IMENU
+(defvar julia-imenu-generic-expression
+ ;; don't use syntax classes, screws egrep
+ '(("Function (_)" "[ \t]*function[ \t]+\\(_[^ \t\n]*\\)" 1)
+ ("Function" "^[ \t]*function[ \t]+\\([^_][^\t\n]*\\)" 1)
+ ("Const" "[ \t]*const \\([^ \t\n]*\\)" 1)
+ ("Type" "^[ \t]*[a-zA-Z0-9_]*type[a-zA-Z0-9_]* \\([^ \t\n]*\\)" 1)
+ ("Require" " *\\(\\brequire\\)(\\([^ \t\n)]*\\)" 2)
+ ("Include" " *\\(\\binclude\\)(\\([^ \t\n)]*\\)" 2)
+ ;; ("Classes" "^.*setClass(\\(.*\\)," 1)
+ ;; ("Coercions" "^.*setAs(\\([^,]+,[^,]*\\)," 1) ; show from and to
+ ;; ("Generics" "^.*setGeneric(\\([^,]*\\)," 1)
+ ;; ("Methods" "^.*set\\(Group\\|Replace\\)?Method(\"\\(.+\\)\"," 2)
+ ;; ;;[ ]*\\(signature=\\)?(\\(.*,?\\)*\\)," 1)
+ ;; ;;
+ ;; ;;("Other" "^\\(.+\\)\\s-*<-[ \t\n]*[^\\(function\\|read\\|.*data\.frame\\)]" 1)
+ ;; ("Package" "^.*\\(library\\|require\\)(\\(.*\\)," 2)
+ ;; ("Data" "^\\(.+\\)\\s-*<-[ \t\n]*\\(read\\|.*data\.frame\\).*(" 1)))
+ ))
+
+;;;###autoload
+(define-derived-mode julia-mode julia-mode-prog-mode "Julia"
+ "Major mode for editing julia code."
+ (set-syntax-table julia-mode-syntax-table)
+ (set (make-local-variable 'comment-start) "# ")
+ (set (make-local-variable 'comment-start-skip) "#+\\s-*")
+ (set (make-local-variable 'font-lock-defaults) '(julia-font-lock-keywords))
+ (if (< emacs-major-version 24)
+ ;; Emacs 23 doesn't have syntax-propertize-function
+ (set (make-local-variable 'font-lock-syntactic-keywords)
+ (list
+ `(,julia-char-regex
+ (1 "\"") ; Treat ' as a string delimiter.
+ (2 ".") ; Don't highlight anything between the open and close '.
+ (3 "\"")) ; Treat the close ' as a string delimiter.
+ `(,julia-triple-quoted-string-regex
+ (1 "\"") ; Treat the first " in """ as a string delimiter.
+ (2 ".") ; Don't highlight anything between.
+ (3 "\"")))) ; Treat the last " in """ as a string delimiter.
+ ;; Emacs 24 and later has syntax-propertize-function, so use that instead.
+ (set (make-local-variable 'syntax-propertize-function)
+ julia-syntax-propertize-function))
+ (set (make-local-variable 'indent-line-function) 'julia-indent-line)
+ (set (make-local-variable 'beginning-of-defun-function) #'julia-beginning-of-defun)
+ (set (make-local-variable 'end-of-defun-function) #'julia-end-of-defun)
+ (setq indent-tabs-mode nil)
+ (setq imenu-generic-expression julia-imenu-generic-expression)
+ (imenu-add-to-menubar "Imenu"))
+
+(defun julia-manual-deindent ()
+ "Deindent by `julia-indent-offset' regardless of current
+indentation context. To be used to manually indent inside
+strings."
+ (interactive)
+ (indent-line-to (max 0 (- (current-indentation) julia-indent-offset))))
+(define-key julia-mode-map (kbd "<backtab>") 'julia-manual-deindent)
+
+(defvar julia-latexsubs (make-hash-table :test 'equal))
+
+(defun julia-latexsub ()
+ "Perform a LaTeX-like Unicode symbol substitution."
+ (interactive "*i")
+ (let ((orig-pt (point)))
+ (while (not (or (bobp) (= ?\\ (char-before))
+ (= ?\s (char-syntax (char-before)))))
+ (backward-char))
+ (if (and (not (bobp)) (= ?\\ (char-before)))
+ (progn
+ (backward-char)
+ (let ((sub (gethash (buffer-substring (point) orig-pt) julia-latexsubs)))
+ (if sub
+ (progn
+ (delete-region (point) orig-pt)
+ (insert sub))
+ (goto-char orig-pt))))
+ (goto-char orig-pt))))
+
+(defalias 'latexsub 'julia-latexsub)
+
+(defun julia-latexsub-or-indent (arg)
+ "Either indent according to mode or perform a LaTeX-like symbol substution"
+ (interactive "*i")
+ (if (latexsub)
+ (indent-for-tab-command arg)))
+(define-key julia-mode-map (kbd "TAB") 'julia-latexsub-or-indent)
+
+(defalias 'latexsub-or-indent 'julia-latexsub-or-indent)
+
+;;; populate LaTeX symbols hash table from a generated file.
+;;; (See Julia issue #8947 for why we don't use the Emacs tex input mode.)
+(load (expand-file-name "julia-latexsubs" (file-name-directory load-file-name)))
+
+;; Math insertion in julia. Use it with
+;; (add-hook 'julia-mode-hook 'julia-math-mode)
+;; (add-hook 'inferior-julia-mode-hook 'julia-math-mode)
+
+(when (require 'latex nil t)
+ (declare-function LaTeX-math-abbrev-prefix "latex")
+
+ (defun julia-math-insert (s)
+ "Inserts math symbol given by `s'"
+ (when s
+ (let ((sym (gethash (concat "\\" s) julia-latexsubs)))
+ (when sym
+ (insert sym)))))
+
+ (with-no-warnings
+ (define-minor-mode julia-math-mode
+ "A minor mode with easy access to TeX math commands. The
+command is only entered if it is supported in Julia. The
+following commands are defined:
+
+\\{LaTeX-math-mode-map}"
+ nil nil (list (cons (LaTeX-math-abbrev-prefix) LaTeX-math-keymap))
+ (if julia-math-mode
+ (set (make-local-variable 'LaTeX-math-insert-function)
+ 'julia-math-insert)))))
+
+;; Code for `inferior-julia-mode'
+(require 'comint)
+
+(defcustom julia-program "julia"
+ "Path to the program used by `inferior-julia'."
+ :type 'string
+ :group 'julia)
+
+(defcustom julia-arguments '("-i" "--color=yes")
+ "Commandline arguments to pass to `julia-program'."
+ :type '(repeat (string :tag "argument"))
+ :group 'julia)
+
+(defvar julia-prompt-regexp "^\\w*> "
+ "Regexp for matching `inferior-julia' prompt.")
+
+(defvar inferior-julia-mode-map
+ (let ((map (nconc (make-sparse-keymap) comint-mode-map)))
+ ;; example definition
+ (define-key map (kbd "TAB") 'julia-latexsub-or-indent)
+ map)
+ "Basic mode map for `inferior-julia-mode'.")
+
+;;;###autoload
+(defun inferior-julia ()
+ "Run an inferior instance of `julia' inside Emacs."
+ (interactive)
+ (let ((julia-program julia-program)
+ (buffer (get-buffer-create "*Julia*")))
+ (when (not (comint-check-proc "*Julia*"))
+ (apply #'make-comint-in-buffer "Julia" "*Julia*"
+ julia-program nil julia-arguments))
+ (pop-to-buffer-same-window "*Julia*")
+ (inferior-julia-mode)))
+
+(defun inferior-julia--initialize ()
+ "Helper function to initialize `inferior-julia'."
+ (setq comint-use-prompt-regexp t))
+
+(define-derived-mode inferior-julia-mode comint-mode "Julia"
+ "Major mode for `inferior-julia'.
+
+\\<inferior-julia-mode-map>"
+ nil "Julia"
+ (setq comint-prompt-regexp julia-prompt-regexp)
+ (setq comint-prompt-read-only t)
+ (set (make-local-variable 'font-lock-defaults) '(julia-font-lock-keywords t))
+ (set (make-local-variable 'paragraph-start) julia-prompt-regexp)
+ (set (make-local-variable 'indent-line-function) 'julia-indent-line))
+
+(add-hook 'inferior-julia-mode-hook 'inferior-julia--initialize)
+
+;;;###autoload
+(defalias 'run-julia #'inferior-julia
+ "Run an inferior instance of `julia' inside Emacs.")
+
+(provide 'julia-mode)
+
+;; Local Variables:
+;; coding: utf-8
+;; byte-compile-warnings: (not obsolete)
+;; End:
+;;; julia-mode.el ends here
--- /dev/null
+This is the official Emacs mode for editing Julia programs.
--- /dev/null
+;;; pcre2el-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
+\f
+;;;### (autoloads nil "pcre2el" "pcre2el.el" (23997 31476 537680
+;;;;;; 409000))
+;;; Generated autoloads from pcre2el.el
+
+(defvar pcre-mode nil "\
+Non-nil if PCRE mode is enabled.
+See the `pcre-mode' command
+for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `pcre-mode'.")
+
+(custom-autoload 'pcre-mode "pcre2el" nil)
+
+(autoload 'pcre-mode "pcre2el" "\
+Use emulated PCRE syntax for regexps wherever possible.
+
+Advises the `interactive' specs of `read-regexp' and the
+following other functions so that they read PCRE syntax and
+translate to its Emacs equivalent:
+
+- `align-regexp'
+- `find-tag-regexp'
+- `sort-regexp-fields'
+- `isearch-message-prefix'
+- `ibuffer-do-replace-regexp'
+
+Also alters the behavior of `isearch-mode' when searching by regexp.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'pcre-query-replace-regexp "pcre2el" "\
+Perform `query-replace-regexp' using PCRE syntax.
+
+Consider using `pcre-mode' instead of this function.
+
+\(fn)" t nil)
+
+(autoload 'rxt-elisp-to-pcre "pcre2el" "\
+Translate REGEXP, a regexp in Emacs Lisp syntax, to Perl-compatible syntax.
+
+Interactively, reads the regexp in one of three ways. With a
+prefix arg, reads from minibuffer without string escaping, like
+`query-replace-regexp'. Without a prefix arg, uses the text of
+the region if it is active. Otherwise, uses the result of
+evaluating the sexp before point (which might be a string regexp
+literal or an expression that produces a string).
+
+Displays the translated PCRE regexp in the echo area and copies
+it to the kill ring.
+
+Emacs regexp features such as syntax classes which cannot be
+translated to PCRE will cause an error.
+
+\(fn REGEXP)" t nil)
+
+(autoload 'rxt-elisp-to-rx "pcre2el" "\
+Translate REGEXP, a regexp in Emacs Lisp syntax, to `rx' syntax.
+
+See `rxt-elisp-to-pcre' for a description of the interactive
+behavior and `rx' for documentation of the S-expression based
+regexp syntax.
+
+\(fn REGEXP)" t nil)
+
+(autoload 'rxt-elisp-to-strings "pcre2el" "\
+Return a list of all strings matched by REGEXP, an Emacs Lisp regexp.
+
+See `rxt-elisp-to-pcre' for a description of the interactive behavior.
+
+This is useful primarily for getting back the original list of
+strings from a regexp generated by `regexp-opt', but it will work
+with any regexp without unbounded quantifiers (*, +, {2, } and so
+on).
+
+Throws an error if REGEXP contains any infinite quantifiers.
+
+\(fn REGEXP)" t nil)
+
+(autoload 'rxt-toggle-elisp-rx "pcre2el" "\
+Toggle the regexp near point between Elisp string and rx syntax.
+
+\(fn)" t nil)
+
+(autoload 'rxt-pcre-to-elisp "pcre2el" "\
+Translate PCRE, a regexp in Perl-compatible syntax, to Emacs Lisp.
+
+Interactively, uses the contents of the region if it is active,
+otherwise reads from the minibuffer. Prints the Emacs translation
+in the echo area and copies it to the kill ring.
+
+PCRE regexp features that cannot be translated into Emacs syntax
+will cause an error. See the commentary section of pcre2el.el for
+more details.
+
+\(fn PCRE &optional FLAGS)" t nil)
+
+(defalias 'pcre-to-elisp 'rxt-pcre-to-elisp)
+
+(autoload 'rxt-pcre-to-rx "pcre2el" "\
+Translate PCRE, a regexp in Perl-compatible syntax, to `rx' syntax.
+
+See `rxt-pcre-to-elisp' for a description of the interactive behavior.
+
+\(fn PCRE &optional FLAGS)" t nil)
+
+(autoload 'rxt-pcre-to-strings "pcre2el" "\
+Return a list of all strings matched by PCRE, a Perl-compatible regexp.
+
+See `rxt-elisp-to-pcre' for a description of the interactive
+behavior and `rxt-elisp-to-strings' for why this might be useful.
+
+Throws an error if PCRE contains any infinite quantifiers.
+
+\(fn PCRE &optional FLAGS)" t nil)
+
+(autoload 'rxt-explain-elisp "pcre2el" "\
+Insert the pretty-printed `rx' syntax for REGEXP in a new buffer.
+
+REGEXP is a regular expression in Emacs Lisp syntax. See
+`rxt-elisp-to-pcre' for a description of how REGEXP is read
+interactively.
+
+\(fn REGEXP)" t nil)
+
+(autoload 'rxt-explain-pcre "pcre2el" "\
+Insert the pretty-printed `rx' syntax for REGEXP in a new buffer.
+
+REGEXP is a regular expression in PCRE syntax. See
+`rxt-pcre-to-elisp' for a description of how REGEXP is read
+interactively.
+
+\(fn REGEXP &optional FLAGS)" t nil)
+
+(autoload 'rxt-quote-pcre "pcre2el" "\
+Return a PCRE regexp which matches TEXT literally.
+
+Any PCRE metacharacters in TEXT will be quoted with a backslash.
+
+\(fn TEXT)" nil nil)
+
+(autoload 'rxt-explain "pcre2el" "\
+Pop up a buffer with pretty-printed `rx' syntax for the regex at point.
+
+Chooses regex syntax to read based on current major mode, calling
+`rxt-explain-elisp' if buffer is in `emacs-lisp-mode' or
+`lisp-interaction-mode', or `rxt-explain-pcre' otherwise.
+
+\(fn)" t nil)
+
+(autoload 'rxt-convert-syntax "pcre2el" "\
+Convert regex at point to other kind of syntax, depending on major mode.
+
+For buffers in `emacs-lisp-mode' or `lisp-interaction-mode',
+calls `rxt-elisp-to-pcre' to convert to PCRE syntax. Otherwise,
+calls `rxt-pcre-to-elisp' to convert to Emacs syntax.
+
+The converted syntax is displayed in the echo area and copied to
+the kill ring; see the two functions named above for details.
+
+\(fn)" t nil)
+
+(autoload 'rxt-convert-to-rx "pcre2el" "\
+Convert regex at point to RX syntax. Chooses Emacs or PCRE syntax by major mode.
+
+\(fn)" t nil)
+
+(autoload 'rxt-convert-to-strings "pcre2el" "\
+Convert regex at point to RX syntax. Chooses Emacs or PCRE syntax by major mode.
+
+\(fn)" t nil)
+
+(autoload 'rxt-mode "pcre2el" "\
+Regex translation utilities.
+
+\(fn &optional ARG)" t nil)
+
+(autoload 'turn-on-rxt-mode "pcre2el" "\
+Turn on `rxt-mode' in the current buffer.
+
+\(fn)" t nil)
+
+(defvar rxt-global-mode nil "\
+Non-nil if Rxt-Global mode is enabled.
+See the `rxt-global-mode' command
+for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `rxt-global-mode'.")
+
+(custom-autoload 'rxt-global-mode "pcre2el" nil)
+
+(autoload 'rxt-global-mode "pcre2el" "\
+Toggle Rxt mode in all buffers.
+With prefix ARG, enable Rxt-Global mode if ARG is positive;
+otherwise, disable it. If called from Lisp, enable the mode if
+ARG is omitted or nil.
+
+Rxt mode is enabled in all buffers where
+`turn-on-rxt-mode' would do it.
+See `rxt-mode' for more information on Rxt mode.
+
+\(fn &optional ARG)" t nil)
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; pcre2el-autoloads.el ends here
--- /dev/null
+;;; -*- no-byte-compile: t -*-
+(define-package "pcre2el" "20161120.2103" "regexp syntax converter" '((emacs "24") (cl-lib "0.3")) :commit "0b5b2a2c173aab3fd14aac6cf5e90ad3bf58fa7d" :authors '(("joddie <jonxfield at gmail.com>")) :maintainer '("joddie <jonxfield at gmail.com>") :url "https://github.com/joddie/pcre2el")
--- /dev/null
+;;; pcre2el.el --- regexp syntax converter -*- lexical-binding: t -*-
+
+;; Copyright (C) 2012-2015 Jon Oddie <jonxfieldNOSPAMgmail.com>
+
+;; Author: joddie <jonxfield at gmail.com>
+;; Hacked additionally by: opensource at hardakers dot net
+;; Created: 14 Feb 2012
+;; Updated: 13 December 2015
+;; Version: 1.8
+;; Package-Version: 20161120.2103
+;; Url: https://github.com/joddie/pcre2el
+;; Package-Requires: ((emacs "24") (cl-lib "0.3"))
+
+;; This file is NOT part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation, either version 3 of the
+;; License, or (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see `http://www.gnu.org/licenses/'.
+
+;; This file incorporates work covered by the following copyright and
+;; permission notice:
+;;
+;; Copyright (c) 1993-2002 Richard Kelsey and Jonathan Rees
+;; Copyright (c) 1994-2002 by Olin Shivers and Brian D. Carlstrom.
+;; Copyright (c) 1999-2002 by Martin Gasbichler.
+;; Copyright (c) 2001-2002 by Michael Sperber.
+;; All rights reserved.
+;;
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions
+;; are met: 1. Redistributions of source code must retain the above
+;; copyright notice, this list of conditions and the following
+;; disclaimer. 2. Redistributions in binary form must reproduce the
+;; above copyright notice, this list of conditions and the following
+;; disclaimer in the documentation and/or other materials provided
+;; with the distribution. 3. The name of the authors may not be used
+;; to endorse or promote products derived from this software without
+;; specific prior written permission.
+;;
+;; THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR
+;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+;;; Commentary:
+
+;; 1 Overview
+;; ==========
+
+;; `pcre2el' or `rxt' (RegeXp Translator or RegeXp Tools) is a utility
+;; for working with regular expressions in Emacs, based on a
+;; recursive-descent parser for regexp syntax. In addition to converting
+;; (a subset of) PCRE syntax into its Emacs equivalent, it can do the
+;; following:
+
+;; - convert Emacs syntax to PCRE
+;; - convert either syntax to `rx', an S-expression based regexp syntax
+;; - untangle complex regexps by showing the parse tree in `rx' form and
+;; highlighting the corresponding chunks of code
+;; - show the complete list of strings (productions) matching a regexp,
+;; provided the list is finite
+;; - provide live font-locking of regexp syntax (so far only for Elisp
+;; buffers -- other modes on the TODO list)
+
+
+;; 2 Usage
+;; =======
+
+;; Enable `rxt-mode' or its global equivalent `rxt-global-mode' to get
+;; the default key-bindings. There are three sets of commands: commands
+;; that take a PCRE regexp, commands which take an Emacs regexp, and
+;; commands that try to do the right thing based on the current
+;; mode. Currently, this means Emacs syntax in `emacs-lisp-mode' and
+;; `lisp-interaction-mode', and PCRE syntax everywhere else.
+
+;; The default key bindings all begin with `C-c /' and have a mnemonic
+;; structure: `C-c / <source> <target>', or just `C-c / <target>' for the
+;; "do what I mean" commands. The complete list of key bindings is given
+;; here and explained in more detail below:
+
+;; - "Do-what-I-mean" commands:
+;; `C-c / /': `rxt-explain'
+;; `C-c / c': `rxt-convert-syntax'
+;; `C-c / x': `rxt-convert-to-rx'
+;; `C-c / '': `rxt-convert-to-strings'
+
+;; - Commands that work on a PCRE regexp:
+;; `C-c / p e': `rxt-pcre-to-elisp'
+;; `C-c / %': `pcre-query-replace-regexp'
+;; `C-c / p x': `rxt-pcre-to-rx'
+;; `C-c / p '': `rxt-pcre-to-strings'
+;; `C-c / p /': `rxt-explain-pcre'
+
+;; - Commands that work on an Emacs regexp:
+;; `C-c / e /': `rxt-explain-elisp'
+;; `C-c / e p': `rxt-elisp-to-pcre'
+;; `C-c / e x': `rxt-elisp-to-rx'
+;; `C-c / e '': `rxt-elisp-to-strings'
+;; `C-c / e t': `rxt-toggle-elisp-rx'
+;; `C-c / t': `rxt-toggle-elisp-rx'
+
+
+;; 2.1 Interactive input and output
+;; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+;; When used interactively, the conversion commands can read a regexp
+;; either from the current buffer or from the minibuffer. The output is
+;; displayed in the minibuffer and copied to the kill-ring.
+
+;; - When called with a prefix argument (`C-u'), they read a regular
+;; expression from the minibuffer literally, without further processing
+;; -- meaning there's no need to double the backslashes if it's an
+;; Emacs regexp. This is the same way commands like
+;; `query-replace-regexp' read input.
+
+;; - When the region is active, they use they the region contents, again
+;; literally (without any translation of string syntax).
+
+;; - With neither a prefix arg nor an active region, the behavior depends
+;; on whether the command expects an Emacs regexp or a PCRE one.
+
+;; Commands that take an Emacs regexp behave like `C-x C-e': they
+;; evaluate the sexp before point (which could be simply a string
+;; literal) and use its value. This is designed for use in Elisp
+;; buffers. As a special case, if point is *inside* a string, it's
+;; first moved to the string end, so in practice they should work as
+;; long as point is somewhere within the regexp literal.
+
+;; Commands that take a PCRE regexp try to read a Perl-style delimited
+;; regex literal *after* point in the current buffer, including its
+;; flags. For example, putting point before the `m' in the following
+;; example and doing `C-c / p e' (`rxt-pcre-to-elisp') displays
+;; `\(?:bar\|foo\)', correctly stripping out the whitespace and
+;; comment:
+
+;; ,----
+;; | $x =~ m/ foo | (?# comment) bar /x
+;; `----
+
+;; The PCRE reader currently only works with `/ ... /' delimiters. It
+;; will ignore any preceding `m', `s', or `qr' operator, as well as the
+;; replacement part of an `s' construction.
+
+;; Readers for other PCRE-using languages are on the TODO list.
+
+;; The translation functions display their result in the minibuffer and
+;; copy it to the kill ring. When translating something into Elisp
+;; syntax, you might need to use the result either literally (e.g. for
+;; interactive input to a command like `query-replace-regexp'), or as a
+;; string to paste into Lisp code. To allow both uses,
+;; `rxt-pcre-to-elisp' copies both versions successively to the
+;; kill-ring. The literal regexp without string quoting is the top
+;; element of the kill-ring, while the Lisp string is the
+;; second-from-top. You can paste the literal regexp somewhere by doing
+;; `C-y', or the Lisp string by `C-y M-y'.
+
+
+;; 2.2 Syntax conversion commands
+;; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+;; `rxt-convert-syntax' (`C-c / c') converts between Emacs and PCRE
+;; syntax, depending on the major mode in effect when called.
+;; Alternatively, you can specify the conversion direction explicitly by
+;; using either `rxt-pcre-to-elisp' (`C-c / p e') or `rxt-elisp-to-pcre'
+;; (`C-c / e p').
+
+;; Similarly, `rxt-convert-to-rx' (`C-c / x') converts either kind of
+;; syntax to `rx' form, while `rxt-convert-pcre-to-rx' (`C-c / p x') and
+;; `rxt-convert-elisp-to-rx' (`C-c / e x') convert to `rx' from a
+;; specified source type.
+
+;; In Elisp buffers, you can use `rxt-toggle-elisp-rx' (`C-c / t' or `C-c
+;; / e t') to switch the regexp at point back and forth between string
+;; and `rx' syntax. Point should either be within an `rx' or
+;; `rx-to-string' form or a string literal for this to work.
+
+
+;; 2.3 PCRE mode (experimental)
+;; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+;; If you want to use emulated PCRE regexp syntax in all Emacs commands,
+;; try `pcre-mode', which uses Emacs's advice system to make all commands
+;; that read regexps using the minibuffer use emulated PCRE syntax. It
+;; should also work with Isearch.
+
+;; This feature is still fairly experimental. It may fail to work or do
+;; the wrong thing with certain commands. Please report bugs.
+
+;; `pcre-query-replace-regexp' was originally defined to do query-replace
+;; using emulated PCRE regexps, and is now made somewhat obsolete by
+;; `pcre-mode'. It is bound to `C-c / %' by default, by analogy with
+;; `M-%'. Put the following in your `.emacs' if you want to use
+;; PCRE-style query replacement everywhere:
+
+;; ,----
+;; | (global-set-key [(meta %)] 'pcre-query-replace-regexp)
+;; `----
+
+;; 2.5 Explain regexps
+;; ~~~~~~~~~~~~~~~~~~~
+
+;; When syntax-highlighting isn't enough to untangle some gnarly regexp
+;; you find in the wild, try the 'explain' commands: `rxt-explain' (`C-c
+;; / /'), `rxt-explain-pcre' (`C-c / p') and `rxt-explain-elisp' (`C-c /
+;; e'). These display the original regexp along with its pretty-printed
+;; `rx' equivalent in a new buffer. Moving point around either in the
+;; original regexp or the `rx' translation highlights corresponding
+;; pieces of syntax, which can aid in seeing things like the scope of
+;; quantifiers.
+
+;; I call them "explain" commands because the `rx' form is close to a
+;; plain syntax tree, and this plus the wordiness of the operators
+;; usually helps to clarify what is going on. People who dislike Lisp
+;; syntax might disagree with this assessment.
+
+
+;; 2.6 Generate all matching strings (productions)
+;; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+;; Occasionally you come across a regexp which is designed to match a
+;; finite set of strings, e.g. a set of keywords, and it would be useful
+;; to recover the original set. (In Emacs you can generate such regexps
+;; using `regexp-opt'). The commands `rxt-convert-to-strings' (`C-c /
+;; ′'), `rxt-pcre-to-strings' (`C-c / p ′') or `rxt-elisp-to-strings'
+;; (`C-c / e ′') accomplish this by generating all the matching strings
+;; ("productions") of a regexp. (The productions are copied to the kill
+;; ring as a Lisp list).
+
+;; An example in Lisp code:
+
+;; ,----
+;; | (regexp-opt '("cat" "caterpillar" "catatonic"))
+;; | ;; => "\\(?:cat\\(?:atonic\\|erpillar\\)?\\)"
+;; | (rxt-elisp-to-strings "\\(?:cat\\(?:atonic\\|erpillar\\)?\\)")
+;; | ;; => '("cat" "caterpillar" "catatonic")
+;; `----
+
+;; For obvious reasons, these commands only work with regexps that don't
+;; include any unbounded quantifiers like `+' or `*'. They also can't
+;; enumerate all the characters that match a named character class like
+;; `[[:alnum:]]'. In either case they will give a (hopefully meaningful)
+;; error message. Due to the nature of permutations, it's still possible
+;; for a finite regexp to generate a huge number of productions, which
+;; will eat memory and slow down your Emacs. Be ready with `C-g' if
+;; necessary.
+
+
+;; 2.7 RE-Builder support
+;; ~~~~~~~~~~~~~~~~~~~~~~
+
+;; The Emacs RE-Builder is a useful visual tool which allows using
+;; several different built-in syntaxes via `reb-change-syntax' (`C-c
+;; TAB'). It supports Elisp read and literal syntax and `rx', but it can
+;; only convert from the symbolic forms to Elisp, not the other way. This
+;; package hacks the RE-Builder to also work with emulated PCRE syntax,
+;; and to convert transparently between Elisp, PCRE and rx syntaxes. PCRE
+;; mode reads a delimited Perl-like literal of the form `/ ... /', and it
+;; should correctly support using the `x' and `s' flags.
+
+
+;; 2.8 Use from Lisp
+;; ~~~~~~~~~~~~~~~~~
+
+;; Example of using the conversion functions:
+;; ,----
+;; | (rxt-pcre-to-elisp "(abc|def)\\w+\\d+")
+;; | ;; => "\\(\\(?:abc\\|def\\)\\)[_[:alnum:]]+[[:digit:]]+"
+;; `----
+
+;; All the conversion functions take a single string argument, the regexp
+;; to translate:
+
+;; - `rxt-pcre-to-elisp'
+;; - `rxt-pcre-to-rx'
+;; - `rxt-pcre-to-strings'
+;; - `rxt-elisp-to-pcre'
+;; - `rxt-elisp-to-rx'
+;; - `rxt-elisp-to-strings'
+
+
+;; 3 Bugs and Limitations
+;; ======================
+
+;; 3.1 Limitations on PCRE syntax
+;; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+;; PCRE has a complicated syntax and semantics, only some of which can be
+;; translated into Elisp. The following subset of PCRE should be
+;; correctly parsed and converted:
+
+;; - parenthesis grouping `( .. )', including shy matches `(?: ... )'
+;; - backreferences (various syntaxes), but only up to 9 per expression
+;; - alternation `|'
+;; - greedy and non-greedy quantifiers `*', `*?', `+', `+?', `?' and `??'
+;; (all of which are the same in Elisp as in PCRE)
+;; - numerical quantifiers `{M,N}'
+;; - beginning/end of string `\A', `\Z'
+;; - string quoting `\Q .. \E'
+;; - word boundaries `\b', `\B' (these are the same in Elisp)
+;; - single character escapes `\a', `\c', `\e', `\f', `\n', `\r', `\t',
+;; `\x', and `\octal digits' (but see below about non-ASCII characters)
+;; - character classes `[...]' including Posix escapes
+;; - character classes `\d', `\D', `\h', `\H', `\s', `\S', `\v', `\V'
+;; both within character class brackets and outside
+;; - word and non-word characters `\w' and `\W' (Emacs has the same
+;; syntax, but its meaning is different)
+;; - `s' (single line) and `x' (extended syntax) flags, in regexp
+;; literals, or set within the expression via `(?xs-xs)' or `(?xs-xs:
+;; .... )' syntax
+;; - comments `(?# ... )'
+
+;; Most of the more esoteric PCRE features can't really be supported by
+;; simple translation to Elisp regexps. These include the different
+;; lookaround assertions, conditionals, and the "backtracking control
+;; verbs" `(* ...)' . OTOH, there are a few other syntaxes which are
+;; currently unsupported and possibly could be:
+
+;; - `\L', `\U', `\l', `\u' case modifiers
+;; - `\g{...}' backreferences
+
+
+;; 3.2 Other limitations
+;; ~~~~~~~~~~~~~~~~~~~~~
+
+;; - The order of alternatives and characters in char classes sometimes
+;; gets shifted around, which is annoying.
+;; - Although the string parser tries to interpret PCRE's octal and
+;; hexadecimal escapes correctly, there are problems with matching
+;; 8-bit characters that I don't use enough to properly understand,
+;; e.g.:
+;; ,----
+;; | (string-match-p (rxt-pcre-to-elisp "\\377") "\377") => nil
+;; `----
+;; A fix for this would be welcome.
+
+;; - Most of PCRE's rules for how `^', `\A', `$' and `\Z' interact with
+;; newlines are not implemented, since they seem less relevant to
+;; Emacs's buffer-oriented rather than line-oriented model. However,
+;; the different meanings of the `.' metacharacter *are* implemented
+;; (it matches newlines with the `/s' flag, but not otherwise).
+
+;; - Not currently namespace clean (both `rxt-' and a couple of `pcre-'
+;; functions).
+
+
+;; 3.3 TODO:
+;; ~~~~~~~~~
+
+;; - Python-specific extensions to PCRE?
+;; - Language-specific stuff to enable regexp font-locking and explaining
+;; in different modes. Each language would need two functions, which
+;; could be kept in an alist:
+
+;; 1. A function to read PCRE regexps, taking the string syntax into
+;; account. E.g., Python has single-quoted, double-quoted and raw
+;; strings, each with different quoting rules. PHP has the kind of
+;; belt-and-suspenders solution you would expect: regexps are in
+;; strings, /and/ you have to include the `/ ... /' delimiters!
+;; Duh.
+
+;; 2. A function to copy faces back from the parsed string to the
+;; original buffer text. This has to recognize any escape sequences
+;; so they can be treated as a single character.
+
+
+;; 4 Internal details
+;; ==================
+
+;; `rxt' defines an internal syntax tree representation of regular
+;; expressions, parsers for Elisp and PCRE syntax, and 'unparsers'
+;; to convert the internal representation to PCRE or `rx' syntax.
+;; Converting from the internal representation to Emacs syntax is
+;; done by converting to `rx' form and passing it to `rx-to-string'.
+;; See `rxt-parse-re', `rxt-adt->pcre', and `rxt-adt->rx' for
+;; details.
+
+;; This code is partially based on Olin Shivers' reference SRE
+;; implementation in scsh, although it is simplified in some respects and
+;; extended in others. See `scsh/re.scm', `scsh/spencer.scm' and
+;; `scsh/posixstr.scm' in the `scsh' source tree for details. In
+;; particular, `pcre2el' steals the idea of an abstract data type for
+;; regular expressions and the general structure of the string regexp
+;; parser and unparser. The data types for character sets are extended in
+;; order to support symbolic translation between character set
+;; expressions without assuming a small (Latin1) character set. The
+;; string parser is also extended to parse a bigger variety of
+;; constructions, including POSIX character classes and various Emacs and
+;; Perl regexp assertions. Otherwise, only the bare minimum of scsh's
+;; abstract data type is implemented.
+
+
+;; 5 Soapbox
+;; =========
+
+;; Emacs regexps have their annoyances, but it is worth getting used to
+;; them. The Emacs assertions for word boundaries, symbol boundaries, and
+;; syntax classes depending on the syntax of the mode in effect are
+;; especially useful. (PCRE has `\b' for word-boundary, but AFAIK it
+;; doesn't have separate assertions for beginning-of-word and
+;; end-of-word). Other things that might be done with huge regexps in
+;; other languages can be expressed more understandably in Elisp using
+;; combinations of `save-excursion' with the various searches (regexp,
+;; literal, skip-syntax-forward, sexp-movement functions, etc.).
+
+;; There's not much point in using `rxt-pcre-to-elisp' to use PCRE
+;; notation in a Lisp program you're going to maintain, since you still
+;; have to double all the backslashes. Better to just use the converted
+;; result (or better yet, the `rx' form).
+
+
+;; 6 History and acknowledgments
+;; =============================
+
+;; This was originally created out of an answer to a stackoverflow
+;; question:
+;; [http://stackoverflow.com/questions/9118183/elisp-mechanism-for-converting-pcre-regexps-to-emacs-regexps]
+
+;; Thanks to:
+
+;; - Wes Hardaker (hardaker) for the initial inspiration and subsequent
+;; hacking
+;; - priyadarshan for requesting RX support
+;; - Daniel Colascione (dcolascione) for a patch to support Emacs's
+;; explicitly-numbered match groups
+;; - Aaron Meurer (asmeurer) for requesting Isearch support
+;; - Philippe Vaucher (silex) for a patch to support `ibuffer-do-replace-regexp'
+;; in PCRE mode
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'rx)
+(require 're-builder)
+(require 'advice)
+(require 'ring)
+(require 'pcase)
+
+;;; Customization group
+(defgroup rxt nil
+ "Regex syntax converter and utilities."
+ :version 1.2
+ :group 'tools
+ :group 'lisp
+ :link '(emacs-commentary-link :tag "commentary" "pcre2el.el")
+ :link '(emacs-library-link :tag "lisp file" "pcre2el.el")
+ :link '(url-link :tag "web page" "https://github.com/joddie/pcre2el"))
+
+(defface rxt-highlight-face
+ '((((min-colors 16581375) (background light)) :background "#eee8d5")
+ (((min-colors 16581375) (background dark)) :background "#222222"))
+ "Face for highlighting corresponding regex syntax in `rxt-explain' buffers."
+ :group 'rxt)
+
+(defcustom rxt-verbose-rx-translation nil
+ "Non-nil if `rxt-pcre-to-rx' and `rxt-elisp-to-rx' should use verbose `rx' primitives.
+
+Verbose primitives are things like `line-start' instead of `bol',
+etc."
+ :group 'rxt
+ :type 'boolean)
+
+(defcustom rxt-explain-verbosely t
+ "Non-nil if `rxt-explain-elisp' and `rxt-explain-pcre' should use verbose `rx' primitives.
+
+This overrides the value of `rxt-verbose-rx-translation' for
+these commands only."
+ :group 'rxt
+ :type 'boolean)
+
+\f
+;;;; Macros and functions for writing interactive input and output
+
+;; Macros for handling return values. If called interactively,
+;; display the value in the echo area and copy it to the kill ring,
+;; otherwise just return the value. PCREs are copied as unquoted
+;; strings for yanking into Perl, JS, etc. `rx' forms and other sexps
+;; are copied as `read'-able literals for yanking into Elisp buffers.
+;; Emacs regexps are copied twice: once as an unquoted value for
+;; interactive use, and once as a readable string literal for yanking
+;; into Elisp buffers.
+(defmacro rxt-return-pcre (expr)
+ (let ((value (make-symbol "value")))
+ `(let ((,value ,expr))
+ (when (called-interactively-p 'any)
+ (rxt--kill-pcre ,value))
+ ,value)))
+
+(defmacro rxt-return-sexp (expr)
+ (let ((value (make-symbol "value")))
+ `(let ((,value ,expr))
+ (when (called-interactively-p 'any)
+ (rxt--kill-sexp ,value))
+ ,value)))
+
+(defmacro rxt-return-emacs-regexp (expr)
+ (let ((value (make-symbol "value")))
+ `(let ((,value ,expr))
+ (when (called-interactively-p 'any)
+ (rxt--kill-emacs-regexp ,value))
+ ,value)))
+
+(defun rxt--kill-sexp (value)
+ (let ((lisp-literal (prin1-to-string value)))
+ (message "%s" lisp-literal)
+ (kill-new lisp-literal)))
+
+(defun rxt--kill-pcre (value)
+ (message "%s" value)
+ (kill-new value))
+
+(defun rxt--kill-emacs-regexp (value)
+ (let ((lisp-literal (prin1-to-string value)))
+ (message "%s" value)
+ (kill-new lisp-literal)
+ (kill-new value)))
+
+;; Read an Elisp regexp interactively.
+;;
+;; Three possibilities:
+;;
+;; 1) With a prefix arg, reads literally from the minibuffer, w/o
+;; using string syntax -- just like query-replace-regexp, etc.
+;;
+;; 2) If the region is active, use the text of the region literally
+;; (again w/o string syntax)
+;;
+;; 3) Otherwise, eval the sexp before point (which might be a string
+;; literal or an expression) and use its value. Falls back to method
+;; (1) if this fails to produce a string value.
+;;
+(cl-defun rxt-interactive/elisp (&optional (prompt "Emacs regexp: "))
+ (list
+ (cond (current-prefix-arg
+ (read-string prompt))
+
+ ((use-region-p)
+ (buffer-substring-no-properties (region-beginning) (region-end)))
+
+ (t
+ (condition-case nil
+ (save-excursion
+ (while (nth 3 (syntax-ppss)) (forward-char))
+ (let ((re (eval (preceding-sexp))))
+ (if (stringp re) re
+ (read-string prompt))))
+ (error
+ (read-string prompt)))))))
+
+;; Read a PCRE regexp interactively.
+;;
+;; Three possibilities: As above, except that without prefix arg or
+;; active region, tries to read a delimited regexp literal like /.../,
+;; m/.../, or qr/.../ following point in the current buffer. Falls
+;; back to reading from minibuffer if that fails.
+;;
+;; Returns the regexp, with flags as text properties.
+;;
+;; TODO: Different delimiters
+(cl-defun rxt-interactive/pcre (&optional (prompt "PCRE regexp: "))
+ (list
+ (cond (current-prefix-arg
+ (rxt--read-pcre prompt))
+
+ ((use-region-p)
+ (buffer-substring-no-properties (region-beginning) (region-end)))
+
+ (t
+ (condition-case nil
+ (rxt-read-delimited-pcre)
+ (error ; Fall back to reading from minibuffer
+ (rxt--read-pcre prompt)))))
+ nil))
+
+(define-minor-mode rxt--read-pcre-mode
+ "Minor-mode with key-bindings for toggling PCRE flags.
+
+You should not normally call this directly. It will be enabled
+in minibuffers for `read-regexp' and in the `re-builder' buffer
+when `pcre-mode' is active. These bindings will also be added to
+`isearch-mode-map' in `pcre-mode'."
+ :initial nil
+ :lighter nil
+ :keymap
+ `((,(kbd "C-c s") . ,#'rxt--toggle-s-mode)
+ (,(kbd "C-c x") . ,#'rxt--toggle-x-mode)
+ (,(kbd "C-c i") . ,#'rxt--toggle-i-mode)))
+
+(defun rxt--read-pcre (prompt)
+ "Read a PCRE regexp for translation, together with option flags.
+
+The `s', `x', and `i' flags can be toggled using the following
+commands: \\<rxt--read-pcre-mode-map>
+
+\\[rxt--toggle-s-mode] : toggle `s' (single-line) mode
+\\[rxt--toggle-x-mode] : toggle `x' (extended) mode
+\\[rxt--toggle-i-mode] : toggle `i' (case-insensitive) mode
+
+In single-line mode, `.' will also match newlines.
+In extended mode, whitespace is ignored.
+
+Case-insensitive mode emulates matching without case,
+independently of Emacs's builtin `case-fold-search' setting.
+Note that this does not apply to backreferences."
+ (minibuffer-with-setup-hook #'rxt--read-pcre-mode
+ (read-from-minibuffer prompt)))
+
+(defun rxt--toggle-s-mode ()
+ "Toggle emulated PCRE single-line (s) flag."
+ (interactive)
+ (rxt--toggle-flag ?s))
+
+(defun rxt--toggle-x-mode ()
+ "Toggle emulated PCRE extended (x) flag."
+ (interactive)
+ (rxt--toggle-flag ?x))
+
+(defun rxt--toggle-i-mode ()
+ "Toggle emulated PCRE case-insensitive (i) flag."
+ (interactive)
+ (rxt--toggle-flag ?i))
+
+(defun rxt--toggle-flag (char)
+ "Toggle CHAR, a PCRE flag."
+ (cond
+ ((derived-mode-p 'reb-mode) ; RE-Builder
+ (rxt--toggle-flag-re-builder char))
+ ((minibufferp)
+ (rxt--toggle-flag-minibuffer char))
+ (isearch-mode
+ (rxt--toggle-flag-isearch char))
+ (t
+ (error "Not in minibuffer, RE-Builder or isearch mode."))))
+
+(defun rxt--toggle-flag-re-builder (char)
+ (save-excursion
+ (goto-char (point-max))
+ (search-backward "/")
+ (forward-char)
+ (when (looking-at (rx (* (any ?i ?s ?x))))
+ (let ((inhibit-modification-hooks t))
+ (replace-match (rxt--xor-flags (match-string 0) char) t t))))
+ (reb-do-update))
+
+(defun rxt--toggle-flag-minibuffer (char)
+ (setf (buffer-substring (minibuffer-prompt-end) (point-max))
+ (rxt--toggle-flag-string (minibuffer-contents) char))
+ (when
+ (and (= (point) (minibuffer-prompt-end))
+ (looking-at (rx "(?" (group (+ (any ?i ?s ?x))) ")")))
+ (forward-sexp)))
+
+(defun rxt--toggle-flag-isearch (char)
+ (when isearch-regexp
+ (setq isearch-string
+ (rxt--toggle-flag-string isearch-string char))
+ (setq isearch-message
+ (mapconcat #'isearch-text-char-description isearch-string ""))
+ (isearch-search-and-update)))
+
+(defun rxt--toggle-flag-string (string char)
+ (if (string-match (rx string-start "(?" (group (+ (any ?i ?s ?x))) ")")
+ string)
+ (let ((flags (rxt--xor-flags (match-string 1 string) char)))
+ (if (string= flags "")
+ (replace-match "" t t string)
+ (replace-match flags t t string 1)))
+ (format "(?%c)%s" char string)))
+
+(defun rxt--xor-flags (flags char)
+ (concat
+ (sort
+ (cl-set-exclusive-or (string-to-list flags) (list char))
+ #'<)))
+
+\f
+;;;; Minor mode for using emulated PCRE syntax
+
+(defvar pcre-old-isearch-search-fun-function nil
+ "Original value of `isearch-search-fun-function' before entering `pcre-mode.'
+
+This function is wrapped by `pcre-isearch-search-fun-function'
+and restored on exit from `pcre-mode'.")
+(make-variable-buffer-local 'pcre-old-isearch-search-fun-function)
+
+(defvar pcre-old-isearch-key-bindings nil
+ "Alist of key-bindings to restore in `isearch-mode-map' on exiting `pcre-mode'.")
+
+;;;###autoload
+(define-minor-mode pcre-mode
+ "Use emulated PCRE syntax for regexps wherever possible.
+
+Advises the `interactive' specs of `read-regexp' and the
+following other functions so that they read PCRE syntax and
+translate to its Emacs equivalent:
+
+- `align-regexp'
+- `find-tag-regexp'
+- `sort-regexp-fields'
+- `isearch-message-prefix'
+- `ibuffer-do-replace-regexp'
+
+Also alters the behavior of `isearch-mode' when searching by regexp."
+ nil " PCRE"
+ nil
+ :global t
+
+ (if pcre-mode
+ ;; Enabling
+ (progn
+ ;; Enable advice
+ (ad-enable-regexp "pcre-mode")
+ ;; Set up isearch hooks
+ (add-hook 'isearch-mode-hook #'pcre-isearch-mode-hook)
+ (add-hook 'isearch-mode-end-hook #'pcre-isearch-mode-end-hook)
+ ;; Add the keybindings of `rxt--read-pcre-mode-map' to
+ ;; `isearch-mode-map' (so that they do not cause an exit from
+ ;; `isearch-mode'), and save any existing bindings for those
+ ;; keys to restore on exit from `pcre-mode'.
+ (setq pcre-old-isearch-key-bindings
+ (cl-loop for key being the key-seqs of rxt--read-pcre-mode-map
+ for def = (lookup-key isearch-mode-map key)
+ collect (cons (copy-sequence key)
+ (if (numberp def) nil def))))
+ (cl-loop for key being the key-seqs of rxt--read-pcre-mode-map
+ using (key-bindings def)
+ do (define-key isearch-mode-map key def)))
+
+ ;; Disable advice
+ (ad-disable-regexp "pcre-mode")
+ ;; Remove from isearch hooks
+ (remove-hook 'isearch-mode-hook #'pcre-isearch-mode-hook)
+ (remove-hook 'isearch-mode-end-hook #'pcre-isearch-mode-end-hook)
+ ;; Restore key-bindings
+ (cl-loop for (key . def) in pcre-old-isearch-key-bindings
+ do (define-key isearch-mode-map key def)))
+
+ ;; "Activating" advice re-computes the function definitions, which
+ ;; is necessary whether enabling or disabling
+ (ad-activate-regexp "pcre-mode"))
+
+;;; Cache of PCRE -> Elisp translations
+(defvar pcre-mode-cache-size 100
+ "Number of PCRE-to-Emacs translations to keep in the `pcre-mode' cache.")
+
+(defvar pcre-mode-cache (make-hash-table :test 'equal)
+ "Cache of PCRE-to-Emacs translations used in `pcre-mode'.
+
+Keys are PCRE regexps, values are their Emacs equivalents.")
+
+(defvar pcre-mode-reverse-cache (make-hash-table :test 'equal)
+ "Cache of original PCREs translated to Emacs syntax in `pcre-mode'.
+
+Keys are translated Emacs regexps, values are their original PCRE
+form. This is used to display the original PCRE regexp in place
+of its translated form.")
+
+(defvar pcre-cache-ring (make-ring pcre-mode-cache-size)
+ "Ring of PCRE-to-Emacs translations used in `pcre-mode'.
+
+When the ring fills up, the oldest element is removed and the
+corresponding entries are deleted from the hash tables
+`pcre-mode-cache' and `pcre-mode-reverse-cache'.")
+
+(defun pcre-to-elisp/cached (pcre)
+ "Translate PCRE to Emacs syntax, caching both forms."
+ (or (gethash pcre pcre-mode-cache)
+ (let ((elisp (rxt-pcre-to-elisp pcre)))
+ (pcre-set-cache pcre elisp)
+ elisp)))
+
+(defun pcre-set-cache (pcre-regexp emacs-regexp)
+ "Add a PCRE-to-Emacs translation to the `pcre-mode' cache."
+ (when (and (not (zerop (length pcre-regexp)))
+ (not (zerop (length emacs-regexp)))
+ (not (gethash pcre-regexp pcre-mode-cache)))
+ (if (= (ring-length pcre-cache-ring) (ring-size pcre-cache-ring))
+ (let* ((old-item (ring-remove pcre-cache-ring))
+ (old-pcre (car old-item))
+ (old-emacs (cdr old-item)))
+ (remhash old-pcre pcre-mode-cache)
+ (remhash old-emacs pcre-mode-reverse-cache))
+ (puthash pcre-regexp emacs-regexp pcre-mode-cache)
+ (puthash emacs-regexp pcre-regexp pcre-mode-reverse-cache)
+ (ring-insert pcre-cache-ring (cons pcre-regexp emacs-regexp)))))
+
+;;; Isearch advice
+(defun pcre-isearch-mode-hook ()
+ (when (not (eq isearch-search-fun-function #'isearch-search-fun-default))
+ (message "Warning: pcre-mode overriding existing isearch function `%s'"
+ isearch-search-fun-function))
+ ;; Prevent an infinite loop, if a previous isearch in pcre-mode
+ ;; exited without restoring the original search function for some
+ ;; reason
+ (unless (eq isearch-search-fun-function #'pcre-isearch-search-fun-function)
+ (setq pcre-old-isearch-search-fun-function isearch-search-fun-function))
+ (set (make-local-variable 'isearch-search-fun-function)
+ #'pcre-isearch-search-fun-function))
+
+(defun pcre-isearch-mode-end-hook ()
+ (setq isearch-search-fun-function pcre-old-isearch-search-fun-function))
+
+(defun pcre-isearch-search-fun-function ()
+ "Enable isearching using emulated PCRE syntax.
+
+This is set as the value of `isearch-search-fun-function' when
+`pcre-mode' is enabled. Returns a function which searches using
+emulated PCRE regexps when `isearch-regexp' is true."
+ (lambda (string bound noerror)
+ (let ((real-search-function
+ (funcall (or pcre-old-isearch-search-fun-function 'isearch-search-fun-default))))
+ (if (not isearch-regexp)
+ (funcall real-search-function string bound noerror)
+ ;; Raise an error if the regexp ends in an incomplete escape
+ ;; sequence (= odd number of backslashes).
+ ;; TODO: Perhaps this should really be handled in rxt-pcre-to-elisp?
+ (if (isearch-backslash string) (rxt-error "Trailing backslash"))
+ (funcall real-search-function
+ (pcre-to-elisp/cached string) bound noerror)))))
+
+(defadvice isearch-message-prefix (after pcre-mode disable)
+ "Add \"PCRE\" to the Isearch message when searching by regexp in `pcre-mode'."
+ (when (and isearch-regexp
+ ;; Prevent an inaccurate message if our callback was
+ ;; removed somehow
+ (eq isearch-search-fun-function #'pcre-isearch-search-fun-function))
+ (let ((message ad-return-value))
+ ;; Some hackery to give replacement the same fontification as
+ ;; the original
+ (when
+ (let ((case-fold-search t)) (string-match "regexp" message))
+ (let* ((match (match-string 0 message))
+ (properties (text-properties-at 0 match))
+ (replacement (apply #'propertize "PCRE regexp" properties))
+ (new-message (replace-match replacement t t message)))
+ (setq ad-return-value new-message))))))
+
+(defadvice isearch-fallback
+ (before pcre-mode (want-backslash &optional allow-invalid to-barrier) disable)
+ "Hack to fall back correctly in `pcre-mode'. "
+ ;; A dirty hack to the internals of isearch. Falling back to a
+ ;; previous match position is necessary when the (Emacs) regexp ends
+ ;; in "*", "?", "\{" or "\|": this is handled in
+ ;; `isearch-process-search-char' by calling `isearch-fallback' with
+ ;; `t' for the value of the first parameter, `want-backslash', in
+ ;; the last two cases. With PCRE regexps, falling back should take
+ ;; place on "*", "?", "{" or "|", with no backslashes required.
+ ;; This advice handles the last two cases by unconditionally setting
+ ;; `want-backslash' to nil.
+ (ad-set-arg 0 nil))
+
+(defadvice isearch-edit-string
+ (around pcre-mode disable)
+ "Add PCRE mode-toggling keys to Isearch minibuffer in regexp mode."
+ (if isearch-regexp
+ (minibuffer-with-setup-hook
+ #'rxt--read-pcre-mode
+ ad-do-it)
+ ad-do-it))
+
+;;; evil-mode advice
+(defadvice evil-search-function
+ (around pcre-mode (forward regexp-p wrap) disable)
+ (if (and regexp-p (not isearch-mode))
+ (let ((real-search-function ad-do-it))
+ (setq ad-return-value
+ (pcre-decorate-search-function real-search-function)))
+ ad-do-it))
+
+(eval-after-load 'evil
+ '(when pcre-mode
+ (ad-enable-advice 'evil-search-function 'around 'pcre-mode)
+ (ad-activate 'evil-search-function)))
+
+(defun pcre-decorate-search-function (real-search-function)
+ (lambda (string &optional bound noerror count)
+ (funcall real-search-function
+ (pcre-to-elisp/cached string)
+ bound noerror count)))
+
+;;; Other hooks and defadvices
+
+;;;###autoload
+(defun pcre-query-replace-regexp ()
+ "Perform `query-replace-regexp' using PCRE syntax.
+
+Consider using `pcre-mode' instead of this function."
+ (interactive)
+ (let ((old-pcre-mode pcre-mode))
+ (unwind-protect
+ (progn
+ (pcre-mode +1)
+ (call-interactively #'query-replace-regexp))
+ (pcre-mode (if old-pcre-mode 1 0)))))
+
+
+(defadvice add-to-history
+ (before pcre-mode (history-var newelt &optional maxelt keep-all) disable)
+ "Add the original PCRE to query-replace history in `pcre-mode'."
+ (when (eq history-var query-replace-from-history-variable)
+ (let ((original (gethash newelt pcre-mode-reverse-cache)))
+ (when original
+ (ad-set-arg 1 original)))))
+
+(defadvice query-replace-descr
+ (before pcre-mode (from) disable)
+ "Use the original PCRE in Isearch prompts in `pcre-mode'."
+ (let ((original (gethash from pcre-mode-reverse-cache)))
+ (when original
+ (ad-set-arg 0 original))))
+
+;;; The `interactive' specs of the following functions are lifted
+;;; wholesale from the original built-ins, which see.
+(defadvice read-regexp
+ (around pcre-mode first (prompt &optional defaults history) disable)
+ "Read regexp using PCRE syntax and convert to Elisp equivalent."
+ (ad-set-arg 0 (concat "[PCRE] " prompt))
+ (minibuffer-with-setup-hook
+ #'rxt--read-pcre-mode
+ ad-do-it)
+ (setq ad-return-value
+ (pcre-to-elisp/cached ad-return-value)))
+
+(defadvice align-regexp
+ (before pcre-mode first (beg end regexp &optional group spacing repeat) disable)
+ "Read regexp using PCRE syntax and convert to Elisp equivalent."
+ (interactive
+ (append
+ (list (region-beginning) (region-end))
+ (if current-prefix-arg
+ (list (rxt-pcre-to-elisp
+ (read-string "Complex align using PCRE regexp: "
+ "(\\s*)"))
+ (string-to-number
+ (read-string
+ "Parenthesis group to modify (justify if negative): " "1"))
+ (string-to-number
+ (read-string "Amount of spacing (or column if negative): "
+ (number-to-string align-default-spacing)))
+ (y-or-n-p "Repeat throughout line? "))
+ (list (concat "\\(\\s-*\\)"
+ (rxt-pcre-to-elisp
+ (read-string "Align PCRE regexp: ")))
+ 1 align-default-spacing nil)))))
+
+(defadvice ibuffer-do-replace-regexp
+ (before pcre-mode first (from-str to-str) disable)
+ "Read regexp using PCRE syntax and convert to Elisp equivalent."
+ (interactive
+ (let* ((from-str (read-from-minibuffer "[PCRE] Replace regexp: "))
+ (to-str (read-from-minibuffer (concat "[PCRE] Replace " from-str " with: "))))
+ (list (rxt-pcre-to-elisp from-str) to-str))))
+
+(defadvice find-tag-regexp
+ (before pcre-mode first (regexp &optional next-p other-window) disable)
+ "Read regexp using PCRE syntax and convert to Elisp equivalent.
+Perform `find-tag-regexp' using emulated PCRE regexp syntax."
+ (interactive
+ (let ((args (find-tag-interactive "[PCRE] Find tag regexp: " t)))
+ (list (rxt-pcre-to-elisp (nth 0 args))
+ (nth 1 args) (nth 2 args)))))
+
+(defadvice sort-regexp-fields
+ (before pcre-mode first (reverse record-regexp key-regexp beg end) disable)
+ "Read regexp using PCRE syntax and convert to Elisp equivalent."
+ (interactive "P\nsPCRE regexp specifying records to sort: \n\
+sPCRE regexp specifying key within record: \nr")
+ (ad-set-arg 1 (rxt-pcre-to-elisp (ad-get-arg 1)))
+ (ad-set-arg 2 (rxt-pcre-to-elisp (ad-get-arg 2))))
+
+
+\f
+;;; Commands that take Emacs-style regexps as input
+
+;;;###autoload
+(defun rxt-elisp-to-pcre (regexp)
+ "Translate REGEXP, a regexp in Emacs Lisp syntax, to Perl-compatible syntax.
+
+Interactively, reads the regexp in one of three ways. With a
+prefix arg, reads from minibuffer without string escaping, like
+`query-replace-regexp'. Without a prefix arg, uses the text of
+the region if it is active. Otherwise, uses the result of
+evaluating the sexp before point (which might be a string regexp
+literal or an expression that produces a string).
+
+Displays the translated PCRE regexp in the echo area and copies
+it to the kill ring.
+
+Emacs regexp features such as syntax classes which cannot be
+translated to PCRE will cause an error."
+ (interactive (rxt-interactive/elisp))
+ (rxt-return-pcre (rxt-adt->pcre (rxt-parse-elisp regexp))))
+
+;;;###autoload
+(defun rxt-elisp-to-rx (regexp)
+ "Translate REGEXP, a regexp in Emacs Lisp syntax, to `rx' syntax.
+
+See `rxt-elisp-to-pcre' for a description of the interactive
+behavior and `rx' for documentation of the S-expression based
+regexp syntax."
+ (interactive (rxt-interactive/elisp))
+ (rxt-return-sexp (rxt-adt->rx (rxt-parse-elisp regexp))))
+
+;;;###autoload
+(defun rxt-elisp-to-strings (regexp)
+ "Return a list of all strings matched by REGEXP, an Emacs Lisp regexp.
+
+See `rxt-elisp-to-pcre' for a description of the interactive behavior.
+
+This is useful primarily for getting back the original list of
+strings from a regexp generated by `regexp-opt', but it will work
+with any regexp without unbounded quantifiers (*, +, {2, } and so
+on).
+
+Throws an error if REGEXP contains any infinite quantifiers."
+ (interactive (rxt-interactive/elisp))
+ (rxt-return-sexp (rxt-adt->strings (rxt-parse-elisp regexp))))
+
+;;;###autoload
+(defun rxt-toggle-elisp-rx ()
+ "Toggle the regexp near point between Elisp string and rx syntax."
+ (interactive)
+ ;; First, position point before the regex form near point (either
+ ;; a string literal or a list beginning `rx' or `rx-to-string').
+ (let* ((context (syntax-ppss))
+ (string-start (nth 8 context)))
+ (cond (string-start (goto-char string-start))
+ ((looking-back "\"") (backward-sexp))
+ ((looking-at "\"") nil)
+ (t
+ ;; Search backwards, leaving point in place on error
+ (goto-char
+ (save-excursion
+ (skip-syntax-forward "-")
+ (while (not (looking-at
+ (rx "(" (or "rx" "rx-to-string") symbol-end)))
+ (backward-up-list))
+ (point))))))
+
+ ;; Read and replace the regex following point
+ (let* ((regex (read (current-buffer)))
+ (print-escape-newlines t))
+ (save-excursion
+ (if (listp regex)
+ ;; Replace rx form with string value
+ (prin1 (eval regex) (current-buffer))
+ ;; Pretty-print rx form
+ (save-restriction
+ (let* ((start (point))
+ (rx-syntax (rxt-elisp-to-rx regex))
+ (rx-form
+ (pcase rx-syntax
+ (`(seq . ,rest) `(rx . ,rest))
+ (form `(rx ,form)))))
+ (rxt-print rx-form)
+ (narrow-to-region start (point)))
+ (pp-buffer)
+ ;; remove the extra newline that pp-buffer inserts
+ (goto-char (point-max))
+ (delete-region
+ (point)
+ (save-excursion (skip-chars-backward " \t\n") (point))))))
+ (kill-sexp -1)
+ (indent-pp-sexp)))
+
+
+\f
+;;; Commands that translate PCRE to other formats
+
+;;;###autoload
+(defun rxt-pcre-to-elisp (pcre &optional flags)
+ "Translate PCRE, a regexp in Perl-compatible syntax, to Emacs Lisp.
+
+Interactively, uses the contents of the region if it is active,
+otherwise reads from the minibuffer. Prints the Emacs translation
+in the echo area and copies it to the kill ring.
+
+PCRE regexp features that cannot be translated into Emacs syntax
+will cause an error. See the commentary section of pcre2el.el for
+more details."
+ (interactive (rxt-interactive/pcre))
+ (rxt-return-emacs-regexp
+ (rx-to-string
+ (rxt-pcre-to-rx (rxt--add-flags pcre flags))
+ t)))
+
+;;;###autoload
+(defalias 'pcre-to-elisp 'rxt-pcre-to-elisp)
+
+;;;###autoload
+(defun rxt-pcre-to-rx (pcre &optional flags)
+ "Translate PCRE, a regexp in Perl-compatible syntax, to `rx' syntax.
+
+See `rxt-pcre-to-elisp' for a description of the interactive behavior."
+ (interactive (rxt-interactive/pcre))
+ (rxt-return-sexp (rxt-adt->rx (rxt-parse-pcre (rxt--add-flags pcre flags)))))
+
+;;;###autoload
+(defun rxt-pcre-to-strings (pcre &optional flags)
+ "Return a list of all strings matched by PCRE, a Perl-compatible regexp.
+
+See `rxt-elisp-to-pcre' for a description of the interactive
+behavior and `rxt-elisp-to-strings' for why this might be useful.
+
+Throws an error if PCRE contains any infinite quantifiers."
+ (interactive (rxt-interactive/pcre))
+ (rxt-return-sexp (rxt-adt->strings (rxt-parse-pcre (rxt--add-flags pcre flags)))))
+
+(defun rxt--add-flags (pcre flags)
+ "Prepend FLAGS to PCRE."
+ (if (not (zerop (length flags)))
+ (concat "(?" flags ")" pcre)
+ pcre))
+
+\f
+;;; Regexp explaining functions to display pretty-printed rx syntax
+
+;; When the `rxt-explain' flag is non-nil, `rxt-adt->rx' records
+;; location information for each element of the generated `rx' form,
+;; allowing highlighting corresponding pieces of syntax at point.
+(defvar rxt-explain nil)
+
+(defvar rxt-highlight-overlays nil
+ "List of active location-highlighting overlays in rxt-help-mode buffer.")
+
+;;;###autoload
+(defun rxt-explain-elisp (regexp)
+ "Insert the pretty-printed `rx' syntax for REGEXP in a new buffer.
+
+REGEXP is a regular expression in Emacs Lisp syntax. See
+`rxt-elisp-to-pcre' for a description of how REGEXP is read
+interactively."
+ (interactive (rxt-interactive/elisp))
+ (let ((rxt-explain t)
+ (rxt-verbose-rx-translation rxt-explain-verbosely))
+ (rxt-pp-rx regexp (rxt-elisp-to-rx regexp))))
+
+;;;###autoload
+(defun rxt-explain-pcre (regexp &optional flags)
+ "Insert the pretty-printed `rx' syntax for REGEXP in a new buffer.
+
+REGEXP is a regular expression in PCRE syntax. See
+`rxt-pcre-to-elisp' for a description of how REGEXP is read
+interactively."
+ (interactive (rxt-interactive/pcre))
+ (let ((rxt-explain t)
+ (rxt-verbose-rx-translation rxt-explain-verbosely))
+ (rxt-pp-rx regexp (rxt-pcre-to-rx regexp flags))))
+
+;;;###autoload
+(defun rxt-quote-pcre (text)
+ "Return a PCRE regexp which matches TEXT literally.
+
+Any PCRE metacharacters in TEXT will be quoted with a backslash."
+ (rxt-adt->pcre (rxt-string text)))
+
+\f
+;;;; Commands that depend on the major mode in effect
+
+;; Macro: interactively call one of two functions depending on the
+;; major mode
+(defmacro rxt-mode-dispatch (elisp-function pcre-function)
+ `(if (memq major-mode '(emacs-lisp-mode lisp-interaction-mode))
+ (call-interactively #',elisp-function)
+ (call-interactively #',pcre-function)))
+
+;;;###autoload
+(defun rxt-explain ()
+ "Pop up a buffer with pretty-printed `rx' syntax for the regex at point.
+
+Chooses regex syntax to read based on current major mode, calling
+`rxt-explain-elisp' if buffer is in `emacs-lisp-mode' or
+`lisp-interaction-mode', or `rxt-explain-pcre' otherwise."
+ (interactive)
+ (rxt-mode-dispatch rxt-explain-elisp rxt-explain-pcre))
+
+;;;###autoload
+(defun rxt-convert-syntax ()
+ "Convert regex at point to other kind of syntax, depending on major mode.
+
+For buffers in `emacs-lisp-mode' or `lisp-interaction-mode',
+calls `rxt-elisp-to-pcre' to convert to PCRE syntax. Otherwise,
+calls `rxt-pcre-to-elisp' to convert to Emacs syntax.
+
+The converted syntax is displayed in the echo area and copied to
+the kill ring; see the two functions named above for details."
+ (interactive)
+ (rxt-mode-dispatch rxt-elisp-to-pcre rxt-pcre-to-elisp))
+
+;;;###autoload
+(defun rxt-convert-to-rx ()
+ "Convert regex at point to RX syntax. Chooses Emacs or PCRE syntax by major mode."
+ (interactive)
+ (rxt-mode-dispatch rxt-elisp-to-rx rxt-pcre-to-rx))
+
+;;;###autoload
+(defun rxt-convert-to-strings ()
+ "Convert regex at point to RX syntax. Chooses Emacs or PCRE syntax by major mode."
+ (interactive)
+ (rxt-mode-dispatch rxt-elisp-to-strings rxt-pcre-to-strings))
+
+
+\f
+;;; Minor mode and keybindings
+
+(defvar rxt-mode-map
+ (let ((map (make-sparse-keymap)))
+ ;; Generic
+ (define-key map (kbd "C-c / /") 'rxt-explain)
+ (define-key map (kbd "C-c / c") 'rxt-convert-syntax)
+ (define-key map (kbd "C-c / x") 'rxt-convert-to-rx)
+ (define-key map (kbd "C-c / '") 'rxt-convert-to-strings)
+
+ ;; From PCRE
+ (define-key map (kbd "C-c / p /") 'rxt-explain-pcre)
+ (define-key map (kbd "C-c / p e") 'rxt-pcre-to-elisp)
+ (define-key map (kbd "C-c / p x") 'rxt-pcre-to-rx)
+ (define-key map (kbd "C-c / p '") 'rxt-pcre-to-strings)
+
+ ;; From Elisp
+ (define-key map (kbd "C-c / e /") 'rxt-explain-elisp)
+ (define-key map (kbd "C-c / e p") 'rxt-elisp-to-pcre)
+ (define-key map (kbd "C-c / e x") 'rxt-elisp-to-rx)
+ (define-key map (kbd "C-c / e '") 'rxt-elisp-to-strings)
+ (define-key map (kbd "C-c / e t") 'rxt-toggle-elisp-rx)
+ (define-key map (kbd "C-c / t") 'rxt-toggle-elisp-rx)
+
+ ;; Search
+ (define-key map (kbd "C-c / %") 'pcre-query-replace-regexp)
+
+ map)
+ "Keymap for `rxt-mode'.")
+
+;;;###autoload
+(define-minor-mode rxt-mode
+ "Regex translation utilities." nil nil)
+
+;;;###autoload
+(defun turn-on-rxt-mode ()
+ "Turn on `rxt-mode' in the current buffer."
+ (interactive)
+ (rxt-mode 1))
+
+;;;###autoload
+(define-globalized-minor-mode rxt-global-mode rxt-mode
+ turn-on-rxt-mode)
+
+\f
+;;;; Syntax explanations
+
+;; Major mode for displaying pretty-printed S-exp syntax
+(define-derived-mode rxt-help-mode emacs-lisp-mode "Regexp Explain"
+ (setq buffer-read-only t)
+ (add-hook 'post-command-hook 'rxt-highlight-text nil t)
+ (rxt-highlight-text))
+
+;; Hack: stop paredit-mode interfering with `rxt-print'
+(eval-when-compile (declare-function paredit-mode "paredit.el"))
+(add-hook 'rxt-help-mode-hook
+ (lambda ()
+ (if (and (boundp 'paredit-mode)
+ paredit-mode)
+ (paredit-mode 0))))
+
+(define-key rxt-help-mode-map "q" 'quit-window)
+(define-key rxt-help-mode-map "z" 'kill-this-buffer)
+(define-key rxt-help-mode-map "n" 'next-line)
+(define-key rxt-help-mode-map "p" 'previous-line)
+(define-key rxt-help-mode-map "f" 'forward-list)
+(define-key rxt-help-mode-map "b" 'backward-list)
+(define-key rxt-help-mode-map "u" 'backward-up-list)
+(define-key rxt-help-mode-map "d" 'down-list)
+
+(defvar rxt--print-with-overlays nil)
+(defvar rxt--print-depth 0)
+
+(defconst rxt--print-char-alist
+ '((?\a . "\\a")
+ (?\b . "\\b")
+ (?\t . "\\t")
+ (?\n . "\\n")
+ (?\v . "\\v")
+ (?\f . "\\f")
+ (?\r . "\\r")
+ (?\e . "\\e")
+ (?\s . "\\s")
+ (?\\ . "\\\\")
+ (?\d . "\\d"))
+ "Alist of characters to print using an escape sequence in Elisp source.
+See (info \"(elisp) Basic Char Syntax\").")
+
+(defconst rxt--whitespace-display-regexp
+ (rx-to-string `(any ,@(mapcar #'car rxt--print-char-alist))))
+
+(defconst rxt--print-special-chars
+ '(?\( ?\) ?\\ ?\| ?\; ?\' ?\` ?\" ?\# ?\. ?\,)
+ "Characters which require a preceding backslash in Elisp source.
+See (info \"(elisp) Basic Char Syntax\").")
+
+(defun rxt-pp-rx (regexp rx)
+ "Display string regexp REGEXP with its `rx' form RX in an `rxt-help-mode' buffer."
+ (with-current-buffer (get-buffer-create "* Regexp Explain *")
+ (let ((print-escape-newlines t)
+ (inhibit-read-only t))
+ (erase-buffer)
+ (rxt-help-mode)
+ (insert (rxt--propertize-whitespace regexp))
+ (newline 2)
+ (save-excursion
+ (let ((sexp-begin (point))
+ (rxt--print-with-overlays t))
+ (rxt-print rx)
+ (narrow-to-region sexp-begin (point))
+ (pp-buffer)
+ (widen)))
+ (rxt-highlight-text))
+ (pop-to-buffer (current-buffer))))
+
+(cl-defun rxt-print (rx)
+ "Insert RX, an `rx' form, into the current buffer, optionally adding overlays.
+
+Similar to `print' or `prin1', but ensures that `rx' forms are
+printed readably, using character or integer syntax depending on
+context.
+
+If `rxt--print-with-overlays' is non-nil, also creates overlays linking
+elements of RX to their corresponding locations in the source
+string (see `rxt-explain-elisp', `rxt-explain-pcre' and
+`rxt--make-help-overlays')."
+ (let ((start (point)))
+ (cl-typecase rx
+ (cons
+ (pcase rx
+ (`(,(and (or `repeat `**) head)
+ ,(and (pred integerp) from)
+ ,(and (pred integerp) to)
+ . ,rest)
+ (insert (format "(%s %d %d" head from to))
+ (rxt--print-list-tail rest))
+ (`(,(and (or `repeat `= `>=) head)
+ ,(and (pred integerp) n)
+ . ,rest)
+ (insert (format "(%s %d" head n))
+ (rxt--print-list-tail rest))
+ (_
+ (rxt--print-list-tail rx t))))
+ (symbol
+ (cl-case rx
+ ;; `print' escapes the ? characters in the rx operators *?
+ ;; and +?, but this looks bad and is not strictly necessary:
+ ;; (eq (read "*?") (read "*\\?")) => t
+ ;; (eq (read "+?") (read "+\\?")) => t
+ ((*? +?) (insert (symbol-name rx)))
+ (t (prin1 rx (current-buffer)))))
+ (string
+ (insert (rxt--propertize-whitespace (prin1-to-string rx))))
+ (character
+ (cond
+ ((eq ? rx)
+ (insert "?"))
+ ((memq rx rxt--print-special-chars)
+ (insert "?\\" rx))
+ ((assq rx rxt--print-char-alist)
+ (insert "?" (assoc-default rx rxt--print-char-alist)))
+ (t
+ (insert "?" (char-to-string rx)))))
+ (t
+ (prin1 rx (current-buffer))))
+ (when rxt--print-with-overlays
+ (rxt--make-help-overlays rx start (point)))))
+
+(defun rxt--print-list-tail (tail &optional open-paren)
+ (let ((rxt--print-depth (1+ rxt--print-depth)))
+ (let ((done nil))
+ (while (not done)
+ (cl-typecase tail
+ (null
+ (insert ")")
+ (setq done t))
+ (cons
+ (if open-paren
+ (progn
+ (insert "(")
+ (setq open-paren nil))
+ (insert " "))
+ (rxt-print (car tail))
+ (setq tail (cdr tail)))
+ (t
+ (insert " . ")
+ (rxt-print tail)
+ (insert ")")
+ (setq done t)))))))
+
+(defun rxt--make-help-overlays (rx start end)
+ (let ((location (rxt-location rx)))
+ (when (and location
+ (rxt-location-start location)
+ (rxt-location-end location))
+ (let* ((sexp-begin (copy-marker start t))
+ (sexp-end (copy-marker end))
+ (sexp-bounds (list sexp-begin sexp-end))
+
+ (source-begin (1+ (rxt-location-start location)))
+ (source-end (1+ (rxt-location-end location)))
+ (source-bounds (list source-begin source-end))
+
+ (bounds (list source-bounds sexp-bounds))
+
+ (sexp-ol (make-overlay sexp-begin sexp-end (current-buffer) t nil))
+ (source-ol (make-overlay source-begin source-end (current-buffer) t nil)))
+ (dolist (ol (list sexp-ol source-ol))
+ (overlay-put ol 'priority rxt--print-depth)
+ (overlay-put ol 'rxt-bounds bounds))))))
+
+(defun rxt--propertize-whitespace (string)
+ (let ((string (copy-sequence string))
+ (start 0))
+ (while (string-match rxt--whitespace-display-regexp string start)
+ (put-text-property (match-beginning 0) (match-end 0)
+ 'display
+ (assoc-default (string-to-char (match-string 0 string))
+ rxt--print-char-alist)
+ string)
+ (setq start (match-end 0)))
+ string))
+
+(defun rxt-highlight-text ()
+ "Highlight the regex syntax at point and its corresponding RX/string form."
+ (let ((all-bounds (get-char-property (point) 'rxt-bounds)))
+ (mapc #'delete-overlay rxt-highlight-overlays)
+ (setq rxt-highlight-overlays nil)
+ (dolist (bounds all-bounds)
+ (cl-destructuring-bind (begin end) bounds
+ (let ((overlay (make-overlay begin end)))
+ (push overlay rxt-highlight-overlays)
+ (overlay-put overlay 'face 'rxt-highlight-face))))))
+
+\f
+;;;; Error handling
+
+(if (fboundp 'define-error)
+ (define-error 'rxt-invalid-regexp "Invalid regexp" 'invalid-regexp)
+ (put 'rxt-invalid-regexp
+ 'error-conditions
+ '(rxt-invalid-regexp invalid-regexp error))
+ (put 'rxt-invalid-regexp 'error-message "Invalid regexp"))
+
+(defun rxt-error (&rest format-args)
+ (signal 'rxt-invalid-regexp (list (apply #'format format-args))))
+
+\f
+;;;; Regexp syntax tree data type
+
+;; Base class from which other elements of the syntax-tree inherit
+(cl-defstruct rxt-syntax-tree)
+
+;; Struct representing the original source location
+(cl-defstruct rxt-location
+ source ; Either a string or a buffer
+ start end ; Offsets, 0- or 1-indexed as appropriate
+ )
+
+(defun rxt-location-text (location)
+ (if (not (rxt-location-p location))
+ nil
+ (let ((start (rxt-location-start location))
+ (end (rxt-location-end location))
+ (source (rxt-location-source location)))
+ (cond
+ ((buffer-live-p source)
+ (with-current-buffer source
+ (buffer-substring-no-properties start end)))
+ ((stringp source)
+ (substring source start end))
+ (t nil)))))
+
+;; Hash table mapping from syntax-tree elements to source locations.
+(defvar rxt-location-map (make-hash-table :weakness 'key))
+
+(defun rxt-location (object)
+ (gethash object rxt-location-map))
+
+(gv-define-setter rxt-location (value object)
+ `(puthash ,object ,value rxt-location-map))
+
+(defun rxt-source-text (object)
+ (rxt-location-text (rxt-location object)))
+
+(defun rxt-to-string (tree)
+ "Return a readable representation of TREE, a regex syntax-tree object."
+ (or (rxt-source-text tree)
+ (let ((print-level 1))
+ (prin1-to-string tree))))
+(defalias 'rxt-syntax-tree-readable 'rxt-to-string)
+
+;; FIXME
+(defvar rxt-pcre-case-fold nil)
+
+;; Literal string
+(cl-defstruct
+ (rxt-string
+ (:constructor rxt-string (chars &optional case-fold))
+ (:include rxt-syntax-tree))
+ chars
+ (case-fold rxt-pcre-case-fold))
+
+(defun rxt-empty-string ()
+ (rxt-string ""))
+
+(defun rxt-trivial-p (re)
+ (and (rxt-string-p re)
+ (equal (rxt-string-chars re) "")))
+
+;;; Other primitives
+(cl-defstruct (rxt-primitive
+ (:constructor rxt-primitive (pcre rx))
+ (:include rxt-syntax-tree))
+ pcre rx)
+
+(defun rxt-bos () (rxt-primitive "\\A" 'bos))
+(defun rxt-eos () (rxt-primitive "\\Z" 'eos))
+
+(defun rxt-bol () (rxt-primitive "^" 'bol))
+(defun rxt-eol () (rxt-primitive "$" 'eol))
+
+;; FIXME
+(defun rxt-anything () (rxt-primitive "." 'anything))
+(defun rxt-nonl () (rxt-primitive "." 'nonl))
+
+(defun rxt-word-boundary () (rxt-primitive "\\b" 'word-boundary))
+(defun rxt-not-word-boundary () (rxt-primitive "\\B" 'not-word-boundary))
+
+(defun rxt-wordchar () (rxt-primitive "\\w" 'wordchar))
+(defun rxt-not-wordchar () (rxt-primitive "\\W" 'not-wordchar))
+
+(defun rxt-symbol-start () (rxt-primitive nil 'symbol-start))
+(defun rxt-symbol-end () (rxt-primitive nil 'symbol-end))
+
+(defun rxt-bow () (rxt-primitive nil 'bow))
+(defun rxt-eow () (rxt-primitive nil 'eow))
+
+;;; Sequence
+(cl-defstruct
+ (rxt-seq
+ (:constructor make-rxt-seq (elts))
+ (:include rxt-syntax-tree))
+ elts)
+
+;; Slightly smart sequence constructor:
+;; - Flattens nested sequences
+;; - Drops trivial "" elements
+;; - Empty sequence => ""
+;; - Singleton sequence is reduced to its one element.
+(defun rxt-seq (&rest res) ; Flatten nested seqs & drop ""'s.
+ (let ((res (rxt-seq-flatten res)))
+ (if (consp res)
+ (if (consp (cdr res))
+ (make-rxt-seq res) ; General case
+ (car res)) ; Singleton sequence
+ (rxt-empty-string)))) ; Empty seq -- ""
+
+(defun rxt-seq-flatten (res)
+ (if (consp res)
+ (let ((re (car res))
+ (tail (rxt-seq-flatten (cdr res))))
+ (cond ((rxt-seq-p re) ; Flatten nested seqs
+ (append (rxt-seq-flatten (rxt-seq-elts re)) tail))
+ ((rxt-trivial-p re) tail) ; Drop trivial elts
+ ((and (rxt-string-p re) ; Flatten strings
+ (consp tail)
+ (rxt-string-p (car tail)))
+ (cons
+ (rxt-string-concat re (car tail))
+ (cdr tail)))
+ (t (cons re tail))))
+ '()))
+
+(defun rxt-string-concat (str1 str2)
+ (if (not (eq (rxt-string-case-fold str1)
+ (rxt-string-case-fold str2)))
+ (make-rxt-seq (list str1 str2))
+ (let ((result
+ (rxt-string (concat (rxt-string-chars str1)
+ (rxt-string-chars str2))
+ (rxt-string-case-fold str1)))
+ (first (rxt-location str1))
+ (last (rxt-location str2)))
+ (when (and first last)
+ (setf (rxt-location result)
+ (make-rxt-location :source (rxt-location-source first)
+ :start (rxt-location-start first)
+ :end (rxt-location-end last))))
+ result)))
+
+;;; Choice (alternation/union)
+(cl-defstruct
+ (rxt-choice
+ (:constructor make-rxt-choice (elts))
+ (:include rxt-syntax-tree))
+ elts)
+
+;;; The empty choice represents a regexp that never matches in any context
+(defvar rxt-empty (make-rxt-choice nil))
+(defun rxt-empty-p (re)
+ (or
+ (and (rxt-choice-p re)
+ (null (rxt-choice-elts re)))
+ (rxt-empty-char-set-p re)))
+
+(defun rxt-choice (&rest alternatives)
+ "Construct the alternation (union) of several regexps.
+
+ALTERNATIVES should be a list of `rxt-syntax-tree' objects.
+The return value is an `rxt-choice' object representing a regexp
+which matches any one of ALTERNATIVES, but simplified in the
+following ways:
+
+- If ALTERNATIVES contains only one element, it is returned unchanged.
+
+- All existing `rxt-choice' elements in ALTERNATIVES are replaced
+ by a flat list of their subexpressions: symbolically,
+ a|(b|(c|d)) is replaced by a|b|c|d
+
+- All character sets and single-character strings in ALTERNATIVES
+ are combined together into one or two character sets,
+ respecting case-folding behaviour."
+ (cl-destructuring-bind (other-elements char-set case-fold-char-set)
+ (rxt--simplify-alternatives alternatives)
+ (let ((simplified-alternatives
+ (append (if (not (rxt-empty-p char-set))
+ (list char-set)
+ '())
+ (if (not (rxt-empty-p case-fold-char-set))
+ (list case-fold-char-set)
+ '())
+ other-elements)))
+ (pcase simplified-alternatives
+ (`()
+ rxt-empty)
+ (`(,element)
+ element)
+ (_
+ (make-rxt-choice simplified-alternatives))))))
+
+(defun rxt--simplify-alternatives (alternatives)
+ "Simplify a set of regexp alternatives.
+
+ALTERNATIVES should be a list of `rxt-syntax-tree' objects to be combined
+into an `rxt-choice' structure. The result is a three-element
+list (OTHER-ELEMENTS CHAR-SET CASE-FOLDED-CHAR-SET):
+
+- CHAR-SET is an `rxt-char-set-union' containing the union of all
+ case-sensitive character sets and single-character strings in
+ RES.
+
+- CASE-FOLDED-CHAR-SET is similar but combines all the
+ case-insensitive character sets and single-character strings.
+
+- OTHER-ELEMENTS is a list of all other elements, with all
+ `rxt-choice' structures replaced by a flat list of their
+ component subexpressions."
+ (if (null alternatives)
+ (list '()
+ (make-rxt-char-set-union :case-fold nil)
+ (make-rxt-char-set-union :case-fold t))
+ (let* ((re (car alternatives)))
+ (cl-destructuring-bind (tail char-set case-fold-char-set)
+ (rxt--simplify-alternatives (cdr alternatives))
+ (cond ((rxt-choice-p re) ; Flatten nested choices
+ (list
+ (append (rxt-choice-elts re) tail)
+ char-set
+ case-fold-char-set))
+
+ ((rxt-empty-p re) ; Drop empty re's.
+ (list tail char-set case-fold-char-set))
+
+ ((rxt-char-set-union-p re) ; Fold char sets together
+ (if (rxt-char-set-union-case-fold re)
+ (list tail
+ char-set
+ (rxt-char-set-union case-fold-char-set re))
+ (list tail
+ (rxt-char-set-union char-set re)
+ case-fold-char-set)))
+
+ ((and (rxt-string-p re) ; Same for 1-char strings
+ (= 1 (length (rxt-string-chars re))))
+ (if (rxt-string-case-fold re)
+ (list tail
+ char-set
+ (rxt-char-set-union case-fold-char-set re))
+ (list tail
+ (rxt-char-set-union char-set re)
+ case-fold-char-set)))
+
+ (t ; Otherwise.
+ (list (cons re tail) char-set case-fold-char-set)))))))
+
+;;; Repetition
+(cl-defstruct (rxt-repeat
+ (:include rxt-syntax-tree))
+ from to body greedy)
+
+(cl-defun rxt-repeat (from to body &optional (greedy t))
+ (if (equal to 0)
+ (rxt-empty-string)
+ (make-rxt-repeat :from from :to to
+ :body body :greedy greedy)))
+
+;;; Submatch
+(cl-defstruct
+ (rxt-submatch
+ (:constructor rxt-submatch (body))
+ (:include rxt-syntax-tree))
+ body)
+
+;;; Numbered submatch (Emacs only)
+(cl-defstruct
+ (rxt-submatch-numbered
+ (:constructor rxt-submatch-numbered (n body))
+ (:include rxt-syntax-tree))
+ n
+ body)
+
+;;; Backreference
+(cl-defstruct
+ (rxt-backref
+ (:constructor rxt-backref (n))
+ (:include rxt-syntax-tree))
+ n)
+
+;;; Syntax classes (Emacs only)
+(cl-defstruct (rxt-syntax-class
+ (:include rxt-syntax-tree))
+ symbol)
+
+(defun rxt-syntax-class (symbol)
+ (if (assoc symbol rx-syntax)
+ (make-rxt-syntax-class :symbol symbol)
+ (rxt-error "Invalid syntax class symbol `%s'" symbol)))
+
+;;; Character categories (Emacs only)
+(cl-defstruct (rxt-char-category
+ (:include rxt-syntax-tree))
+ symbol)
+
+(defun rxt-char-category (symbol)
+ (if (assoc symbol rx-categories)
+ (make-rxt-char-category :symbol symbol)
+ (rxt-error "Invalid character category symbol `%s'" symbol)))
+
+
+;;; Char sets
+;; <rxt-char-set> ::= <rxt-char-set-union>
+;; | <rxt-char-set-negation>
+;; | <rxt-char-set-intersection>
+
+(cl-defstruct (rxt-char-set (:include rxt-syntax-tree)))
+
+;; An rxt-char-set-union represents the union of any number of
+;; characters, character ranges, and POSIX character classes: anything
+;; that can be represented in string notation as a class [ ... ]
+;; without the negation operator.
+(cl-defstruct (rxt-char-set-union
+ (:include rxt-char-set))
+ chars ; list of single characters
+ ranges ; list of ranges (from . to)
+ classes ; list of character classes
+ (case-fold rxt-pcre-case-fold))
+
+;; Test for empty character set
+(defun rxt-empty-char-set-p (cset)
+ (and (rxt-char-set-union-p cset)
+ (null (rxt-char-set-union-chars cset))
+ (null (rxt-char-set-union-ranges cset))
+ (null (rxt-char-set-union-classes cset))))
+
+;; Simple union constructor
+(defun rxt-char-set-union (&rest items)
+ "Construct an regexp character set representing the union of ITEMS.
+
+Each element of ITEMS may be either: a character; a
+single-character string; a single-character `rxt-string' object;
+a cons, (FROM . TO) representing a range of characters; a symbol,
+representing a named character class; or an `rxt-char-set-union'
+object. All `rxt-char-set-union' objects in ITEMS must have the
+same `case-fold' property."
+ (let ((chars '())
+ (ranges '())
+ (classes '())
+ (case-fold 'undetermined))
+ (dolist (item items)
+ (cl-etypecase item
+ (character
+ (push item chars))
+
+ (string
+ (cl-assert (= 1 (length item)))
+ (push (string-to-char item) chars))
+
+ (rxt-string
+ (cl-assert (= 1 (length (rxt-string-chars item))))
+ (push (string-to-char (rxt-string-chars item)) chars))
+
+ (cons ; range (from . to)
+ (cl-check-type (car item) character)
+ (cl-check-type (cdr item) character)
+ (push item ranges))
+
+ (symbol ; named character class
+ (push item classes))
+
+ (rxt-char-set-union
+ (if (eq case-fold 'undetermined)
+ (setq case-fold (rxt-char-set-union-case-fold item))
+ (unless (eq case-fold (rxt-char-set-union-case-fold item))
+ (error "Cannot construct union of char-sets with unlike case-fold setting: %S" item)))
+ (setq chars (nconc chars (rxt-char-set-union-chars item)))
+ (setq ranges (nconc ranges (rxt-char-set-union-ranges item)))
+ (setq classes (nconc classes (rxt-char-set-union-classes item))))))
+
+ (make-rxt-char-set-union :chars chars :ranges ranges :classes classes
+ :case-fold (if (eq case-fold 'undetermined)
+ rxt-pcre-case-fold
+ case-fold))))
+
+(defun rxt--all-char-set-union-chars (char-set)
+ "Return a list of all characters in CHAR-SET."
+ (cl-assert (rxt-char-set-union-p char-set))
+ (append
+ (rxt-char-set-union-chars char-set)
+ (cl-loop for (start . end) in (rxt-char-set-union-ranges char-set)
+ nconc (cl-loop for char from start to end collect char))))
+
+(defun rxt--simplify-char-set (char-set &optional case-fold-p)
+ "Return a minimal char-set to match the same characters as CHAR-SET.
+
+With optional argument CASE-FOLD-P, return a char-set which
+emulates case-folding behaviour by including both uppercase and
+lowercase versions of all characters in CHAR-SET."
+ (cl-assert (rxt-char-set-union-p char-set))
+ (let* ((classes (rxt-char-set-union-classes char-set))
+ (all-chars
+ (if case-fold-p
+ (cl-loop for char in (rxt--all-char-set-union-chars char-set)
+ nconc (list (upcase char) (downcase char)))
+ (rxt--all-char-set-union-chars char-set)))
+ (all-ranges
+ (rxt--extract-ranges (rxt--remove-redundant-chars all-chars classes))))
+ (let ((singletons nil)
+ (ranges nil))
+ (cl-loop for (start . end) in all-ranges
+ do
+ (cond ((= start end) (push start singletons))
+ ((= (1+ start) end)
+ (push start singletons)
+ (push end singletons))
+ (t (push (cons start end) ranges))))
+ (make-rxt-char-set-union :chars (nreverse singletons)
+ :ranges (nreverse ranges)
+ :classes classes
+ :case-fold (if case-fold-p
+ nil
+ (rxt-char-set-union-case-fold char-set))))))
+
+(defun rxt--remove-redundant-chars (chars classes)
+ "Remove all characters which match a character class in CLASSES from CHARS."
+ (if (null classes)
+ chars
+ (string-to-list
+ (replace-regexp-in-string
+ (rx-to-string `(any ,@classes))
+ ""
+ (apply #'string chars)))))
+
+(defun rxt--extract-ranges (chars)
+ "Return a list of all contiguous ranges in CHARS.
+
+CHARS should be a list of characters (integers). The return
+value is a list of conses (START . END) representing ranges, such
+that the union of all the ranges represents the same of
+characters as CHARS.
+
+Example:
+ (rxt--extract-ranges (list ?a ?b ?c ?q ?x ?y ?z))
+ => ((?a . ?c) (?q . ?q) (?x . ?z))"
+ (let ((array
+ (apply #'vector
+ (cl-remove-duplicates
+ (sort (copy-sequence chars) #'<)))))
+ (cl-labels
+ ((recur (start end)
+ (if (< end start)
+ nil
+ (let ((min (aref array start))
+ (max (aref array end)))
+ (if (= (- max min) (- end start))
+ (list (cons min max))
+ (let* ((split-point (/ (+ start end) 2))
+ (left (recur start split-point))
+ (right (recur (1+ split-point) end)))
+ (merge left right))))))
+ (merge (left right)
+ (cond ((null left) right)
+ ((null right) left)
+ (t
+ (let ((last-left (car (last left)))
+ (first-right (car right)))
+ (if (= (1+ (cdr last-left))
+ (car first-right))
+ (append (cl-subseq left 0 -1)
+ (list
+ (cons (car last-left)
+ (cdr first-right)))
+ (cl-subseq right 1))
+ (append left right)))))))
+ (recur 0 (1- (length array))))))
+
+;;; Set complement of character set, syntax class, or character
+;;; category
+
+;; In general, all character sets that can be represented in string
+;; notation as [^ ... ] (but see `rxt-char-set-intersection', below), plus
+;; Emacs' \Sx and \Cx constructions.
+(cl-defstruct (rxt-char-set-negation
+ (:include rxt-char-set))
+ elt)
+
+(defun rxt-negate (char-set)
+ "Construct the logical complement (negation) of CHAR-SET.
+
+CHAR-SET may be any of the following types: `rxt-char-set-union',
+`rxt-syntax-class', `rxt-char-category', or `rxt-char-set-negation'."
+ (cl-etypecase char-set
+ ((or rxt-char-set-union rxt-syntax-class rxt-char-category)
+ (make-rxt-char-set-negation :elt char-set))
+
+ (rxt-char-set-negation
+ (rxt-char-set-negation-elt char-set))))
+
+;;; Intersections of char sets
+
+;; These are difficult to represent in general, but can be constructed
+;; in Perl using double negation; for example: [^\Wabc] means the set
+;; complement of [abc] with respect to the universe of "word
+;; characters": (& (~ (~ word)) (~ ("abc"))) == (& word (~ ("abc")))
+;; == (- word ("abc"))
+
+(cl-defstruct (rxt-char-set-intersection
+ (:include rxt-char-set))
+ elts)
+
+;; Intersection constructor
+(defun rxt-char-set-intersection (&rest charsets)
+ (let ((elts '())
+ (cmpl (make-rxt-char-set-union)))
+ (dolist (cset (rxt-int-flatten charsets))
+ (cond
+ ((rxt-char-set-negation-p cset)
+ ;; Fold negated charsets together: ~A & ~B = ~(A|B)
+ (setq cmpl (rxt-char-set-union cmpl (rxt-char-set-negation-elt cset))))
+
+ ((rxt-char-set-union-p cset)
+ (push cset elts))
+
+ (t
+ (rxt-error "Can't take intersection of non-character-set %S" cset))))
+
+ (if (null elts)
+ (rxt-negate cmpl)
+ (unless (rxt-empty-char-set-p cmpl)
+ (push (rxt-negate cmpl) elts))
+ (if (null (cdr elts))
+ (car elts) ; singleton case
+ (make-rxt-char-set-intersection :elts elts)))))
+
+;; Constructor helper: flatten nested intersections
+(defun rxt-int-flatten (csets)
+ (if (consp csets)
+ (let ((cset (car csets))
+ (tail (rxt-int-flatten (cdr csets))))
+ (if (rxt-char-set-intersection-p cset)
+ (append (rxt-int-flatten (rxt-char-set-intersection-elts cset)) tail)
+ (cons cset tail)))
+ '()))
+
+
+\f
+;;;; Macros for building the parser
+
+(defmacro rxt-token-case (&rest cases)
+ "Consume a token at point and evaluate corresponding forms.
+
+CASES is a list of `cond'-like clauses, (REGEXP BODY ...) where
+the REGEXPs define possible tokens which may appear at point. The
+CASES are considered in order. For each case, if the text at
+point matches REGEXP, then point is moved to the end of the
+matched token, the corresponding BODY is evaluated and their
+value returned. The matched token is available within the BODY
+forms as (match-string 0).
+
+There can be a default case where REGEXP is `t', which evaluates
+the corresponding FORMS but does not move point.
+
+Returns `nil' if none of the CASES matches."
+ (declare (debug (&rest (sexp &rest form))))
+ `(cond
+ ,@(cl-loop for (token . action) in cases
+ collect
+ (if (eq token t)
+ `(t ,@action)
+ `((looking-at ,token)
+ (goto-char (match-end 0))
+ ,@action)))))
+
+(defmacro rxt-with-source-location (&rest body)
+ "Evaluate BODY and record source location information on its value.
+
+BODY may evaluate to any kind of object, but its value should
+generally not be `eq' to any other object."
+ (declare (debug (&rest form)))
+ (let ((begin (make-symbol "begin"))
+ (value (make-symbol "value")))
+ `(let ((,begin (point))
+ (,value ,(macroexp-progn body)))
+ (setf (rxt-location ,value)
+ (make-rxt-location :source rxt-source-text-string
+ :start (1- ,begin)
+ :end (1- (point))))
+ ,value)))
+
+;; Read PCRE + flags
+(defun rxt-read-delimited-pcre ()
+ "Read a Perl-style delimited regexp and flags from the current buffer.
+
+Point should be before the regexp literal before calling
+this. Currently only regexps delimited by / ... / are supported.
+A preceding \"m\", \"qr\" or \"s\" will be ignored, as will the
+replacement string in an s/.../.../ construction.
+
+Returns two strings: the regexp and the flags."
+ (save-excursion
+ (skip-syntax-forward "-")
+
+ ;; Skip m, qr, s
+ (let ((is-subst (rxt-token-case
+ ("s" t)
+ ((rx (or "m" "qr")) nil))))
+
+ (when (not (looking-at "/"))
+ (error "Only Perl regexps delimited by slashes are supported"))
+ (let ((beg (match-end 0))
+ (delim (rx (not (any "\\"))
+ (group "/"))))
+ (search-forward-regexp delim)
+ (let ((end (match-beginning 1)))
+ (when is-subst (search-forward-regexp delim))
+ (let ((pcre (buffer-substring-no-properties beg end)))
+ (rxt-token-case
+ ("[gimosx]*"
+ (rxt--add-flags pcre (match-string-no-properties 0))))))))))
+
+\f
+;;;; Elisp and PCRE string notation parser
+
+;;; Parser constants
+(defconst rxt-pcre-char-set-alist
+ `((?w . ; "word" characters
+ (?_ alnum))
+ (?d . ; digits
+ (digit))
+ (?h . ; horizontal whitespace
+ (#x0009 #x0020 #x00A0 #x1680 #x180E #x2000 #x2001 #x2002 #x2003
+ #x2004 #x2005 #x2006 #x2007 #x2008 #x2009 #x200A #x202F
+ #x205F #x3000))
+ (?s . ; whitespace
+ (9 10 12 13 32))
+ (?v . ; vertical whitespace
+ (#x000A #x000B #x000C #x000D #x0085 #x2028 #x2029))))
+
+(defconst rxt-pcre-named-classes-regexp
+ (rx "[:"
+ (submatch
+ (or "alnum" "alpha" "ascii" "blank" "cntrl" "digit" "graph" "lower"
+ "print" "punct" "space" "upper" "word" "xdigit"))
+ ":]"))
+
+(defconst rxt-elisp-named-classes-regexp
+ (rx "[:"
+ (submatch
+ (or "alnum" "alpha" "ascii" "blank" "cntrl" "digit" "graph" "lower"
+ "print" "punct" "space" "upper" "word" "xdigit"
+ "unibyte" "nonascii" "multibyte"))
+ ":]"))
+
+;;; The following dynamically bound variables control the operation of
+;;; the parser (see `rxt-parse-re'.)
+(defvar rxt-parse-pcre nil
+ "t if the rxt string parser is parsing PCRE syntax, nil for Elisp syntax.
+
+This should only be let-bound internally, never set otherwise.")
+
+(defvar rxt-pcre-extended-mode nil
+ "t if the rxt string parser is emulating PCRE's \"extended\" mode.
+
+In extended mode (indicated by /x in Perl/PCRE), whitespace
+outside of character classes and \\Q...\\E quoting is ignored,
+and a `#' character introduces a comment that extends to the end
+of line.")
+
+(defvar rxt-pcre-s-mode nil
+ "t if the rxt string parser is emulating PCRE's single-line \"/s\" mode.
+
+When /s is used, PCRE's \".\" matches newline characters, which
+otherwise it would not match.")
+
+(defvar rxt-pcre-case-fold nil
+ "non-nil to emulate PCRE's case-insensitive \"/i\" mode in translated regexps.")
+
+(defvar rxt-branch-end-regexp nil)
+(defvar rxt-choice-regexp nil)
+(defvar rxt-brace-begin-regexp nil)
+(defvar rxt-m-to-n-brace-regexp nil)
+(defvar rxt-m-to-?-brace-regexp nil)
+(defvar rxt-m-brace-regexp nil)
+(defvar rxt-named-classes-regexp nil)
+
+(defvar rxt-subgroup-count nil)
+(defvar rxt-source-text-string nil)
+
+(defun rxt-parse-pcre (re)
+ (rxt-parse-re re t))
+
+(defun rxt-parse-elisp (re)
+ (rxt-parse-re re nil))
+
+(defun rxt-parse-re (re pcre-p)
+ (let* ((rxt-parse-pcre pcre-p)
+ (rxt-pcre-extended-mode nil)
+ (rxt-pcre-s-mode nil)
+ (rxt-pcre-case-fold nil)
+
+ ;; Bind regexps to match syntax that differs between PCRE and
+ ;; Elisp only in the addition of a backslash "\"
+ (escape (if pcre-p "" "\\"))
+ (rxt-choice-regexp
+ (rx-to-string `(seq ,escape "|")))
+ (rxt-branch-end-regexp
+ (rx-to-string `(or buffer-end
+ (seq ,escape (or "|" ")")))))
+ (rxt-brace-begin-regexp
+ (rx-to-string `(seq ,escape "{")))
+ (rxt-m-to-n-brace-regexp
+ (rx-to-string
+ `(seq
+ (submatch (* (any "0-9"))) "," (submatch (+ (any "0-9")))
+ ,escape "}")))
+ (rxt-m-to-?-brace-regexp
+ (rx-to-string
+ `(seq (submatch (+ (any "0-9"))) "," ,escape "}")))
+ (rxt-m-brace-regexp
+ (rx-to-string
+ `(seq (submatch (+ (any "0-9"))) ,escape "}")))
+
+ ;; Named character classes [: ... :] differ slightly
+ (rxt-named-classes-regexp
+ (if pcre-p
+ rxt-pcre-named-classes-regexp
+ rxt-elisp-named-classes-regexp))
+
+ (rxt-subgroup-count 0)
+ (case-fold-search nil))
+ (with-temp-buffer
+ (insert re)
+ (goto-char (point-min))
+ (let ((rxt-source-text-string re))
+ (rxt-parse-exp)))))
+
+;; Parse a complete regex: a number of branches separated by | or
+;; \|, as determined by `rxt-branch-end-regexp'.
+(defun rxt-parse-exp ()
+ ;; These variables are let-bound here because in PCRE mode they may
+ ;; be set internally by (?x) or (?s) constructions, whose scope
+ ;; lasts until the end of a sub-expression
+ (rxt-with-source-location
+ (let ((rxt-pcre-extended-mode rxt-pcre-extended-mode)
+ (rxt-pcre-s-mode rxt-pcre-s-mode)
+ (rxt-pcre-case-fold rxt-pcre-case-fold))
+ (if (eobp)
+ (rxt-seq)
+ (let ((branches '()))
+ (cl-block nil
+ (while t
+ (let ((branch (rxt-parse-branch)))
+ (push branch branches)
+ (rxt-token-case
+ (rxt-choice-regexp nil)
+ (t (cl-return (apply #'rxt-choice (reverse branches)))))))))))))
+
+;; Skip over whitespace and comments in PCRE extended mode
+(defun rxt-extended-skip ()
+ (when rxt-pcre-extended-mode
+ (skip-syntax-forward "-")
+ (while (looking-at "#")
+ (beginning-of-line 2)
+ (skip-syntax-forward "-"))))
+
+;; Parse a regexp "branch": a sequence of pieces
+(defun rxt-parse-branch ()
+ (rxt-extended-skip)
+ (rxt-with-source-location
+ (let ((pieces '())
+ (branch-start-p t))
+ (while (not (looking-at rxt-branch-end-regexp))
+ (push (rxt-parse-piece branch-start-p) pieces)
+ (setq branch-start-p nil))
+ (apply #'rxt-seq (reverse pieces)))))
+
+;; Parse a regexp "piece": an atom (`rxt-parse-atom') plus any
+;; following quantifiers
+(defun rxt-parse-piece (&optional branch-begin)
+ (rxt-extended-skip)
+ (rxt-with-source-location
+ (let ((atom (rxt-parse-atom branch-begin)))
+ (rxt-parse-quantifiers atom))))
+
+;; Parse any and all quantifiers after ATOM and return the quantified
+;; regexp, or ATOM unchanged if no quantifiers
+(defun rxt-parse-quantifiers (atom)
+ (catch 'done
+ (while (not (eobp))
+ (let ((atom1 (rxt-parse-quantifier atom)))
+ (if (eq atom1 atom)
+ (throw 'done t)
+ (setq atom atom1)))))
+ atom)
+
+;; Possibly parse a single quantifier after ATOM and return the
+;; quantified atom, or ATOM if no quantifier
+(defun rxt-parse-quantifier (atom)
+ (rxt-extended-skip)
+ (rxt-token-case
+ ((rx "*?") (rxt-repeat 0 nil atom nil))
+ ((rx "*") (rxt-repeat 0 nil atom t))
+ ((rx "+?") (rxt-repeat 1 nil atom nil))
+ ((rx "+") (rxt-repeat 1 nil atom t))
+ ((rx "??") (rxt-repeat 0 1 atom nil))
+ ((rx "?") (rxt-repeat 0 1 atom t))
+ ;; Brace expression "{M,N}", "{M,}", "{M}"
+ (rxt-brace-begin-regexp
+ (cl-destructuring-bind (from to)
+ (rxt-parse-braces)
+ (rxt-repeat from to atom)))
+ ;; No quantifiers found
+ (t atom)))
+
+;; Parse a regexp atom, i.e. an element that binds to any following
+;; quantifiers. This includes characters, character classes,
+;; parenthesized groups, assertions, etc.
+(defun rxt-parse-atom (&optional branch-begin)
+ (if (eobp)
+ (rxt-error "Unexpected end of regular expression")
+ (if rxt-parse-pcre
+ (rxt-parse-atom/pcre)
+ (rxt-parse-atom/el branch-begin))))
+
+(defun rxt-parse-atom/common ()
+ (rxt-token-case
+ ((rx "[") (rxt-parse-char-class))
+ ((rx "\\b") (rxt-word-boundary))
+ ((rx "\\B") (rxt-not-word-boundary))))
+
+(defun rxt-parse-atom/el (branch-begin)
+ (rxt-with-source-location
+ (or (rxt-parse-atom/common)
+ (rxt-token-case
+ ;; "." wildcard
+ ((rx ".") (rxt-nonl))
+ ;; "^" and "$" are metacharacters only at beginning or end of a
+ ;; branch in Elisp; elsewhere they are literals
+ ((rx "^")
+ (if branch-begin
+ (rxt-bol)
+ (rxt-string "^")))
+ ((rx "$")
+ (if (looking-at rxt-branch-end-regexp)
+ (rxt-eol)
+ (rxt-string "$")))
+ ;; Beginning & end of string, word, symbol
+ ((rx "\\`") (rxt-bos))
+ ((rx "\\'") (rxt-eos))
+ ((rx "\\<") (rxt-bow))
+ ((rx "\\>") (rxt-eow))
+ ((rx "\\_<") (rxt-symbol-start))
+ ((rx "\\_>") (rxt-symbol-end))
+ ;; Subgroup
+ ((rx "\\(") (rxt-parse-subgroup/el))
+ ;; Word/non-word characters (meaning depending on syntax table)
+ ((rx "\\w") (rxt-wordchar))
+ ((rx "\\W") (rxt-not-wordchar))
+ ;; Other syntax categories
+ ((rx "\\" (submatch (any ?S ?s)) (submatch nonl))
+ (let ((negated (string= (match-string 1) "S"))
+ (syntax
+ (car (rassoc (string-to-char (match-string 2))
+ rx-syntax))))
+ (if syntax
+ (let ((re (rxt-syntax-class syntax)))
+ (if negated (rxt-negate re) re))
+ (rxt-error "Invalid syntax class `\\%s'" (match-string 0)))))
+ ;; Character categories
+ ((rx "\\" (submatch (any ?C ?c)) (submatch nonl))
+ (let ((negated (string= (match-string 1) "C"))
+ (category
+ (car (rassoc (string-to-char (match-string 2))
+ rx-categories))))
+ (if category
+ (let ((re (rxt-char-category category)))
+ (if negated (rxt-negate re) re))
+ (rxt-error "Invalid character category `%s'" (match-string 0)))))
+ ;; Backreference
+ ((rx (seq "\\" (submatch (any "1-9"))))
+ (rxt-backref (string-to-number (match-string 1))))
+ ;; Other escaped characters
+ ((rx (seq "\\" (submatch nonl)))
+ (rxt-string (match-string 1)))
+ ;; Normal characters
+ ((rx (or "\n" nonl))
+ (rxt-string (match-string 0)))))))
+
+(defun rxt-parse-atom/pcre ()
+ (rxt-extended-skip)
+ (rxt-with-source-location
+ (or
+ ;; Is it an atom that's the same in Elisp?
+ (rxt-parse-atom/common)
+ ;; Is it common to PCRE regex and character class syntax?
+ (let ((char (rxt-parse-escapes/pcre)))
+ (and char
+ (rxt-string (char-to-string char))))
+ ;; Otherwise:
+ (rxt-token-case
+ ;; "." wildcard
+ ((rx ".")
+ (if rxt-pcre-s-mode
+ (rxt-anything)
+ (rxt-nonl)))
+ ;; Beginning & end of string/line
+ ((rx "^") (rxt-bol))
+ ((rx "$") (rxt-eol))
+ ((rx "\\A") (rxt-bos))
+ ((rx "\\Z") (rxt-eos))
+ ;; Subgroup
+ ((rx "(") (rxt-parse-subgroup/pcre))
+ ;; Metacharacter quoting
+ ((rx "\\Q")
+ ;; It would seem simple to take all the characters between \Q
+ ;; and \E and make an rxt-string, but \Q...\E isn't an atom:
+ ;; any quantifiers afterward should bind only to the last
+ ;; character, not the whole string.
+ (let ((begin (point)))
+ (search-forward "\\E" nil t)
+ (let* ((end (match-beginning 0))
+ (str (buffer-substring-no-properties begin (1- end)))
+ (char (char-to-string (char-before end))))
+ (rxt-seq (rxt-string str)
+ (rxt-parse-quantifiers (rxt-string char))))))
+ ;; Pre-defined character sets
+ ((rx "\\" (submatch (any "d" "D" "h" "H" "s" "S" "v" "V" "w" "W")))
+ (rxt--pcre-char-set (string-to-char (match-string 1))))
+ ;; \ + digits: backreference or octal char?
+ ((rx "\\" (submatch (+ (any "0-9"))))
+ (let* ((digits (match-string 1))
+ (dec (string-to-number digits)))
+ ;; from "man pcrepattern": If the number is less than 10, or if
+ ;; there have been at least that many previous capturing left
+ ;; parentheses in the expression, the entire sequence is taken
+ ;; as a back reference.
+ (if (and (> dec 0)
+ (or (< dec 10)
+ (>= rxt-subgroup-count dec)))
+ (progn
+ (when rxt-pcre-case-fold
+ (display-warning
+ 'rxt "Backreferences with case-folding are handled poorly"))
+ (rxt-backref dec))
+ ;; from "man pcrepattern": if the decimal number is greater
+ ;; than 9 and there have not been that many capturing
+ ;; subpatterns, PCRE re-reads up to three octal digits
+ ;; following the backslash, and uses them to generate a data
+ ;; character. Any subsequent digits stand for themselves.
+ (goto-char (match-beginning 1))
+ (re-search-forward (rx (** 0 3 (any "0-7"))))
+ (rxt-string (char-to-string (string-to-number (match-string 0) 8))))))
+ ;; Other escaped characters
+ ((rx "\\" (submatch nonl)) (rxt-string (match-string 1)))
+ ;; Everything else
+ ((rx (or (any "\n") nonl)) (rxt-string (match-string 0)))))))
+
+(defun rxt-parse-escapes/pcre ()
+ "Consume a one-char PCRE escape at point and return its codepoint equivalent.
+
+Handles only those character escapes which have the same meaning
+in character classes as outside them."
+ (rxt-token-case
+ ((rx "\\a") #x07) ; bell
+ ((rx "\\e") #x1b) ; escape
+ ((rx "\\f") #x0c) ; formfeed
+ ((rx "\\n") #x0a) ; linefeed
+ ((rx "\\r") #x0d) ; carriage return
+ ((rx "\\t") #x09) ; tab
+ ;; Control character
+ ((rx "\\c" (submatch nonl))
+ ;; from `man pcrepattern':
+ ;; The precise effect of \cx is as follows: if x is a lower case
+ ;; letter, it is converted to upper case. Then bit 6 of the
+ ;; character (hex 40) is inverted.
+ (logxor (string-to-char (upcase (match-string 1))) #x40))
+ ;; Hex escapes
+ ((rx "\\x" (submatch (** 1 2 (any "0-9" "A-Z" "a-z"))))
+ (string-to-number (match-string 1) 16))
+ ((rx "\\x{" (submatch (* (any "0-9" "A-Z" "a-z"))) "}")
+ (string-to-number (match-string 1) 16))))
+
+(defun rxt-parse-subgroup/pcre ()
+ (catch 'return
+ (let ((shy nil)
+ (extended-mode rxt-pcre-extended-mode)
+ (single-line-mode rxt-pcre-s-mode)
+ (case-fold rxt-pcre-case-fold))
+ (rxt-extended-skip)
+ ;; Check for special (? ..) and (* ...) syntax
+ (rxt-token-case
+ ((rx "?") ; (?
+ (rxt-token-case
+ ((rx ")") ; Empty group (?)
+ (throw 'return (rxt-empty-string)))
+ (":" (setq shy t)) ; Shy group (?:
+ ("#" ; Comment (?#
+ (search-forward ")")
+ (throw 'return (rxt-empty-string)))
+ ((rx (or ; Flags (?isx-isx
+ (seq (group (* (any "gimosx"))) "-" (group (+ (any "gimosx"))))
+ (seq (group (+ (any "gimosx"))))))
+ (let ((token (match-string 0))
+ (on (or (match-string 1) (match-string 3)))
+ (off (or (match-string 2) "")))
+ (if (cl-find ?x on) (setq extended-mode t))
+ (if (cl-find ?s on) (setq single-line-mode t))
+ (if (cl-find ?i on) (setq case-fold t))
+ (if (cl-find ?x off) (setq extended-mode nil))
+ (if (cl-find ?s off) (setq single-line-mode nil))
+ (if (cl-find ?i off) (setq case-fold nil))
+ (when (string-match-p "[gmo]" token)
+ (display-warning
+ 'rxt (format "Unhandled PCRE flags in (?%s" token))))
+ (rxt-token-case
+ (":" (setq shy t)) ; Shy group with flags (?isx-isx: ...
+ (")" ; Set flags (?isx-isx)
+ ;; Set flags for the remainder of the current subexpression
+ (setq rxt-pcre-extended-mode extended-mode
+ rxt-pcre-s-mode single-line-mode
+ rxt-pcre-case-fold case-fold)
+ (throw 'return (rxt-empty-string)))))
+ ;; Other constructions like (?=, (?!, etc. are not recognised
+ (t (rxt-error "Unrecognized PCRE extended construction `(?%c'"
+ (char-after)))))
+
+ ;; No special (* ...) verbs are recognised
+ ((rx "*")
+ (let ((begin (point)))
+ (search-forward ")" nil 'go-to-end)
+ (rxt-error "Unrecognized PCRE extended construction `(*%s'"
+ (buffer-substring begin (point))))))
+
+ ;; Parse the remainder of the subgroup
+ (unless shy (cl-incf rxt-subgroup-count))
+ (let* ((rxt-pcre-extended-mode extended-mode)
+ (rxt-pcre-s-mode single-line-mode)
+ (rxt-pcre-case-fold case-fold)
+ (rx (rxt-parse-exp)))
+ (rxt-extended-skip)
+ (rxt-token-case
+ (")" (if shy rx (rxt-submatch rx)))
+ (t (rxt-error "Subexpression missing close paren")))))))
+
+(defun rxt-parse-subgroup/el ()
+ (let ((kind
+ (rxt-token-case
+ ((rx "?:")
+ (cl-incf rxt-subgroup-count)
+ 'shy)
+ ((rx "?" (group (+ (in "0-9"))) ":")
+ (let ((n (string-to-number (match-string 1))))
+ (when (< rxt-subgroup-count n)
+ (setf rxt-subgroup-count n))
+ n))
+ ((rx "?") ; Reserved
+ (rxt-error "Unknown match group sequence")))))
+ (let ((rx (rxt-parse-exp)))
+ (rxt-token-case
+ ((rx "\\)")
+ (cond ((eq kind 'shy) rx)
+ ((numberp kind)
+ (rxt-submatch-numbered kind rx))
+ (t (rxt-submatch rx))))
+ (t (rxt-error "Subexpression missing close paren"))))))
+
+(defun rxt-parse-braces ()
+ (rxt-token-case
+ (rxt-m-to-n-brace-regexp
+ (list (string-to-number (match-string 1))
+ (string-to-number (match-string 2))))
+ (rxt-m-to-?-brace-regexp
+ (list (string-to-number (match-string 1)) nil))
+ (rxt-m-brace-regexp
+ (let ((a (string-to-number (match-string 1))))
+ (list a a)))
+ (t
+ (let ((begin (point)))
+ (search-forward "}" nil 'go-to-end)
+ (rxt-error "Bad brace expression {%s"
+ (buffer-substring-no-properties begin (point)))))))
+
+;; Parse a character set range [...]
+(defun rxt-parse-char-class ()
+ (when (eobp)
+ (rxt-error "Missing close right bracket in regexp"))
+ (rxt-with-source-location
+ (let* ((negated (rxt-token-case
+ ((rx "^") t)
+ (t nil)))
+ (begin (point))
+ (result
+ (if negated
+ (rxt-negate (rxt-char-set-union))
+ (rxt-char-set-union)))
+ (transformer
+ (if negated #'rxt-negate #'identity))
+ (builder
+ (if negated #'rxt-char-set-intersection #'rxt-choice)))
+ (catch 'done
+ (while t
+ (when (eobp)
+ (rxt-error "Missing close right bracket in regexp"))
+ (if (and (looking-at (rx "]"))
+ (not (= (point) begin)))
+ (throw 'done result)
+ (let ((piece (funcall transformer (rxt-parse-char-class-piece))))
+ (setq result (funcall builder result piece))))))
+ (forward-char) ; Skip over closing "]"
+ result)))
+
+;; Parse a single character, a character range, or a posix class
+;; within a character set context. Returns an `rxt-char-set'.
+(defun rxt-parse-char-class-piece ()
+ (let ((atom (rxt-parse-char-class-atom)))
+ (cl-typecase atom
+ (rxt-char-set ; return unchanged
+ atom)
+ (integer ; character: check for range
+ (let ((range-end (rxt-maybe-parse-range-end)))
+ (if range-end
+ (rxt-char-set-union (cons atom range-end))
+ (rxt-char-set-union atom))))
+ (t ; transform into character set
+ (rxt-char-set-union atom)))))
+
+;; Parse a single character or named class within a charset.
+;;
+;; Returns an integer (a character), a symbol (representing a named
+;; character class) or an `rxt-char-set' (for pre-defined character
+;; classes like \d, \W, etc.)
+(defun rxt-parse-char-class-atom ()
+ (or
+ ;; First, check for PCRE-specific backslash sequences
+ (and rxt-parse-pcre
+ (rxt-parse-char-class-atom/pcre))
+ ;; Char-class syntax
+ (rxt-token-case
+ ;; Named classes [:alnum:], ...
+ (rxt-named-classes-regexp
+ (intern (match-string 1)))
+ ;; Error on unknown posix-class-like syntax
+ ((rx "[:" (* (any "a-z")) ":]")
+ (rxt-error "Unknown posix character class `%s'" (match-string 0)))
+ ;; Error on [= ... ]= collation syntax
+ ((rx "[" (submatch (any "." "="))
+ (* (any "a-z")) (backref 1) "]")
+ (rxt-error "Unsupported collation syntax `%s'" (match-string 0)))
+ ;; Other characters stand for themselves
+ ((rx (or "\n" nonl))
+ (string-to-char (match-string 0))))))
+
+;; Parse backslash escapes inside PCRE character classes
+(defun rxt-parse-char-class-atom/pcre ()
+ (or (rxt-parse-escapes/pcre)
+ (rxt-token-case
+ ;; Backslash + digits => octal char
+ ((rx "\\" (submatch (** 1 3 (any "0-7"))))
+ (string-to-number (match-string 1) 8))
+ ;; Pre-defined character sets
+ ((rx "\\" (submatch (any "d" "D" "h" "H" "s" "S" "v" "V" "w" "W")))
+ (rxt--pcre-char-set (string-to-char (match-string 1))))
+ ;; "\b" inside character classes is a backspace
+ ((rx "\\b") ?\C-h)
+ ;; Ignore other escapes
+ ((rx "\\" (submatch nonl))
+ (string-to-char (match-string 1))))))
+
+;; Look for a range tail (the "-z" in "a-z") after parsing a single
+;; character within in a character set. Returns either a character
+;; representing the range end, or nil.
+(defun rxt-maybe-parse-range-end ()
+ (let ((range-end nil) (end-position nil))
+ (when (looking-at (rx "-" (not (any "]"))))
+ (save-excursion
+ (forward-char)
+ (setq range-end (rxt-parse-char-class-atom)
+ end-position (point))))
+
+ (if (characterp range-end)
+ ;; This is a range: move point after it and return the ending character
+ (progn
+ (goto-char end-position)
+ range-end)
+ ;; Not a range.
+ nil)))
+
+;; Return the pre-defined PCRE char-set associated with CHAR: i.e. \d
+;; is digits, \D non-digits, \s space characters, etc.
+(defun rxt--pcre-char-set (char)
+ (let* ((base-char (downcase char))
+ (negated (/= char base-char))
+ (elements (assoc-default base-char rxt-pcre-char-set-alist))
+ (base-char-set (apply #'rxt-char-set-union elements)))
+ (if negated
+ (rxt-negate base-char-set)
+ base-char-set)))
+
+\f
+;;;; Unparser to `rx' syntax
+
+(defconst rxt-rx-verbose-equivalents
+ '((bol . line-start)
+ (eol . line-end)
+ (nonl . not-newline)
+ (bos . string-start)
+ (eos . string-end)
+ (bow . word-start)
+ (eow . word-end)
+ (seq . sequence))
+ "Alist of verbose equivalents for short `rx' primitives.")
+
+(defun rxt-rx-symbol (sym)
+ (if rxt-verbose-rx-translation
+ (or (assoc-default sym rxt-rx-verbose-equivalents)
+ sym)
+ sym))
+
+(defun rxt-adt->rx (re)
+ (let ((rx
+ (cl-typecase re
+ (rxt-primitive
+ (rxt-rx-symbol (rxt-primitive-rx re)))
+
+ (rxt-string
+ (if (or (not (rxt-string-case-fold re))
+ (string= "" (rxt-string-chars re)))
+ (rxt-string-chars re)
+ `(seq
+ ,@(cl-loop for char across (rxt-string-chars re)
+ collect `(any ,(upcase char) ,(downcase char))))))
+
+ (rxt-seq
+ `(seq ,@(mapcar #'rxt-adt->rx (rxt-seq-elts re))))
+
+ (rxt-choice
+ `(or ,@(mapcar #'rxt-adt->rx (rxt-choice-elts re))))
+
+ (rxt-submatch
+ (if (rxt-seq-p (rxt-submatch-body re))
+ `(submatch
+ ,@(mapcar #'rxt-adt->rx (rxt-seq-elts (rxt-submatch-body re))))
+ `(submatch ,(rxt-adt->rx (rxt-submatch-body re)))))
+
+ (rxt-submatch-numbered
+ (if (rxt-seq-p (rxt-submatch-numbered-p re))
+ `(,(rxt-rx-symbol 'submatch-n)
+ ,(rxt-submatch-numbered-n re)
+ ,@(mapcar #'rxt-adt->rx
+ (rxt-seq-elts
+ (rxt-submatch-numbered-body re))))
+ `(,(rxt-rx-symbol 'submatch-n)
+ ,(rxt-submatch-numbered-n re)
+ ,(rxt-adt->rx (rxt-submatch-numbered-body re)))))
+
+ (rxt-backref
+ (let ((n (rxt-backref-n re)))
+ (if (<= n 9)
+ `(backref ,(rxt-backref-n re))
+ (rxt-error "Too many backreferences (%s)" n))))
+
+ (rxt-syntax-class
+ `(syntax ,(rxt-syntax-class-symbol re)))
+
+ (rxt-char-category
+ `(category ,(rxt-char-category-symbol re)))
+
+ (rxt-repeat
+ (let ((from (rxt-repeat-from re))
+ (to (rxt-repeat-to re))
+ (greedy (rxt-repeat-greedy re))
+ (body (rxt-adt->rx (rxt-repeat-body re))))
+ (if rxt-verbose-rx-translation
+ (let ((rx
+ (cond
+ ((and (zerop from) (null to))
+ `(zero-or-more ,body))
+ ((and (equal from 1) (null to))
+ `(one-or-more ,body))
+ ((and (zerop from) (equal to 1))
+ `(zero-or-one ,body))
+ ((null to)
+ `(>= ,from ,body))
+ ((equal from to)
+ `(repeat ,from ,body))
+ (t
+ `(** ,from ,to ,body)))))
+ (if greedy
+ (if rxt-explain
+ rx ; Readable but not strictly accurate. Fixme?
+ `(maximal-match ,rx))
+ `(minimal-match ,rx)))
+ (cond
+ ((and (zerop from) (null to))
+ `(,(if greedy '* '*?) ,body))
+ ((and (equal from 1) (null to))
+ `(,(if greedy '+ '+?) ,body))
+ ((and (zerop from) (equal to 1))
+ `(,(if greedy ? ??) ,body))
+ ((null to)
+ `(>= ,from ,body))
+ ((equal from to)
+ `(= ,from ,body))
+ (t
+ `(** ,from ,to ,body))))))
+
+ (rxt-char-set-union
+ (let* ((case-fold (rxt-char-set-union-case-fold re))
+ (re (rxt--simplify-char-set re case-fold))
+ (chars (rxt-char-set-union-chars re))
+ (ranges (rxt-char-set-union-ranges re))
+ (classes (rxt-char-set-union-classes re))
+ (case-fold (rxt-char-set-union-case-fold re)))
+ (if (and (null chars) (null ranges) (= 1 (length classes)))
+ (car classes)
+ `(any ,@chars ,@ranges ,@classes))))
+
+ (rxt-char-set-negation
+ `(not ,(rxt-adt->rx (rxt-char-set-negation-elt re))))
+
+ (t
+ (rxt-error "No RX translation of `%s'" (rxt-to-string re))))))
+
+ ;; Store source information on each fragment of the generated RX
+ ;; sexp for rxt-explain mode
+ (when rxt-explain
+ ;; Use gensyms to store unique source information for multiple
+ ;; occurrences of primitives like `bol'
+ (when (symbolp rx)
+ (setq rx (make-symbol (symbol-name rx))))
+ (setf (rxt-location rx) (rxt-location re)))
+ rx))
+
+\f
+;;;; 'Unparser' to PCRE notation
+
+;;; Based on scsh/posixstr.scm in scsh
+
+;; To ensure that the operator precedence in the generated regexp does
+;; what we want, we need to keep track of what kind of production is
+;; returned from each step. Therefore these functions return a string
+;; and a numeric "level" which lets the function using the generated
+;; regexp know whether it has to be parenthesized:
+;;
+;; 0: an already parenthesized expression
+;;
+;; 1: a "piece" binds to any succeeding quantifiers
+;;
+;; 2: a "branch", or concatenation of pieces, needs parenthesizing to
+;; bind to quantifiers
+;;
+;; 3: a "top", or alternation of branches, needs parenthesizing to
+;; bind to quantifiers or to concatenation
+;;
+;; This idea is stolen straight out of the scsh implementation.
+
+(defun rxt-adt->pcre (re)
+ (cl-destructuring-bind (s _) (rxt-adt->pcre/lev re) s))
+
+(defun rxt-adt->pcre/lev (re)
+ (cl-typecase re
+ (rxt-primitive
+ (let ((s (rxt-primitive-pcre re)))
+ (if s
+ (list s 1)
+ (rxt-error "No PCRE translation of `%s'" (rxt-to-string re)))))
+
+ (rxt-string (rxt-string->pcre re))
+ (rxt-seq (rxt-seq->pcre re))
+ (rxt-choice (rxt-choice->pcre re))
+ (rxt-submatch (rxt-submatch->pcre re))
+ (rxt-backref
+ (list (format "\\%d" (rxt-backref-n re)) 1))
+ (rxt-repeat (rxt-repeat->pcre re))
+
+ ((or rxt-char-set-union rxt-char-set-negation)
+ (rxt-char-set->pcre re))
+
+ ;; FIXME
+ ;; ((rxt-char-set-intersection re) (rxt-char-set-intersection->pcre re))
+
+ (t
+ (rxt-error "No PCRE translation of `%s'" (rxt-to-string re)))))
+
+(defconst rxt-pcre-metachars (rx (any "\\^.$|()[]*+?{}")))
+(defconst rxt-pcre-charset-metachars (rx (any "]" "[" "\\" "^" "-")))
+
+(defun rxt-string->pcre (re)
+ (let ((chars (rxt-string-chars re)))
+ (list
+ (replace-regexp-in-string
+ rxt-pcre-metachars
+ "\\\\\\&" chars)
+ ;; A one-character string is a 'piece' (it binds to a following
+ ;; quantifier). A longer string is a 'branch' (it has to be
+ ;; enclosed in parentheses to bind to a following quantifier).
+ (if (> (length chars) 1) 2 1))))
+
+(defun rxt-seq->pcre (re)
+ (let ((elts (rxt-seq-elts re)))
+ (if (null elts)
+ ""
+ (rxt-seq-elts->pcre elts))))
+
+(defun rxt-seq-elts->pcre (elts)
+ (cl-destructuring-bind
+ (s lev) (rxt-adt->pcre/lev (car elts))
+ (if (null (cdr elts))
+ (list s lev)
+ (cl-destructuring-bind
+ (s1 lev1) (rxt-seq-elts->pcre (cdr elts))
+ (list (concat (rxt-paren-if-necessary s lev)
+ (rxt-paren-if-necessary s1 lev1))
+ 2)))))
+
+(defun rxt-paren-if-necessary (s lev)
+ (if (< lev 3)
+ s
+ (concat "(?:" s ")")))
+
+(defun rxt-choice->pcre (re)
+ (let ((elts (rxt-choice-elts re)))
+ (if (null elts)
+ nil
+ (rxt-choice-elts->pcre elts))))
+
+(defun rxt-choice-elts->pcre (elts)
+ (cl-destructuring-bind
+ (s lev) (rxt-adt->pcre/lev (car elts))
+ (if (null (cdr elts))
+ (list s lev)
+ (cl-destructuring-bind
+ (s1 lev1) (rxt-choice-elts->pcre (cdr elts))
+ (list (concat s "|" s1) 3)))))
+
+(defun rxt-submatch->pcre (re)
+ (cl-destructuring-bind
+ (s lev) (rxt-adt->pcre/lev (rxt-submatch-body re))
+ (list (concat "(" s ")") 0)))
+
+(defun rxt-repeat->pcre (re)
+ (let ((from (rxt-repeat-from re))
+ (to (rxt-repeat-to re))
+ (body (rxt-repeat-body re))
+ (greedy (rxt-repeat-greedy re)))
+ (cl-destructuring-bind
+ (s lev) (rxt-adt->pcre/lev body)
+ (cond
+ ((and to (= from 1) (= to 1)) (list s lev))
+ ((and to (= from 0) (= to 0)) (list "" 2))
+ (t
+ (when (> lev 1) ; parenthesize non-atoms
+ (setq s (concat "(?:" s ")")
+ lev 0))
+ (list (if to
+ (cond ((and (= from 0) (= to 1))
+ (concat s (if greedy "?" "??")))
+ ((= from to)
+ (concat s "{" (number-to-string to) "}"))
+ (t
+ (concat s "{" (number-to-string from)
+ "," (number-to-string to) "}")))
+ (cond ((= from 0)
+ (concat s (if greedy "*" "*?")))
+ ((= from 1)
+ (concat s (if greedy "+" "+?")))
+ (t (concat s "{" (number-to-string from) ",}"))))
+ 1))))))
+
+(defun rxt-char-set->pcre (re)
+ (cond ((rxt-char-set-union-p re)
+ (list
+ (concat "[" (rxt-char-set->pcre/chars re) "]") 1))
+
+ ((rxt-char-set-negation-p re)
+ (let ((elt (rxt-char-set-negation-elt re)))
+ (if (rxt-char-set-union-p elt)
+ (list
+ (concat "[^" (rxt-char-set->pcre/chars elt) "]") 1)
+ (rxt-error "No PCRE translation of `%s'" (rxt-to-string elt)))))
+
+ (t
+ (rxt-error "Non-char-set in rxt-char-set->pcre: %s" re))))
+
+;; Fortunately, easier in PCRE than in POSIX!
+(defun rxt-char-set->pcre/chars (re)
+ (cl-flet
+ ((escape
+ (char)
+ (let ((s (char-to-string char)))
+ (cond ((string-match rxt-pcre-charset-metachars s)
+ (concat "\\" s))
+
+ ((and (not (string= s " "))
+ (string-match "[^[:graph:]]" s))
+ (format "\\x{%x}" char))
+
+ (t s)))))
+
+ (let ((chars (rxt-char-set-union-chars re))
+ (ranges (rxt-char-set-union-ranges re))
+ (classes (rxt-char-set-union-classes re)))
+
+ (concat
+ (mapconcat #'escape chars "")
+ (mapconcat #'(lambda (rg)
+ (format "%s-%s"
+ (escape (car rg))
+ (escape (cdr rg))))
+ ranges "")
+ (mapconcat #'(lambda (class)
+ (format "[:%s:]" class))
+ classes "")))))
+
+\f
+;;;; Generate all productions of a finite regexp
+
+(defun rxt-adt->strings (re)
+ (cl-typecase re
+ (rxt-primitive
+ (list ""))
+ (rxt-string
+ (list (rxt-string-chars re)))
+ (rxt-seq
+ (rxt-seq-elts->strings (rxt-seq-elts re)))
+ (rxt-choice
+ (rxt-choice-elts->strings (rxt-choice-elts re)))
+ (rxt-submatch
+ (rxt-adt->strings (rxt-submatch-body re)))
+ (rxt-submatch-numbered
+ (rxt-adt->strings (rxt-submatch-numbered-body re)))
+ (rxt-repeat
+ (rxt-repeat->strings re))
+ (rxt-char-set-union
+ (rxt-char-set->strings re))
+ (t
+ (error "Can't generate productions of %s"
+ (rxt-syntax-tree-readable re)))))
+
+(defun rxt-concat-product (heads tails)
+ (cl-mapcan
+ (lambda (hs)
+ (mapcar
+ (lambda (ts) (concat hs ts))
+ tails))
+ heads))
+
+(defun rxt-seq-elts->strings (elts)
+ (if (null elts)
+ '("")
+ (let ((heads (rxt-adt->strings (car elts)))
+ (tails (rxt-seq-elts->strings (cdr elts))))
+ (rxt-concat-product heads tails))))
+
+(defun rxt-choice-elts->strings (elts)
+ (if (null elts)
+ '()
+ (append (rxt-adt->strings (car elts))
+ (rxt-choice-elts->strings (cdr elts)))))
+
+(defun rxt-repeat->strings (re)
+ (let ((from (rxt-repeat-from re))
+ (to (rxt-repeat-to re)))
+ (if (not to)
+ (error "Can't generate all productions of unbounded repeat \"%s\""
+ (rxt-syntax-tree-readable re))
+ (let ((strings (rxt-adt->strings (rxt-repeat-body re))))
+ (rxt-repeat-n-m->strings from to strings)))))
+
+(defun rxt-repeat-n-m->strings (from to strings)
+ (cond
+ ((zerop to) '(""))
+ ((= to from) (rxt-repeat-n->strings from strings))
+ (t ; to > from
+ (let* ((strs-n (rxt-repeat-n->strings from strings))
+ (accum (cl-copy-list strs-n)))
+ (dotimes (i (- to from))
+ (setq strs-n (rxt-concat-product strs-n strings))
+ (setq accum (nconc accum strs-n)))
+ accum))))
+
+(defun rxt-repeat-n->strings (n strings)
+ ;; n > 1
+ (cond ((zerop n) '(""))
+ ((= n 1) strings)
+ (t
+ (rxt-concat-product
+ (rxt-repeat-n->strings (- n 1) strings)
+ strings))))
+
+(defun rxt-char-set->strings (re)
+ (if (rxt-char-set-union-classes re)
+ (error "Can't generate all productions of named character classes in \"%s\""
+ (rxt-syntax-tree-readable re))
+ (let ((chars (mapcar #'char-to-string (rxt-char-set-union-chars re))))
+ (dolist (range (rxt-char-set-union-ranges re))
+ (let ((end (cdr range)))
+ (cl-do ((i (car range) (+ i 1)))
+ ((> i end))
+ (push (char-to-string i) chars))))
+ chars)))
+
+\f
+;;;; RE-Builder hacks
+
+(defadvice reb-update-modestring
+ (after rxt () activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (setq reb-mode-string
+ (concat
+ (format " (%s)" reb-re-syntax)
+ reb-mode-string))
+ (force-mode-line-update))
+
+(defadvice reb-change-syntax
+ (around rxt (&optional syntax) activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (interactive
+ (list (intern
+ (completing-read (format "Select syntax (%s): " reb-re-syntax)
+ '(read string pcre sregex rx)
+ nil t "" nil (symbol-name reb-re-syntax)))))
+ (unless (memq syntax '(read string pcre lisp-re sregex rx))
+ (error "Invalid syntax: %s" syntax))
+ (let ((re-builder-buffer (get-buffer reb-buffer)))
+ (setq reb-re-syntax syntax)
+ (when re-builder-buffer
+ (with-current-buffer reb-target-buffer
+ (cl-case syntax
+ (rx
+ (let ((rx (rxt-elisp-to-rx reb-regexp)))
+ (setq reb-regexp-src
+ (with-temp-buffer
+ (insert "\n" "'")
+ (rxt-print rx)
+ (buffer-string)))))
+ (pcre
+ (setq reb-regexp-src (rxt-elisp-to-pcre reb-regexp)))))
+ (with-current-buffer re-builder-buffer
+ ;; Hack: prevent reb-auto-update from clobbering the
+ ;; reb-regexp-src we just set
+ (let ((inhibit-modification-hooks t))
+ (reb-initialize-buffer))
+ ;; Enable flag-toggling bindings for PCRE syntax
+ (rxt--re-builder-switch-pcre-mode)))))
+
+(defadvice reb-read-regexp
+ (around rxt () activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (if (eq reb-re-syntax 'pcre)
+ (setq ad-return-value
+ (save-excursion
+ (goto-char (point-min))
+ (rxt-read-delimited-pcre)))
+ ad-do-it))
+
+(defadvice reb-insert-regexp
+ (around rxt () activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (if (eq reb-re-syntax 'pcre)
+ (let ((src (reb-target-binding reb-regexp-src)))
+ (if src
+ (insert "\n/" (replace-regexp-in-string "/" "\\/" src t t) "/")
+ (insert "\n//")))
+ ad-do-it))
+
+(defadvice reb-cook-regexp
+ (around rxt (re) activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (if (eq reb-re-syntax 'pcre)
+ (setq ad-return-value (rxt-pcre-to-elisp re))
+ ad-do-it))
+
+(defadvice reb-update-regexp
+ (around rxt () activate compile)
+ "This function is hacked for emulated PCRE syntax and regexp conversion."
+ (setq ad-return-value
+ (let* ((re-src (reb-read-regexp))
+ (re (reb-cook-regexp re-src)))
+ (with-current-buffer reb-target-buffer
+ (let ((oldre reb-regexp))
+ (prog1
+ (not (string= oldre re))
+ (setq reb-regexp re)
+ ;; Update the source re if format requires translation
+ (when (or (reb-lisp-syntax-p) (eq reb-re-syntax 'pcre))
+ (setq reb-regexp-src re-src))))))))
+
+(defun rxt--re-builder-switch-pcre-mode ()
+ (rxt--read-pcre-mode
+ (if (eq reb-re-syntax 'pcre) 1 0)))
+
+(add-hook 'reb-mode-hook #'rxt--re-builder-switch-pcre-mode)
+
+(provide 'rxt)
+(provide 'pcre2el)
+
+
+;;; pcre2el.el ends here
--- /dev/null
+1 Overview
+==========
+
+ `pcre2el' or `rxt' (RegeXp Translator or RegeXp Tools) is a utility
+ for working with regular expressions in Emacs, based on a
+ recursive-descent parser for regexp syntax. In addition to converting
+ (a subset of) PCRE syntax into its Emacs equivalent, it can do the
+ following:
+
+ - convert Emacs syntax to PCRE
+ - convert either syntax to `rx', an S-expression based regexp syntax
+ - untangle complex regexps by showing the parse tree in `rx' form and
+ highlighting the corresponding chunks of code
+ - show the complete list of strings (productions) matching a regexp,
+ provided the list is finite
+ - provide live font-locking of regexp syntax (so far only for Elisp
+ buffers -- other modes on the TODO list)
+
+
+2 Usage
+=======
+
+ Enable `rxt-mode' or its global equivalent `rxt-global-mode' to get
+ the default key-bindings. There are three sets of commands: commands
+ that take a PCRE regexp, commands which take an Emacs regexp, and
+ commands that try to do the right thing based on the current
+ mode. Currently, this means Emacs syntax in `emacs-lisp-mode' and
+ `lisp-interaction-mode', and PCRE syntax everywhere else.
+
+ The default key bindings all begin with `C-c /' and have a mnemonic
+ structure: `C-c / <source> <target>', or just `C-c / <target>' for the
+ "do what I mean" commands. The complete list of key bindings is given
+ here and explained in more detail below:
+
+ - "Do-what-I-mean" commands:
+ `C-c / /': `rxt-explain'
+ `C-c / c': `rxt-convert-syntax'
+ `C-c / x': `rxt-convert-to-rx'
+ `C-c / '': `rxt-convert-to-strings'
+
+ - Commands that work on a PCRE regexp:
+ `C-c / p e': `rxt-pcre-to-elisp'
+ `C-c / %': `pcre-query-replace-regexp'
+ `C-c / p x': `rxt-pcre-to-rx'
+ `C-c / p '': `rxt-pcre-to-strings'
+ `C-c / p /': `rxt-explain-pcre'
+
+ - Commands that work on an Emacs regexp:
+ `C-c / e /': `rxt-explain-elisp'
+ `C-c / e p': `rxt-elisp-to-pcre'
+ `C-c / e x': `rxt-elisp-to-rx'
+ `C-c / e '': `rxt-elisp-to-strings'
+ `C-c / e t': `rxt-toggle-elisp-rx'
+ `C-c / t': `rxt-toggle-elisp-rx'
+
+
+2.1 Interactive input and output
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ When used interactively, the conversion commands can read a regexp
+ either from the current buffer or from the minibuffer. The output is
+ displayed in the minibuffer and copied to the kill-ring.
+
+ - When called with a prefix argument (`C-u'), they read a regular
+ expression from the minibuffer literally, without further processing
+ -- meaning there's no need to double the backslashes if it's an
+ Emacs regexp. This is the same way commands like
+ `query-replace-regexp' read input.
+
+ - When the region is active, they use they the region contents, again
+ literally (without any translation of string syntax).
+
+ - With neither a prefix arg nor an active region, the behavior depends
+ on whether the command expects an Emacs regexp or a PCRE one.
+
+ Commands that take an Emacs regexp behave like `C-x C-e': they
+ evaluate the sexp before point (which could be simply a string
+ literal) and use its value. This is designed for use in Elisp
+ buffers. As a special case, if point is *inside* a string, it's
+ first moved to the string end, so in practice they should work as
+ long as point is somewhere within the regexp literal.
+
+ Commands that take a PCRE regexp try to read a Perl-style delimited
+ regex literal *after* point in the current buffer, including its
+ flags. For example, putting point before the `m' in the following
+ example and doing `C-c / p e' (`rxt-pcre-to-elisp') displays
+ `\(?:bar\|foo\)', correctly stripping out the whitespace and
+ comment:
+
+ ,----
+ | $x =~ m/ foo | (?# comment) bar /x
+ `----
+
+ The PCRE reader currently only works with `/ ... /' delimiters. It
+ will ignore any preceding `m', `s', or `qr' operator, as well as the
+ replacement part of an `s' construction.
+
+ Readers for other PCRE-using languages are on the TODO list.
+
+ The translation functions display their result in the minibuffer and
+ copy it to the kill ring. When translating something into Elisp
+ syntax, you might need to use the result either literally (e.g. for
+ interactive input to a command like `query-replace-regexp'), or as a
+ string to paste into Lisp code. To allow both uses,
+ `rxt-pcre-to-elisp' copies both versions successively to the
+ kill-ring. The literal regexp without string quoting is the top
+ element of the kill-ring, while the Lisp string is the
+ second-from-top. You can paste the literal regexp somewhere by doing
+ `C-y', or the Lisp string by `C-y M-y'.
+
+
+2.2 Syntax conversion commands
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ `rxt-convert-syntax' (`C-c / c') converts between Emacs and PCRE
+ syntax, depending on the major mode in effect when called.
+ Alternatively, you can specify the conversion direction explicitly by
+ using either `rxt-pcre-to-elisp' (`C-c / p e') or `rxt-elisp-to-pcre'
+ (`C-c / e p').
+
+ Similarly, `rxt-convert-to-rx' (`C-c / x') converts either kind of
+ syntax to `rx' form, while `rxt-convert-pcre-to-rx' (`C-c / p x') and
+ `rxt-convert-elisp-to-rx' (`C-c / e x') convert to `rx' from a
+ specified source type.
+
+ In Elisp buffers, you can use `rxt-toggle-elisp-rx' (`C-c / t' or `C-c
+ / e t') to switch the regexp at point back and forth between string
+ and `rx' syntax. Point should either be within an `rx' or
+ `rx-to-string' form or a string literal for this to work.
+
+
+2.3 PCRE mode (experimental)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ If you want to use emulated PCRE regexp syntax in all Emacs commands,
+ try `pcre-mode', which uses Emacs's advice system to make all commands
+ that read regexps using the minibuffer use emulated PCRE syntax. It
+ should also work with Isearch.
+
+ This feature is still fairly experimental. It may fail to work or do
+ the wrong thing with certain commands. Please report bugs.
+
+ `pcre-query-replace-regexp' was originally defined to do query-replace
+ using emulated PCRE regexps, and is now made somewhat obsolete by
+ `pcre-mode'. It is bound to `C-c / %' by default, by analogy with
+ `M-%'. Put the following in your `.emacs' if you want to use
+ PCRE-style query replacement everywhere:
+
+ ,----
+ | (global-set-key [(meta %)] 'pcre-query-replace-regexp)
+ `----
+
+2.5 Explain regexps
+~~~~~~~~~~~~~~~~~~~
+
+ When syntax-highlighting isn't enough to untangle some gnarly regexp
+ you find in the wild, try the 'explain' commands: `rxt-explain' (`C-c
+ / /'), `rxt-explain-pcre' (`C-c / p') and `rxt-explain-elisp' (`C-c /
+ e'). These display the original regexp along with its pretty-printed
+ `rx' equivalent in a new buffer. Moving point around either in the
+ original regexp or the `rx' translation highlights corresponding
+ pieces of syntax, which can aid in seeing things like the scope of
+ quantifiers.
+
+ I call them "explain" commands because the `rx' form is close to a
+ plain syntax tree, and this plus the wordiness of the operators
+ usually helps to clarify what is going on. People who dislike Lisp
+ syntax might disagree with this assessment.
+
+
+2.6 Generate all matching strings (productions)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Occasionally you come across a regexp which is designed to match a
+ finite set of strings, e.g. a set of keywords, and it would be useful
+ to recover the original set. (In Emacs you can generate such regexps
+ using `regexp-opt'). The commands `rxt-convert-to-strings' (`C-c /
+ ′'), `rxt-pcre-to-strings' (`C-c / p ′') or `rxt-elisp-to-strings'
+ (`C-c / e ′') accomplish this by generating all the matching strings
+ ("productions") of a regexp. (The productions are copied to the kill
+ ring as a Lisp list).
+
+ An example in Lisp code:
+
+ ,----
+ | (regexp-opt '("cat" "caterpillar" "catatonic"))
+ | ;; => "\\(?:cat\\(?:atonic\\|erpillar\\)?\\)"
+ | (rxt-elisp-to-strings "\\(?:cat\\(?:atonic\\|erpillar\\)?\\)")
+ | ;; => '("cat" "caterpillar" "catatonic")
+ `----
+
+ For obvious reasons, these commands only work with regexps that don't
+ include any unbounded quantifiers like `+' or `*'. They also can't
+ enumerate all the characters that match a named character class like
+ `[[:alnum:]]'. In either case they will give a (hopefully meaningful)
+ error message. Due to the nature of permutations, it's still possible
+ for a finite regexp to generate a huge number of productions, which
+ will eat memory and slow down your Emacs. Be ready with `C-g' if
+ necessary.
+
+
+2.7 RE-Builder support
+~~~~~~~~~~~~~~~~~~~~~~
+
+ The Emacs RE-Builder is a useful visual tool which allows using
+ several different built-in syntaxes via `reb-change-syntax' (`C-c
+ TAB'). It supports Elisp read and literal syntax and `rx', but it can
+ only convert from the symbolic forms to Elisp, not the other way. This
+ package hacks the RE-Builder to also work with emulated PCRE syntax,
+ and to convert transparently between Elisp, PCRE and rx syntaxes. PCRE
+ mode reads a delimited Perl-like literal of the form `/ ... /', and it
+ should correctly support using the `x' and `s' flags.
+
+
+2.8 Use from Lisp
+~~~~~~~~~~~~~~~~~
+
+ Example of using the conversion functions:
+ ,----
+ | (rxt-pcre-to-elisp "(abc|def)\\w+\\d+")
+ | ;; => "\\(\\(?:abc\\|def\\)\\)[_[:alnum:]]+[[:digit:]]+"
+ `----
+
+ All the conversion functions take a single string argument, the regexp
+ to translate:
+
+ - `rxt-pcre-to-elisp'
+ - `rxt-pcre-to-rx'
+ - `rxt-pcre-to-strings'
+ - `rxt-elisp-to-pcre'
+ - `rxt-elisp-to-rx'
+ - `rxt-elisp-to-strings'
+
+
+3 Bugs and Limitations
+======================
+
+3.1 Limitations on PCRE syntax
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ PCRE has a complicated syntax and semantics, only some of which can be
+ translated into Elisp. The following subset of PCRE should be
+ correctly parsed and converted:
+
+ - parenthesis grouping `( .. )', including shy matches `(?: ... )'
+ - backreferences (various syntaxes), but only up to 9 per expression
+ - alternation `|'
+ - greedy and non-greedy quantifiers `*', `*?', `+', `+?', `?' and `??'
+ (all of which are the same in Elisp as in PCRE)
+ - numerical quantifiers `{M,N}'
+ - beginning/end of string `\A', `\Z'
+ - string quoting `\Q .. \E'
+ - word boundaries `\b', `\B' (these are the same in Elisp)
+ - single character escapes `\a', `\c', `\e', `\f', `\n', `\r', `\t',
+ `\x', and `\octal digits' (but see below about non-ASCII characters)
+ - character classes `[...]' including Posix escapes
+ - character classes `\d', `\D', `\h', `\H', `\s', `\S', `\v', `\V'
+ both within character class brackets and outside
+ - word and non-word characters `\w' and `\W' (Emacs has the same
+ syntax, but its meaning is different)
+ - `s' (single line) and `x' (extended syntax) flags, in regexp
+ literals, or set within the expression via `(?xs-xs)' or `(?xs-xs:
+ .... )' syntax
+ - comments `(?# ... )'
+
+ Most of the more esoteric PCRE features can't really be supported by
+ simple translation to Elisp regexps. These include the different
+ lookaround assertions, conditionals, and the "backtracking control
+ verbs" `(* ...)' . OTOH, there are a few other syntaxes which are
+ currently unsupported and possibly could be:
+
+ - `\L', `\U', `\l', `\u' case modifiers
+ - `\g{...}' backreferences
+
+
+3.2 Other limitations
+~~~~~~~~~~~~~~~~~~~~~
+
+ - The order of alternatives and characters in char classes sometimes
+ gets shifted around, which is annoying.
+ - Although the string parser tries to interpret PCRE's octal and
+ hexadecimal escapes correctly, there are problems with matching
+ 8-bit characters that I don't use enough to properly understand,
+ e.g.:
+ ,----
+ | (string-match-p (rxt-pcre-to-elisp "\\377") "\377") => nil
+ `----
+ A fix for this would be welcome.
+
+ - Most of PCRE's rules for how `^', `\A', `$' and `\Z' interact with
+ newlines are not implemented, since they seem less relevant to
+ Emacs's buffer-oriented rather than line-oriented model. However,
+ the different meanings of the `.' metacharacter *are* implemented
+ (it matches newlines with the `/s' flag, but not otherwise).
+
+ - Not currently namespace clean (both `rxt-' and a couple of `pcre-'
+ functions).
+
+
+3.3 TODO:
+~~~~~~~~~
+
+ - Python-specific extensions to PCRE?
+ - Language-specific stuff to enable regexp font-locking and explaining
+ in different modes. Each language would need two functions, which
+ could be kept in an alist:
+
+ 1. A function to read PCRE regexps, taking the string syntax into
+ account. E.g., Python has single-quoted, double-quoted and raw
+ strings, each with different quoting rules. PHP has the kind of
+ belt-and-suspenders solution you would expect: regexps are in
+ strings, /and/ you have to include the `/ ... /' delimiters!
+ Duh.
+
+ 2. A function to copy faces back from the parsed string to the
+ original buffer text. This has to recognize any escape sequences
+ so they can be treated as a single character.
+
+
+4 Internal details
+==================
+
+ `rxt' defines an internal syntax tree representation of regular
+ expressions, parsers for Elisp and PCRE syntax, and 'unparsers'
+ to convert the internal representation to PCRE or `rx' syntax.
+ Converting from the internal representation to Emacs syntax is
+ done by converting to `rx' form and passing it to `rx-to-string'.
+ See `rxt-parse-re', `rxt-adt->pcre', and `rxt-adt->rx' for
+ details.
+
+ This code is partially based on Olin Shivers' reference SRE
+ implementation in scsh, although it is simplified in some respects and
+ extended in others. See `scsh/re.scm', `scsh/spencer.scm' and
+ `scsh/posixstr.scm' in the `scsh' source tree for details. In
+ particular, `pcre2el' steals the idea of an abstract data type for
+ regular expressions and the general structure of the string regexp
+ parser and unparser. The data types for character sets are extended in
+ order to support symbolic translation between character set
+ expressions without assuming a small (Latin1) character set. The
+ string parser is also extended to parse a bigger variety of
+ constructions, including POSIX character classes and various Emacs and
+ Perl regexp assertions. Otherwise, only the bare minimum of scsh's
+ abstract data type is implemented.
+
+
+5 Soapbox
+=========
+
+ Emacs regexps have their annoyances, but it is worth getting used to
+ them. The Emacs assertions for word boundaries, symbol boundaries, and
+ syntax classes depending on the syntax of the mode in effect are
+ especially useful. (PCRE has `\b' for word-boundary, but AFAIK it
+ doesn't have separate assertions for beginning-of-word and
+ end-of-word). Other things that might be done with huge regexps in
+ other languages can be expressed more understandably in Elisp using
+ combinations of `save-excursion' with the various searches (regexp,
+ literal, skip-syntax-forward, sexp-movement functions, etc.).
+
+ There's not much point in using `rxt-pcre-to-elisp' to use PCRE
+ notation in a Lisp program you're going to maintain, since you still
+ have to double all the backslashes. Better to just use the converted
+ result (or better yet, the `rx' form).
+
+
+6 History and acknowledgments
+=============================
+
+ This was originally created out of an answer to a stackoverflow
+ question:
+ [http://stackoverflow.com/questions/9118183/elisp-mechanism-for-converting-pcre-regexps-to-emacs-regexps]
+
+ Thanks to:
+
+ - Wes Hardaker (hardaker) for the initial inspiration and subsequent
+ hacking
+ - priyadarshan for requesting RX support
+ - Daniel Colascione (dcolascione) for a patch to support Emacs's
+ explicitly-numbered match groups
+ - Aaron Meurer (asmeurer) for requesting Isearch support
+ - Philippe Vaucher (silex) for a patch to support `ibuffer-do-replace-regexp'
+ in PCRE mode
--- /dev/null
+;;; sesman-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
+\f
+;;;### (autoloads nil "sesman" "sesman.el" (23997 31641 490594 106000))
+;;; Generated autoloads from sesman.el
+
+(autoload 'sesman-start "sesman" "\
+Start a Sesman session.
+
+\(fn)" t nil)
+
+(autoload 'sesman-restart "sesman" "\
+Restart sesman session.
+When WHICH is nil, restart the current session; when a single universal
+argument or 'linked, restart all linked sessions; when a double universal
+argument, t or 'all, restart all sessions. For programmatic use, WHICH can also
+be a session or a name of the session, in which case that session is restarted.
+
+\(fn &optional WHICH)" t nil)
+
+(autoload 'sesman-quit "sesman" "\
+Terminate a Sesman session.
+When WHICH is nil, kill only the current session; when a single universal
+argument or 'linked, kill all linked sessions; when a double universal argument,
+t or 'all, kill all sessions. For programmatic use, WHICH can also be a session
+or a name of the session, in which case that session is killed.
+
+\(fn &optional WHICH)" t nil)
+
+(autoload 'sesman-info "sesman" "\
+Display info for all current sessions (`sesman-current-sessions').
+In the resulting minibuffer display linked sessions are numbered and the
+other (friendly) sessions are not. When ALL is non-nil, show info for all
+sessions.
+
+\(fn &optional ALL)" t nil)
+
+(autoload 'sesman-link-with-buffer "sesman" "\
+Ask for SESSION and link with BUFFER.
+BUFFER defaults to current buffer. On universal argument, or if BUFFER is 'ask,
+ask for buffer.
+
+\(fn &optional BUFFER SESSION)" t nil)
+
+(autoload 'sesman-link-with-directory "sesman" "\
+Ask for SESSION and link with DIR.
+DIR defaults to `default-directory'. On universal argument, or if DIR is 'ask,
+ask for directory.
+
+\(fn &optional DIR SESSION)" t nil)
+
+(autoload 'sesman-link-with-project "sesman" "\
+Ask for SESSION and link with PROJECT.
+PROJECT defaults to current project. On universal argument, or if PROJECT is
+'ask, ask for the project. SESSION defaults to the current session.
+
+\(fn &optional PROJECT SESSION)" t nil)
+
+(autoload 'sesman-link-with-least-specific "sesman" "\
+Ask for SESSION and link with the least specific context available.
+Normally the least specific context is the project. If not in a project, link
+with the `default-directory'. If `default-directory' is nil, link with current
+buffer.
+
+\(fn &optional SESSION)" t nil)
+
+(autoload 'sesman-unlink "sesman" "\
+Break any of the previously created links.
+
+\(fn)" t nil)
+ (autoload 'sesman-map "sesman" "Session management prefix keymap." t 'keymap)
+
+;;;***
+\f
+;;;### (autoloads nil "sesman-browser" "sesman-browser.el" (23997
+;;;;;; 31641 489204 100000))
+;;; Generated autoloads from sesman-browser.el
+
+(autoload 'sesman-browser "sesman-browser" "\
+Display an interactive session browser.
+See `sesman-browser-mode' for more details.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads nil nil ("sesman-pkg.el") (23997 31641 487487 99000))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; sesman-autoloads.el ends here
--- /dev/null
+;;; sesman-browser.el --- Interactive Browser for Sesman -*- lexical-binding: t -*-
+;;
+;; Copyright (C) 2018, Vitalie Spinu
+;; Author: Vitalie Spinu
+;; URL: https://github.com/vspinu/sesman
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; This file is *NOT* part of GNU Emacs.
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 3, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+;; Floor, Boston, MA 02110-1301, USA.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+;;
+;; Interactive session browser.
+;;
+;;; Code:
+
+(require 'seq)
+(require 'sesman)
+
+(defgroup sesman-browser nil
+ "Browser for Sesman."
+ :prefix "sesman-browser-"
+ :group 'sesman
+ :link '(url-link :tag "GitHub" "https://github.com/vspinu/sesman"))
+
+(defface sesman-browser-highligh-face
+ '((default (:inherit highlight :weight bold)))
+ "Face used to highlight currently selected button."
+ :group 'sesman-browser)
+
+(defface sesman-browser-button-face
+ '((default (:inherit button :slant italic)))
+ "Face used to highlight currently selected object."
+ :group 'sesman-browser)
+
+(defvar-local sesman-browser--sort-types '(name relevance))
+(defcustom sesman-browser-sort-type 'name
+ "Default sorting type in sesman browser buffers.
+Currently can be either 'name or 'relevance."
+ :type '(choice (const name) (const relevance))
+ :group 'sesman-browser)
+
+(defvar sesman-browser-map
+ (let (sesman-browser-map)
+ (define-prefix-command 'sesman-browser-map)
+ (define-key sesman-browser-map (kbd "r") #'sesman-browser-restart-session)
+ (define-key sesman-browser-map (kbd "q") #'sesman-browser-quit-session)
+ (define-key sesman-browser-map (kbd "b") #'sesman-browser-link-with-buffer)
+ (define-key sesman-browser-map (kbd "d") #'sesman-browser-link-with-directory)
+ (define-key sesman-browser-map (kbd "p") #'sesman-browser-link-with-project)
+ (define-key sesman-browser-map (kbd "u") #'sesman-browser-unlink)
+ sesman-browser-map)
+ "Prefix keymap for sesman commands from sesman browser.")
+
+(defvar sesman-browser-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "n") #'sesman-browser-vertical-next)
+ (define-key map (kbd "p") #'sesman-browser-vertical-prev)
+ (define-key map (kbd "f") #'sesman-browser-forward)
+ (define-key map (kbd "b") #'sesman-browser-backward)
+ (define-key map [remap forward-paragraph] #'sesman-browser-session-next)
+ (define-key map [remap backward-paragraph] #'sesman-browser-session-prev)
+ (define-key map (kbd "C-M-n") #'sesman-browser-session-next)
+ (define-key map (kbd "C-M-p") #'sesman-browser-session-prev)
+ (define-key map (kbd "<tab>") #'sesman-browser-forward)
+ (define-key map (kbd "<backtab>") #'sesman-browser-backward)
+ (define-key map (kbd "<RET>") #'sesman-goto)
+ (define-key map (kbd "o") #'sesman-show)
+ (define-key map (kbd "t") #'sesman-browser-toggle-sort)
+ (define-key map (kbd "S") #'sesman-browser-toggle-sort)
+ (define-key map (kbd "l b") #'sesman-browser-link-with-buffer)
+ (define-key map (kbd "l d") #'sesman-browser-link-with-directory)
+ (define-key map (kbd "l p") #'sesman-browser-link-with-project)
+ (define-key map (kbd "u") #'sesman-browser-unlink)
+ (define-key map (kbd "s") 'sesman-browser-map)
+ (define-key map (kbd "C-c C-s") 'sesman-browser-map)
+ (easy-menu-define sesman-browser-mode-map map
+ "Sesman Browser"
+ '("SesmanBrowser"
+ ["Next row" sesman-browser-vertical-next]
+ ["Previous row" sesman-browser-vertical-prev]
+ ["Next button" sesman-browser-forward]
+ ["Previous button" sesman-browser-backward]
+ ["Next session" sesman-browser-session-next]
+ ["Previous session" sesman-browser-session-prev]
+ "--"
+ ["Goto buffer" sesman-goto]
+ ["Show buffer" sesman-show]
+ "--"
+ ["Link with Buffer" sesman-browser-link-with-buffer]
+ ["Link with Directory" sesman-browser-link-with-directory]
+ ["Link with Project" sesman-browser-link-with-project]
+ ["Unlink" sesman-browser-unlink]
+ "--"
+ ["Toggle sort" sesman-browser-toggle-sort]
+ ["Refresh View" revert-buffer]))
+ map)
+ "Local keymap in `sesman-browser-mode'.")
+
+\f
+;;; Utilities
+
+(defun sesman-browser--closeby-pos (prop lax)
+ (or (when (get-text-property (point) prop)
+ (point))
+ (when (and (not (bobp))
+ (get-text-property (1- (point)) prop))
+ (1- (point)))
+ (when lax
+ (let ((next (save-excursion
+ (and
+ (goto-char (next-single-char-property-change (point) prop))
+ (get-text-property (point) prop)
+ (point))))
+ (prev (save-excursion
+ (and
+ (goto-char (previous-single-char-property-change (point) prop))
+ (not (bobp))
+ (get-text-property (1- (point)) prop)
+ (1- (point))))))
+ (if next
+ (if prev
+ (if (< (- (point) prev) (- next (point)))
+ prev
+ next)
+ next)
+ prev)))))
+
+(defun sesman-browser--closeby-value (prop lax)
+ (when-let ((pos (sesman-browser--closeby-pos prop lax)))
+ (get-text-property pos prop)))
+
+(defun sesman-browser-get (what &optional no-error lax)
+ "Get value of the property WHAT at point.
+If NO-ERROR is non-nil, don't throw an error if no value has been found and
+return nil. If LAX is non-nil, search nearby and return the closest value."
+ (when (derived-mode-p 'sesman-browser-mode)
+ (or (let ((prop (pcase what
+ ('session :sesman-session)
+ ('link :sesman-link)
+ ('object :sesman-object)
+ (_ what))))
+ (sesman-browser--closeby-value prop 'lax))
+ (unless no-error
+ (user-error "No %s %s" what (if lax "nearby" "at point"))))))
+
+\f
+;;; Navigation
+
+(defvar-local sesman-browser--section-overlay nil)
+(defvar-local sesman-browser--stop-overlay nil)
+
+(when (fboundp 'define-fringe-bitmap)
+ (define-fringe-bitmap 'sesman-left-bar
+ [#b00001100] nil nil '(top t)))
+
+(defun sesman-browser--next (prop)
+ (let ((pos (point)))
+ (goto-char (previous-single-char-property-change (point) prop))
+ (unless (get-text-property (point) prop)
+ (goto-char (previous-single-char-property-change (point) prop)))
+ (when (bobp)
+ (goto-char pos))))
+
+(defun sesman-browser--prev (prop)
+ (let ((pos (point)))
+ (goto-char (next-single-char-property-change (point) prop))
+ (unless (get-text-property (point) prop)
+ (goto-char (next-single-char-property-change (point) prop)))
+ (when (eobp)
+ (goto-char pos))))
+
+(defun sesman-browser-forward ()
+ "Go to next button."
+ (interactive)
+ (sesman-browser--prev :sesman-stop))
+
+(defun sesman-browser-backward ()
+ "Go to previous button."
+ (interactive)
+ (sesman-browser--next :sesman-stop))
+
+(defun sesman-browser-vertical-next ()
+ "Go to next button section or row."
+ (interactive)
+ (sesman-browser--prev :sesman-vertical-stop))
+
+(defun sesman-browser-vertical-prev ()
+ "Go to previous button section or row."
+ (interactive)
+ (sesman-browser--next :sesman-vertical-stop))
+
+(defun sesman-browser-session-next ()
+ "Go to next session."
+ (interactive)
+ (sesman-browser--prev :sesman-session-stop))
+
+(defun sesman-browser-session-prev ()
+ "Go to previous session."
+ (interactive)
+ (sesman-browser--next :sesman-session-stop))
+
+\f
+;;; Display
+
+(defun sesman-goto (&optional no-switch)
+ "Go to most relevant buffer for session at point.
+If NO-SWITCH is non-nil, only display the buffer."
+ (interactive "P")
+ (let ((object (get-text-property (point) :sesman-object)))
+ (if (and object (bufferp object))
+ (if no-switch
+ (display-buffer object)
+ (pop-to-buffer object))
+ (let* ((session (sesman-browser-get 'session))
+ (info (sesman-session-info (sesman--system) session))
+ (buffers (or (plist-get info :buffers)
+ (let ((objects (plist-get info :objects)))
+ (seq-filter #'bufferp objects)))))
+ (if buffers
+ (let ((most-recent-buf (seq-find (lambda (b)
+ (member b buffers))
+ (buffer-list))))
+ (if no-switch
+ (display-buffer most-recent-buf)
+ (pop-to-buffer most-recent-buf)))
+ (user-error "Cannot jump to session %s; it doesn't contain any buffers" (car session)))))))
+
+(defun sesman-show ()
+ "Show the most relevant buffer for the session at point."
+ (interactive)
+ (sesman-goto 'no-switch))
+
+(defun sesman-browser--sensor-function (&rest _ignore)
+ (let ((beg (or (when (get-text-property (point) :sesman-stop)
+ (if (get-text-property (1- (point)) :sesman-stop)
+ (previous-single-char-property-change (point) :sesman-stop)
+ (point)))
+ (next-single-char-property-change (point) :sesman-stop)))
+ (end (next-single-char-property-change (point) :sesman-stop)))
+ (move-overlay sesman-browser--stop-overlay beg end)
+ (when window-system
+ (let ((beg (get-text-property (point) :sesman-fragment-beg))
+ (end (get-text-property (point) :sesman-fragment-end)))
+ (when (and beg end)
+ (move-overlay sesman-browser--section-overlay beg end))))))
+
+\f
+;;; Sesman UI
+
+(defun sesman-browser-quit-session ()
+ "Quite session at point."
+ (interactive)
+ (sesman-quit (sesman-browser-get 'session)))
+
+(defun sesman-browser-restart-session ()
+ "Restart session at point."
+ (interactive)
+ (sesman-restart (sesman-browser-get 'session)))
+
+(defun sesman-browser-link-with-buffer ()
+ "Ask for buffer to link session at point to."
+ (interactive)
+ (let ((session (sesman-browser-get 'session)))
+ (sesman-link-with-buffer 'ask session)))
+
+(defun sesman-browser-link-with-directory ()
+ "Ask for directory to link session at point to."
+ (interactive)
+ (let ((session (sesman-browser-get 'session)))
+ (sesman-link-with-directory 'ask session)))
+
+(defun sesman-browser-link-with-project ()
+ "Ask for project to link session at point to."
+ (interactive)
+ (let ((session (sesman-browser-get 'session)))
+ (sesman-link-with-project 'ask session)))
+
+(defun sesman-browser-unlink ()
+ "Unlink the link at point or ask for link to unlink."
+ (interactive)
+ (if-let ((link (sesman-browser-get 'link 'no-error)))
+ (sesman--unlink link)
+ (if-let ((links (sesman-links (sesman--system)
+ (sesman-browser-get 'session))))
+ (mapc #'sesman--unlink
+ (sesman--ask-for-link "Unlink: " links 'ask-all))
+ (user-error "No links for session %s" (car (sesman-browser-get 'session)))))
+ (run-hooks 'sesman-post-command-hook))
+
+\f
+;;; Major Mode
+
+(defun sesman-browser-revert (&rest _ignore)
+ "Refresh current browser buffer."
+ (let ((pos (point)))
+ (sesman-browser)
+ ;; simple but not particularly reliable or useful
+ (goto-char (min pos (point-max)))))
+
+(defun sesman-browser-revert-all (system)
+ "Refresh all Sesman SYSTEM browsers."
+ (mapc (lambda (b)
+ (with-current-buffer b
+ (when (and (derived-mode-p 'sesman-browser-mode)
+ (eq system (sesman--system)))
+ (sesman-browser-revert))))
+ (buffer-list)))
+
+(defun sesman-browser--goto-stop (stop-value)
+ (let ((search t))
+ (goto-char (point-min))
+ (while search
+ (goto-char (next-single-char-property-change (point) :sesman-stop))
+ (if (eobp)
+ (progn (setq search nil)
+ (goto-char (next-single-char-property-change (point-min) :sesman-stop)))
+ (when (equal (get-text-property (point) :sesman-stop) stop-value)
+ (setq search nil))))))
+
+(defun sesman-browser-toggle-sort ()
+ "Toggle sorting of sessions.
+See `sesman-browser-sort-type' for the default sorting type."
+ (interactive)
+ (when (eq sesman-browser-sort-type
+ (car sesman-browser--sort-types))
+ (pop sesman-browser--sort-types))
+ (unless sesman-browser--sort-types
+ (setq-local sesman-browser--sort-types (default-value 'sesman-browser--sort-types)))
+ (setq sesman-browser-sort-type (pop sesman-browser--sort-types))
+ (let ((stop (sesman-browser-get :sesman-stop nil 'lax)))
+ (sesman-browser)
+ (sesman-browser--goto-stop stop)
+ (sesman-browser--sensor-function))
+ (message "Sorted by %s"
+ (propertize (symbol-name sesman-browser-sort-type) 'face 'bold)))
+
+(define-derived-mode sesman-browser-mode special-mode "SesmanBrowser"
+ "Interactive view of Sesman sessions.
+When applicable, system specific commands are locally bound to j when point is
+on a session object."
+ ;; ensure there is a sesman-system here
+ (sesman--system)
+ (delete-all-overlays)
+ (setq-local sesman-browser--stop-overlay (make-overlay (point) (point)))
+ (overlay-put sesman-browser--stop-overlay 'face 'sesman-browser-highligh-face)
+ (setq-local sesman-browser--section-overlay (make-overlay (point) (point)))
+ (when window-system
+ (let* ((fringe-spec '(left-fringe sesman-left-bar sesman-browser-highligh-face))
+ (dummy-string (propertize "|" 'display fringe-spec)))
+ (overlay-put sesman-browser--section-overlay 'line-prefix dummy-string)))
+ (add-hook 'sesman-post-command-hook 'sesman-browser-revert nil t)
+ (setq-local display-buffer-base-action '(nil . ((inhibit-same-window . t))))
+ (setq-local sesman-browser--sort-types (default-value 'sesman-browser--sort-types))
+ (setq-local revert-buffer-function #'sesman-browser-revert))
+
+(defun sesman-browser--insert-session (system ses i)
+ (let ((ses-name (car ses))
+ (head-template "%17s")
+ beg end)
+ (setq beg (point))
+
+ ;; session header
+ (insert (format "%3d: " i))
+ (insert (propertize (car ses)
+ :sesman-stop ses-name
+ :sesman-vertical-stop t
+ :sesman-session-stop t
+ 'face 'bold
+ 'cursor-sensor-functions (list #'sesman-browser--sensor-function)
+ 'mouse-face 'highlight)
+ "\n")
+
+ ;; links
+ (insert (format head-template "linked-to: "))
+ (let ((link-groups (sesman-grouped-links system ses))
+ (vert-stop))
+ (dolist (grp link-groups)
+ (let* ((type (car grp)))
+ (dolist (link (cdr grp))
+ (when (> (current-column) fill-column)
+ (insert "\n" (format head-template " "))
+ (setq vert-stop nil))
+ (let ((val (sesman--abbrev-path-maybe (sesman--lnk-value link))))
+ (insert (propertize (sesman--format-context type val 'sesman-browser-button-face)
+ :sesman-stop (car link)
+ :sesman-vertical-stop (unless vert-stop (setq vert-stop t))
+ :sesman-link link
+ 'cursor-sensor-functions (list #'sesman-browser--sensor-function)
+ 'mouse-face 'highlight)))
+ (insert " ")))))
+ (insert "\n")
+
+ ;; objects
+ (insert (format head-template "objects: "))
+ (let* ((info (sesman-session-info system ses))
+ (map (plist-get info :map))
+ (objects (plist-get info :objects))
+ (strings (or (plist-get info :strings)
+ (mapcar (lambda (x) (format "%s" x)) objects)))
+ (kvals (seq-mapn #'cons objects strings))
+ (kvals (seq-sort (lambda (a b) (string-lessp (cdr a) (cdr b)))
+ kvals))
+ (vert-stop))
+ (dolist (kv kvals)
+ (when (> (current-column) fill-column)
+ (insert "\n" (format head-template " "))
+ (setq vert-stop nil))
+ (let ((str (replace-regexp-in-string ses-name "%s" (cdr kv) nil t)))
+ (insert (propertize str
+ :sesman-stop str
+ :sesman-vertical-stop (unless vert-stop (setq vert-stop t))
+ :sesman-object (car kv)
+ 'cursor-sensor-functions (list #'sesman-browser--sensor-function)
+ 'face 'sesman-browser-button-face
+ 'mouse-face 'highlight
+ 'help-echo "mouse-2: visit in other window"
+ 'keymap map)
+ " "))))
+
+ ;; session properties
+ (setq end (point))
+ (put-text-property beg end :sesman-session ses)
+ (put-text-property beg end :sesman-session-name ses-name)
+ (put-text-property beg end :sesman-fragment-beg beg)
+ (put-text-property beg end :sesman-fragment-end end)
+ (insert "\n\n")))
+
+;;;###autoload
+(defun sesman-browser ()
+ "Display an interactive session browser.
+See `sesman-browser-mode' for more details."
+ (interactive)
+ (let* ((system (sesman--system))
+ (pop-to (called-interactively-p 'any))
+ (sessions (sesman-sessions system))
+ (cur-session (when pop-to
+ (sesman-current-session 'CIDER)))
+ (buff (get-buffer-create (format "*sesman %s browser*" system))))
+ (with-current-buffer buff
+ (setq-local sesman-system system)
+ (sesman-browser-mode)
+ (cursor-sensor-mode 1)
+ (let ((inhibit-read-only t)
+ (sessions (pcase sesman-browser-sort-type
+ ('name (seq-sort (lambda (a b) (string-greaterp (car b) (car a)))
+ sessions))
+ ('relevance (sesman--sort-sessions system sessions))
+ (_ (error "Invalid `sesman-browser-sort-type'"))))
+ (i 0))
+ (erase-buffer)
+ (insert "\n ")
+ (insert (propertize (format "%s Sessions:" system)
+ 'face '(bold font-lock-keyword-face)))
+ (insert "\n\n")
+ (dolist (ses sessions)
+ (setq i (1+ i))
+ (sesman-browser--insert-session system ses i))
+ (when pop-to
+ (pop-to-buffer buff)
+ (sesman-browser--goto-stop (car cur-session)))
+ (sesman-browser--sensor-function)))))
+
+(provide 'sesman-browser)
+;;; sesman-browser.el ends here
--- /dev/null
+(define-package "sesman" "20190909.1754" "Generic Session Manager"
+ '((emacs "25"))
+ :keywords
+ '("process")
+ :authors
+ '(("Vitalie Spinu"))
+ :maintainer
+ '("Vitalie Spinu")
+ :url "https://github.com/vspinu/sesman")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; sesman.el --- Generic Session Manager -*- lexical-binding: t -*-
+;;
+;; Copyright (C) 2018, Vitalie Spinu
+;; Author: Vitalie Spinu
+;; URL: https://github.com/vspinu/sesman
+;; Keywords: process
+;; Version: 0.3.3-DEV
+;; Package-Requires: ((emacs "25"))
+;; Keywords: processes, tools, vc
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; This file is *NOT* part of GNU Emacs.
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 3, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+;; General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program; see the file COPYING. If not, write to
+;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
+;; Floor, Boston, MA 02110-1301, USA.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+;;
+;; Sesman provides facilities for session management and interactive session
+;; association with the current contexts (project, directory, buffers etc). See
+;; project's readme for more details.
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Code:
+
+(require 'cl-generic)
+(require 'seq)
+(require 'subr-x)
+(require 'vc)
+
+(defgroup sesman nil
+ "Generic Session Manager."
+ :prefix "sesman-"
+ :group 'tools
+ :link '(url-link :tag "GitHub" "https://github.com/vspinu/sesman"))
+
+(defface sesman-project-face
+ '((default (:inherit font-lock-doc-face)))
+ "Face used to mark projects."
+ :group 'sesman)
+
+(defface sesman-directory-face
+ '((default (:inherit font-lock-type-face)))
+ "Face used to mark directories."
+ :group 'sesman)
+
+(defface sesman-buffer-face
+ '((default (:inherit font-lock-preprocessor-face)))
+ "Face used to mark buffers."
+ :group 'sesman)
+
+(defcustom sesman-use-friendly-sessions t
+ "If non-nil consider friendly sessions when looking for current sessions.
+The definition of friendly sessions is system dependent but usually means
+sessions running in dependent projects."
+ :group 'sesman
+ :type 'boolean
+ :package-version '(sesman . "0.3.2"))
+
+(defcustom sesman-follow-symlinks 'vc
+ "When non-nil, follow symlinks during the file expansion.
+When nil, don't follow symlinks. When 'vc, follow symlinks only when
+`vc-follow-symlinks' is non-nil. When t, always follow symlinks."
+ :group 'sesman
+ :type '(choice (const :tag "Comply with `vc-follow-symlinks'" vc)
+ (const :tag "Don't follow symlinks" nil)
+ (const :tag "Follow symlinks" t))
+ :package-version '(sesman . "0.3.3"))
+(put 'sesman-follow-symlinks 'safe-local-variable (lambda (x) (memq x '(vc nil t))))
+
+;; (defcustom sesman-disambiguate-by-relevance t
+;; "If t choose most relevant session in ambiguous situations, otherwise ask.
+;; Ambiguity arises when multiple sessions are associated with current context. By
+;; default only projects could be associated with multiple sessions. See
+;; `sesman-single-link-contexts' in order to change that. Relevance is decided by
+;; system's implementation, see `sesman-more-relevant-p'."
+;; :group 'sesman
+;; :type 'boolean)
+
+(defcustom sesman-single-link-context-types '(buffer)
+ "List of context types to which at most one session can be linked."
+ :group 'sesman
+ :type '(repeat symbol)
+ :package-version '(sesman . "0.1.0"))
+
+;; FIXME:
+;; (defcustom sesman-abbreviate-paths 2
+;; "Abbreviate paths to that many parents.
+;; When set to nil, don't abbreviate directories."
+;; :group 'sesman
+;; :type '(choice number
+;; (const :tag "Don't abbreviate" nil)))
+
+(defvar sesman-sessions-hashmap (make-hash-table :test #'equal)
+ "Hash-table of all sesman sessions.
+Key is a cons (system-name . session-name).")
+
+(defvar sesman-links-alist nil
+ "An alist of all sesman links.
+Each element is of the form (key cxt-type cxt-value) where
+\"key\" is of the form (system-name . session-name). system-name
+and cxt-type must be symbols.")
+
+(defvar-local sesman-system nil
+ "Name of the system managed by `sesman'.
+Can be either a symbol, or a function returning a symbol.")
+(put 'sesman-system 'permanent-local 't)
+
+
+\f
+;; Internal Utilities
+
+(defun sesman--on-C-u-u-sessions (system which)
+ (cond
+ ((null which)
+ (let ((ses (sesman-current-session system)))
+ (when ses
+ (list ses))))
+ ((or (equal which '(4)) (eq which 'linked))
+ (sesman--linked-sessions system 'sort))
+ ((or (equal which '(16)) (eq which 'all) (eq which t))
+ (sesman--all-system-sessions system 'sort))
+ ;; session itself
+ ((and (listp which)
+ (or (stringp (car which))
+ (symbolp (car which))))
+ (list which))
+ ;; session name
+ ((or (stringp which)
+ (symbolp which)
+ (gethash (cons system which) sesman-sessions-hashmap)))
+ (t (error "Invalid which argument (%s)" which))))
+
+(defun sesman--cap-system-name (system)
+ (let ((name (symbol-name system)))
+ (if (string-match-p "^[[:upper:]]" name)
+ name
+ (capitalize name))))
+
+(defun sesman--least-specific-context (system)
+ (seq-some (lambda (ctype)
+ (when-let (val (sesman-context ctype system))
+ (cons ctype val)))
+ (reverse (sesman-context-types system))))
+
+(defun sesman--link-session-interactively (session cxt-type cxt-val)
+ (let ((system (sesman--system)))
+ (unless cxt-type
+ (let ((cxt (sesman--least-specific-context system)))
+ (setq cxt-type (car cxt)
+ cxt-val (cdr cxt))))
+ (let ((cxt-name (symbol-name cxt-type)))
+ (if (member cxt-type (sesman-context-types system))
+ (let ((session (or session
+ (sesman-ask-for-session
+ system
+ (format "Link with %s %s: "
+ cxt-name (sesman--abbrev-path-maybe
+ (sesman-context cxt-type system)))
+ (sesman--all-system-sessions system 'sort)
+ 'ask-new))))
+ (sesman-link-session system session cxt-type cxt-val))
+ (error (format "%s association not allowed for this system (%s)"
+ (capitalize cxt-name)
+ system))))))
+
+;; FIXME: incorporate `sesman-abbreviate-paths'
+(defun sesman--abbrev-path-maybe (obj)
+ (if (stringp obj)
+ (abbreviate-file-name obj)
+ obj))
+
+(defun sesman--system-in-buffer (&optional buffer)
+ (with-current-buffer (or buffer (current-buffer))
+ (if (functionp sesman-system)
+ (funcall sesman-system)
+ sesman-system)))
+
+(defun sesman--system ()
+ (if sesman-system
+ (if (functionp sesman-system)
+ (funcall sesman-system)
+ sesman-system)
+ (error "No `sesman-system' in buffer `%s'" (current-buffer))))
+
+(defun sesman--linked-sessions (system &optional sort cxt-types)
+ (let* ((system (or system (sesman--system)))
+ (cxt-types (or cxt-types (sesman-context-types system))))
+ ;; just in case some links are lingering due to user errors
+ (sesman--clear-links)
+ (delete-dups
+ (mapcar (lambda (assoc)
+ (gethash (car assoc) sesman-sessions-hashmap))
+ (sesman-current-links system nil sort cxt-types)))))
+
+(defun sesman--friendly-sessions (system &optional sort)
+ (let ((sessions (seq-filter (lambda (ses) (sesman-friendly-session-p system ses))
+ (sesman--all-system-sessions system))))
+ (if sort
+ (sesman--sort-sessions system sessions)
+ sessions)))
+
+(defun sesman--all-system-sessions (&optional system sort)
+ "Return a list of sessions registered with SYSTEM.
+If SORT is non-nil, sort in relevance order."
+ (let ((system (or system (sesman--system)))
+ sessions)
+ (maphash
+ (lambda (k s)
+ (when (eql (car k) system)
+ (push s sessions)))
+ sesman-sessions-hashmap)
+ (if sort
+ (sesman--sort-sessions system sessions)
+ sessions)))
+
+;; FIXME: make this a macro
+(defun sesman--link-lookup-fn (&optional system ses-name cxt-type cxt-val x)
+ (let ((system (or system (caar x)))
+ (ses-name (or ses-name (cdar x)))
+ (cxt-type (or cxt-type (nth 1 x)))
+ (cxt-val (or cxt-val (nth 2 x))))
+ (lambda (el)
+ (and (or (null system) (eq (caar el) system))
+ (or (null ses-name) (equal (cdar el) ses-name))
+ (or (null cxt-type)
+ (if (listp cxt-type)
+ (member (nth 1 el) cxt-type)
+ (eq (nth 1 el) cxt-type)))
+ (or (null cxt-val) (equal (nth 2 el) cxt-val))))))
+
+(defun sesman--unlink (x)
+ (setq sesman-links-alist
+ (seq-remove (sesman--link-lookup-fn nil nil nil nil x)
+ sesman-links-alist)))
+
+(defun sesman--clear-links ()
+ (setq sesman-links-alist
+ (seq-filter (lambda (x)
+ (gethash (car x) sesman-sessions-hashmap))
+ sesman-links-alist)))
+
+(defun sesman--format-session-objects (system session &optional sep)
+ (let ((info (sesman-session-info system session)))
+ (if (and (listp info)
+ (keywordp (car info)))
+ (let ((ses-name (car session))
+ (sep (or sep " "))
+ (strings (or (plist-get info :strings)
+ (mapcar (lambda (x) (format "%s" x))
+ (plist-get info :objects)))))
+ (mapconcat (lambda (str)
+ (replace-regexp-in-string ses-name "..." str nil t))
+ strings sep))
+ (format "%s" info))))
+
+(defun sesman--format-session (system ses &optional prefix)
+ (format (propertize "%s%s [%s] linked-to %s" 'face 'bold)
+ (or prefix "")
+ (propertize (car ses) 'face 'bold)
+ (propertize (sesman--format-session-objects system ses ", ") 'face 'italic)
+ (sesman-grouped-links system ses t t)))
+
+(defun sesman--format-link (link)
+ (let* ((system (sesman--lnk-system-name link))
+ (session (gethash (car link) sesman-sessions-hashmap)))
+ (format "%s(%s) -> %s [%s]"
+ (sesman--lnk-context-type link)
+ (propertize (format "%s" (sesman--abbrev-path-maybe (sesman--lnk-value link)))
+ 'face 'bold)
+ (propertize (sesman--lnk-session-name link) 'face 'bold)
+ (if session
+ (sesman--format-session-objects system session)
+ "invalid"))))
+
+(defun sesman--ask-for-link (prompt links &optional ask-all)
+ (let* ((name.keys (mapcar (lambda (link)
+ (cons (sesman--format-link link) link))
+ links))
+ (name.keys (append name.keys
+ (when (and ask-all (> (length name.keys) 1))
+ '(("*all*")))))
+ (nms (mapcar #'car name.keys))
+ (sel (completing-read prompt nms nil t nil nil (car nms))))
+ (cond ((string= sel "*all*")
+ links)
+ (ask-all
+ (list (cdr (assoc sel name.keys))))
+ (t
+ (cdr (assoc sel name.keys))))))
+
+(defun sesman--sort-sessions (system sessions)
+ (seq-sort (lambda (x1 x2)
+ (sesman-more-relevant-p system x1 x2))
+ sessions))
+
+(defun sesman--sort-links (system links)
+ (seq-sort (lambda (x1 x2)
+ (sesman-more-relevant-p system
+ (gethash (car x1) sesman-sessions-hashmap)
+ (gethash (car x2) sesman-sessions-hashmap)))
+ links))
+
+;; link data structure accessors
+(defun sesman--lnk-system-name (lnk)
+ (caar lnk))
+(defun sesman--lnk-session-name (lnk)
+ (cdar lnk))
+(defun sesman--lnk-context-type (lnk)
+ (cadr lnk))
+(defun sesman--lnk-value (lnk)
+ (nth 2 lnk))
+
+\f
+;;; User Interface
+
+(defun sesman-post-command-hook nil
+ "Normal hook ran after every state-changing Sesman command.")
+
+;;;###autoload
+(defun sesman-start ()
+ "Start a Sesman session."
+ (interactive)
+ (let ((system (sesman--system)))
+ (message "Starting new %s session ..." system)
+ (prog1 (sesman-start-session system)
+ (run-hooks 'sesman-post-command-hook))))
+
+;;;###autoload
+(defun sesman-restart (&optional which)
+ "Restart sesman session.
+When WHICH is nil, restart the current session; when a single universal
+argument or 'linked, restart all linked sessions; when a double universal
+argument, t or 'all, restart all sessions. For programmatic use, WHICH can also
+be a session or a name of the session, in which case that session is restarted."
+ (interactive "P")
+ (let* ((system (sesman--system))
+ (sessions (sesman--on-C-u-u-sessions system which)))
+ (if (null sessions)
+ (message "No %s sessions found" system)
+ (with-temp-message (format "Restarting %s %s %s" system
+ (if (= 1 (length sessions)) "session" "sessions")
+ (mapcar #'car sessions))
+ (mapc (lambda (s)
+ (sesman-restart-session system s))
+ sessions))
+ ;; restarting is not guaranteed to finish here, but what can we do?
+ (run-hooks 'sesman-post-command-hook))))
+
+;;;###autoload
+(defun sesman-quit (&optional which)
+ "Terminate a Sesman session.
+When WHICH is nil, kill only the current session; when a single universal
+argument or 'linked, kill all linked sessions; when a double universal argument,
+t or 'all, kill all sessions. For programmatic use, WHICH can also be a session
+or a name of the session, in which case that session is killed."
+ (interactive "P")
+ (let* ((system (sesman--system))
+ (sessions (sesman--on-C-u-u-sessions system which)))
+ (if (null sessions)
+ (message "No %s sessions found" system)
+ (with-temp-message (format "Killing %s %s %s" system
+ (if (= 1 (length sessions)) "session" "sessions")
+ (mapcar #'car sessions))
+ (mapc (lambda (s)
+ (sesman-unregister system s)
+ (sesman-quit-session system s))
+ sessions))
+ (run-hooks 'sesman-post-command-hook))))
+
+;;;###autoload
+(defun sesman-info (&optional all)
+ "Display info for all current sessions (`sesman-current-sessions').
+In the resulting minibuffer display linked sessions are numbered and the
+other (friendly) sessions are not. When ALL is non-nil, show info for all
+sessions."
+ (interactive "P")
+ (let* ((system (sesman--system))
+ (i 1)
+ (sessions (if all
+ (sesman-sessions system t)
+ (sesman-current-sessions system)))
+ (empty-prefix (if (> (length sessions) 1) " " "")))
+ (if sessions
+ (message (mapconcat (lambda (ses)
+ (let ((prefix (if (sesman-relevant-session-p system ses)
+ (prog1 (format "%d " i)
+ (setq i (1+ i)))
+ empty-prefix)))
+ (sesman--format-session system ses prefix)))
+ sessions
+ "\n"))
+ (message "No %s%s sessions"
+ (if all "" "current ")
+ system))))
+
+;;;###autoload
+(defun sesman-link-with-buffer (&optional buffer session)
+ "Ask for SESSION and link with BUFFER.
+BUFFER defaults to current buffer. On universal argument, or if BUFFER is 'ask,
+ask for buffer."
+ (interactive "P")
+ (let ((buf (if (or (eq buffer 'ask)
+ (equal buffer '(4)))
+ (let ((this-system (sesman--system)))
+ (read-buffer "Link buffer: " (current-buffer) t
+ (lambda (buf-cons)
+ (equal this-system
+ (sesman--system-in-buffer (cdr buf-cons))))))
+ (or buffer (current-buffer)))))
+ (sesman--link-session-interactively session 'buffer buf)))
+
+;;;###autoload
+(defun sesman-link-with-directory (&optional dir session)
+ "Ask for SESSION and link with DIR.
+DIR defaults to `default-directory'. On universal argument, or if DIR is 'ask,
+ask for directory."
+ (interactive "P")
+ (let ((dir (if (or (eq dir 'ask)
+ (equal dir '(4)))
+ (read-directory-name "Link directory: ")
+ (or dir default-directory))))
+ (sesman--link-session-interactively session 'directory dir)))
+
+;;;###autoload
+(defun sesman-link-with-project (&optional project session)
+ "Ask for SESSION and link with PROJECT.
+PROJECT defaults to current project. On universal argument, or if PROJECT is
+'ask, ask for the project. SESSION defaults to the current session."
+ (interactive "P")
+ (let* ((system (sesman--system))
+ (project (expand-file-name
+ (if (or (eq project 'ask)
+ (equal project '(4)))
+ ;; FIXME: should be a completion over all known projects for this system
+ (read-directory-name "Project: " (sesman-project system))
+ (or project (sesman-project system))))))
+ (sesman--link-session-interactively session 'project project)))
+
+;;;###autoload
+(defun sesman-link-with-least-specific (&optional session)
+ "Ask for SESSION and link with the least specific context available.
+Normally the least specific context is the project. If not in a project, link
+with the `default-directory'. If `default-directory' is nil, link with current
+buffer."
+ (interactive "P")
+ (sesman--link-session-interactively session nil nil))
+
+;;;###autoload
+(defun sesman-unlink ()
+ "Break any of the previously created links."
+ (interactive)
+ (let* ((system (sesman--system))
+ (links (or (sesman-current-links system)
+ (user-error "No %s links found" system))))
+ (mapc #'sesman--unlink
+ (sesman--ask-for-link "Unlink: " links 'ask-all)))
+ (run-hooks 'sesman-post-command-hook))
+
+(declare-function sesman-browser "sesman-browser")
+;;;###autoload (autoload 'sesman-map "sesman" "Session management prefix keymap." t 'keymap)
+(defvar sesman-map
+ (let (sesman-map)
+ (define-prefix-command 'sesman-map)
+ (define-key sesman-map (kbd "C-i") #'sesman-info)
+ (define-key sesman-map (kbd "i") #'sesman-info)
+ (define-key sesman-map (kbd "C-w") #'sesman-browser)
+ (define-key sesman-map (kbd "w") #'sesman-browser)
+ (define-key sesman-map (kbd "C-s") #'sesman-start)
+ (define-key sesman-map (kbd "s") #'sesman-start)
+ (define-key sesman-map (kbd "C-r") #'sesman-restart)
+ (define-key sesman-map (kbd "r") #'sesman-restart)
+ (define-key sesman-map (kbd "C-q") #'sesman-quit)
+ (define-key sesman-map (kbd "q") #'sesman-quit)
+ (define-key sesman-map (kbd "C-l") #'sesman-link-with-least-specific)
+ (define-key sesman-map (kbd "l") #'sesman-link-with-least-specific)
+ (define-key sesman-map (kbd "C-b") #'sesman-link-with-buffer)
+ (define-key sesman-map (kbd "b") #'sesman-link-with-buffer)
+ (define-key sesman-map (kbd "C-d") #'sesman-link-with-directory)
+ (define-key sesman-map (kbd "d") #'sesman-link-with-directory)
+ (define-key sesman-map (kbd "C-p") #'sesman-link-with-project)
+ (define-key sesman-map (kbd "p") #'sesman-link-with-project)
+ (define-key sesman-map (kbd "C-u") #'sesman-unlink)
+ (define-key sesman-map (kbd " u") #'sesman-unlink)
+ sesman-map)
+ "Session management prefix keymap.")
+
+(defvar sesman-menu
+ '("Sesman"
+ ["Show Session Info" sesman-info]
+ "--"
+ ["Start" sesman-start]
+ ["Restart" sesman-restart :active (sesman-current-session (sesman--system))]
+ ["Quit" sesman-quit :active (sesman-current-session (sesman--system))]
+ "--"
+ ["Link with Buffer" sesman-link-with-buffer :active (sesman-current-session (sesman--system))]
+ ["Link with Directory" sesman-link-with-directory :active (sesman-current-session (sesman--system))]
+ ["Link with Project" sesman-link-with-project :active (sesman-current-session (sesman--system))]
+ ["Unlink" sesman-unlink :active (sesman-current-session (sesman--system))]
+ "--"
+ ["Browser" sesman-browser :active (sesman-current-session (sesman--system))])
+ "Sesman Menu.")
+
+(defun sesman-install-menu (map)
+ "Install `sesman-menu' into MAP."
+ (easy-menu-do-define 'sesman-menu-open
+ map
+ (get 'sesman-menu 'variable-documentation)
+ sesman-menu))
+
+\f
+;;; System Generic
+
+(cl-defgeneric sesman-start-session (system)
+ "Start and return SYSTEM SESSION.")
+
+(cl-defgeneric sesman-quit-session (system session)
+ "Terminate SYSTEM SESSION.")
+
+(cl-defgeneric sesman-restart-session (system session)
+ "Restart SYSTEM SESSION.
+By default, calls `sesman-quit-session' and then
+`sesman-start-session'."
+ (let ((old-name (car session)))
+ (sesman-quit-session system session)
+ (let ((new-session (sesman-start-session system)))
+ (setcar new-session old-name))))
+
+(cl-defgeneric sesman-session-info (_system session)
+ "Return a plist with :objects key containing user \"visible\" objects.
+Optional :strings value is a list of string representations of objects. Optional
+:map key is a local keymap to place on every object in the session browser.
+Optional :buffers is a list of buffers which will be used for navigation from
+the session browser. If :buffers is missing, buffers from :objects are used
+instead."
+ (list :objects (cdr session)))
+
+(cl-defgeneric sesman-project (_system)
+ "Retrieve project root in current directory (`default-directory') for SYSTEM.
+Return a string or nil if no project has been found."
+ nil)
+
+(cl-defgeneric sesman-more-relevant-p (_system session1 session2)
+ "Return non-nil if SESSION1 should be sorted before SESSION2.
+By default, sort by session name. Systems should overwrite this method to
+provide a more meaningful ordering. If your system objects are buffers you can
+use `sesman-more-recent-p' utility in this method."
+ (not (string-greaterp (car session1) (car session2))))
+
+(cl-defgeneric sesman-friendly-session-p (_system _session)
+ "Return non-nil if SESSION is a friendly session in current context.
+The \"friendship\" is system dependent but usually means sessions running in
+dependent projects. Unless SYSTEM has defined a method for this generic, there
+are no friendly sessions."
+ nil)
+
+(cl-defgeneric sesman-context-types (_system)
+ "Return a list of context types understood by SYSTEM.
+Contexts must be sorted from most specific to least specific."
+ '(buffer directory project))
+
+\f
+;;; System API
+
+(defun sesman-session (system session-name)
+ "Retrieve SYSTEM's session with SESSION-NAME from global hash."
+ (let ((system (or system (sesman--system))))
+ (gethash (cons system session-name) sesman-sessions-hashmap)))
+
+(defun sesman-sessions (system &optional sort type cxt-types)
+ "Return a list of sessions registered with SYSTEM.
+When TYPE is either 'all or nil return all sessions registered with the SYSTEM,
+when 'linked, only linked to the current context sessions, when 'friendly - only
+friendly sessions. If SORT is non-nil, sessions are sorted in the relevance
+order with linked sessions leading the list. CXT-TYPES is a list of context
+types to consider for linked sessions."
+ (let ((system (or system (sesman--system))))
+ (cond
+ ((eq type 'linked)
+ (sesman--linked-sessions system sort cxt-types))
+ ((eq type 'friendly)
+ (sesman--friendly-sessions system sort))
+ ((memq type '(all nil))
+ (if sort
+ (delete-dups
+ (append (sesman--linked-sessions system 'sort cxt-types)
+ (sesman--all-system-sessions system 'sort)))
+ (sesman--all-system-sessions system)))
+ (t (error "Invalid session TYPE argument %s" type)))))
+
+(defun sesman-current-sessions (system &optional cxt-types)
+ "Return a list of SYSTEM sessions active in the current context.
+Sessions are ordered by the relevance order and linked sessions come first. If
+`sesman-use-friendly-sessions' current sessions consist of linked and friendly
+sessions, otherwise only of linked sessions. CXT-TYPES is a list of context
+types to consider. Defaults to the list returned from `sesman-context-types'."
+ (if sesman-use-friendly-sessions
+ (delete-dups
+ (append (sesman--linked-sessions system 'sort cxt-types)
+ (sesman--friendly-sessions system 'sort)))
+ (sesman--linked-sessions system 'sort cxt-types)))
+
+(defun sesman-current-session (system &optional cxt-types)
+ "Get the most relevant current session for the SYSTEM.
+CXT-TYPES is a list of context types to consider."
+ (or (car (sesman--linked-sessions system 'sort cxt-types))
+ (car (sesman--friendly-sessions system 'sort))))
+
+(defun sesman-ensure-session (system &optional cxt-types)
+ "Get the most relevant linked session for SYSTEM or throw if none exists.
+CXT-TYPES is a list of context types to consider."
+ (or (sesman-current-session system cxt-types)
+ (user-error "No linked %s sessions" system)))
+
+(defun sesman-has-sessions-p (system)
+ "Return t if there is at least one session registered with SYSTEM."
+ (let ((system (or system (sesman--system)))
+ (found))
+ (condition-case nil
+ (maphash (lambda (k _)
+ (when (eq (car k) system)
+ (setq found t)
+ (throw 'found nil)))
+ sesman-sessions-hashmap)
+ (error))
+ found))
+
+(defvar sesman--select-session-history nil)
+(defun sesman-ask-for-session (system prompt &optional sessions ask-new ask-all)
+ "Ask for a SYSTEM session with PROMPT.
+SESSIONS defaults to value returned from `sesman-sessions'. If
+ASK-NEW is non-nil, offer *new* option to start a new session. If
+ASK-ALL is non-nil offer *all* option. If ASK-ALL is non-nil,
+return a list of sessions, otherwise a single session."
+ (let* ((sessions (or sessions (sesman-sessions system)))
+ (name.syms (mapcar (lambda (s)
+ (let ((name (car s)))
+ (cons (if (symbolp name) (symbol-name name) name)
+ name)))
+ sessions))
+ (nr (length name.syms))
+ (syms (if (and (not ask-new) (= nr 0))
+ (error "No %s sessions found" system)
+ (append name.syms
+ (when ask-new '(("*new*")))
+ (when (and ask-all (> nr 1))
+ '(("*all*"))))))
+ (def (caar syms))
+ ;; (def (if (assoc (car sesman--select-session-history) syms)
+ ;; (car sesman--select-session-history)
+ ;; (caar syms)))
+ (sel (completing-read
+ prompt (mapcar #'car syms) nil t nil 'sesman--select-session-history def)))
+ (cond
+ ((string= sel "*new*")
+ (let ((ses (sesman-start-session system)))
+ (message "Started %s" (car ses))
+ (if ask-all (list ses) ses)))
+ ((string= sel "*all*")
+ sessions)
+ (t
+ (let* ((sym (cdr (assoc sel syms)))
+ (ses (assoc sym sessions)))
+ (if ask-all (list ses) ses))))))
+
+(defvar sesman--cxt-abbrevs '(buffer "buf" project "proj" directory "dir"))
+(defun sesman--format-context (cxt-type cxt-val extra-face)
+ (let* ((face (intern (format "sesman-%s-face" cxt-type)))
+ (short-type (propertize (or (plist-get sesman--cxt-abbrevs cxt-type)
+ (symbol-value cxt-type))
+ 'face (list (if (facep face)
+ face
+ 'font-lock-function-name-face)
+ extra-face))))
+ (concat short-type
+ (propertize (format "(%s)" cxt-val)
+ 'face extra-face))))
+
+(defun sesman-grouped-links (system session &optional current-first as-string)
+ "Retrieve all links for SYSTEM's SESSION from the global `sesman-links-alist'.
+Return an alist of the form
+
+ ((buffer buffers..)
+ (directory directories...)
+ (project projects...)).
+
+When `CURRENT-FIRST' is non-nil, a cons of two lists as above is returned with
+car containing links relevant in current context and cdr all other links. If
+AS-STRING is non-nil, return an equivalent string representation."
+ (let* ((system (or system (sesman--system)))
+ (session (or session (sesman-current-session system)))
+ (ses-name (car session))
+ (links (thread-last sesman-links-alist
+ (seq-filter (sesman--link-lookup-fn system ses-name))
+ (sesman--sort-links system)
+ (reverse)))
+ (out (mapcar (lambda (x) (list x))
+ (sesman-context-types system)))
+ (out-rel (when current-first
+ (copy-alist out))))
+ (mapc (lambda (link)
+ (let* ((type (sesman--lnk-context-type link))
+ (entry (if (and current-first
+ (sesman-relevant-link-p link))
+ (assoc type out-rel)
+ (assoc type out))))
+ (when entry
+ (setcdr entry (cons link (cdr entry))))))
+ links)
+ (let ((out (delq nil (mapcar (lambda (el) (and (cdr el) el)) out)))
+ (out-rel (delq nil (mapcar (lambda (el) (and (cdr el) el)) out-rel))))
+ (if as-string
+ (let ((fmt-fn (lambda (typed-links)
+ (let* ((type (car typed-links)))
+ (mapconcat (lambda (lnk)
+ (let ((val (sesman--abbrev-path-maybe
+ (sesman--lnk-value lnk))))
+ (sesman--format-context type val 'italic)))
+ (cdr typed-links)
+ ", ")))))
+ (if out-rel
+ (concat (mapconcat fmt-fn out-rel ", ")
+ (when out " | ")
+ (mapconcat fmt-fn out ", "))
+ (mapconcat fmt-fn out ", ")))
+ (if current-first
+ (cons out-rel out)
+ out)))))
+
+(defun sesman-link-session (system session &optional cxt-type cxt-val)
+ "Link SYSTEM's SESSION to context give by CXT-TYPE and CXT-VAL.
+If CXT-TYPE is nil, use the least specific type available in the current
+context. If CXT-TYPE is non-nil, and CXT-VAL is not given, retrieve it with
+`sesman-context'. See also `sesman-link-with-project',
+`sesman-link-with-directory' and `sesman-link-with-buffer'."
+ (let* ((ses-name (or (car-safe session)
+ (error "SESSION must be a headed list")))
+ (cxt-val (or cxt-val
+ (or (if cxt-type
+ (sesman-context cxt-type system)
+ (let ((cxt (sesman--least-specific-context system)))
+ (setq cxt-type (car cxt))
+ (cdr cxt)))
+ (error "No local context of type %s" cxt-type))))
+ (cxt-val (if (stringp cxt-val)
+ (expand-file-name cxt-val)
+ cxt-val))
+ (key (cons system ses-name))
+ (link (list key cxt-type cxt-val)))
+ (if (member cxt-type sesman-single-link-context-types)
+ (thread-last sesman-links-alist
+ (seq-remove (sesman--link-lookup-fn system nil cxt-type cxt-val))
+ (cons link)
+ (setq sesman-links-alist))
+ (unless (seq-filter (sesman--link-lookup-fn system ses-name cxt-type cxt-val)
+ sesman-links-alist)
+ (setq sesman-links-alist (cons link sesman-links-alist))))
+ (run-hooks 'sesman-post-command-hook)
+ link))
+
+(defun sesman-links (system &optional session-or-name cxt-types sort)
+ "Retrieve all links for SYSTEM, SESSION-OR-NAME and CXT-TYPES.
+SESSION-OR-NAME can be either a session or a name of the session. If SORT is
+non-nil links are sorted in relevance order and `sesman-current-links' lead the
+list, otherwise links are returned in the creation order."
+ (let* ((ses-name (if (listp session-or-name)
+ (car session-or-name)
+ session-or-name))
+ (lfn (sesman--link-lookup-fn system ses-name cxt-types)))
+ (if sort
+ (delete-dups (append
+ (sesman-current-links system ses-name)
+ (sesman--sort-links system (seq-filter lfn sesman-links-alist))))
+ (seq-filter lfn sesman-links-alist))))
+
+(defun sesman-current-links (system &optional session-or-name sort cxt-types)
+ "Retrieve all active links in current context for SYSTEM and SESSION-OR-NAME.
+SESSION-OR-NAME can be either a session or a name of the session. CXT-TYPES is a
+list of context types to consider. Returned links are a subset of
+`sesman-links-alist' sorted in order of relevance if SORT is non-nil."
+ ;; mapcan is a built-in in 26.1; don't want to require cl-lib for one function
+ (let ((ses-name (if (listp session-or-name)
+ (car session-or-name)
+ session-or-name)))
+ (seq-mapcat
+ (lambda (cxt-type)
+ (let* ((lfn (sesman--link-lookup-fn system ses-name cxt-type))
+ (links (seq-filter (lambda (l)
+ (and (funcall lfn l)
+ (sesman-relevant-context-p cxt-type (sesman--lnk-value l))))
+ sesman-links-alist)))
+ (if sort
+ (sesman--sort-links system links)
+ links)))
+ (or cxt-types (sesman-context-types system)))))
+
+(defun sesman-has-links-p (system &optional cxt-types)
+ "Return t if there is at least one linked session.
+CXT-TYPES defaults to `sesman-context-types' for current SYSTEM."
+ (let ((cxt-types (or cxt-types (sesman-context-types system)))
+ (found))
+ (condition-case nil
+ (mapc (lambda (l)
+ (when (eq system (sesman--lnk-system-name l))
+ (let ((cxt (sesman--lnk-context-type l)))
+ (when (and (member cxt cxt-types)
+ (sesman-relevant-context-p cxt (sesman--lnk-value l)))
+ (setq found t)
+ (throw 'found nil)))))
+ sesman-links-alist)
+ (error))
+ found))
+
+(defun sesman-register (system session)
+ "Register SESSION into `sesman-sessions-hashmap' and `sesman-links-alist'.
+SYSTEM defaults to current system. If a session with same name is already
+registered in `sesman-sessions-hashmap', change the name by appending \"#1\",
+\"#2\" ... to the name. This function should be called by system-specific
+connection initializers (\"run-xyz\", \"xyz-jack-in\" etc.)."
+ (let* ((system (or system (sesman--system)))
+ (ses-name (car session))
+ (ses-name0 (car session))
+ (i 1))
+ (while (sesman-session system ses-name)
+ (setq ses-name (format "%s#%d" ses-name0 i)
+ i (1+ i)))
+ (setq session (cons ses-name (cdr session)))
+ (puthash (cons system ses-name) session sesman-sessions-hashmap)
+ (sesman-link-session system session)
+ session))
+
+(defun sesman-unregister (system session)
+ "Unregister SESSION.
+SYSTEM defaults to current system. Remove session from
+`sesman-sessions-hashmap' and `sesman-links-alist'."
+ (let ((ses-key (cons system (car session))))
+ (remhash ses-key sesman-sessions-hashmap)
+ (sesman--clear-links)
+ session))
+
+(defun sesman-add-object (system session-name object &optional allow-new)
+ "Add (destructively) OBJECT to session SESSION-NAME of SYSTEM.
+If ALLOW-NEW is nil and session with SESSION-NAME does not exist
+throw an error, otherwise register a new session with
+session (list SESSION-NAME OBJECT)."
+ (let* ((system (or system (sesman--system)))
+ (session (sesman-session system session-name)))
+ (if session
+ (setcdr session (cons object (cdr session)))
+ (if allow-new
+ (sesman-register system (list session-name object))
+ (error "%s session '%s' does not exist"
+ (sesman--cap-system-name system) session-name)))))
+
+(defun sesman-remove-object (system session-name object &optional auto-unregister no-error)
+ "Remove (destructively) OBJECT from session SESSION-NAME of SYSTEM.
+If SESSION-NAME is nil, retrieve the session with
+`sesman-session-for-object'. If OBJECT is the last object in sesman
+session, `sesman-unregister' the session. If AUTO-UNREGISTER is non-nil
+unregister sessions of length 0 and remove all the links with the session.
+If NO-ERROR is non-nil, don't throw an error if OBJECT is not found in any
+session. This is useful if there are several \"concurrent\" parties which
+can remove the object."
+ (let* ((system (or system (sesman--system)))
+ (session (if session-name
+ (sesman-session system session-name)
+ (sesman-session-for-object system object no-error)))
+ (new-session (delete object session)))
+ (cond ((null new-session))
+ ((= (length new-session) 1)
+ (when auto-unregister
+ (sesman-unregister system session)))
+ (t
+ (puthash (cons system (car session)) new-session sesman-sessions-hashmap)))))
+
+(defun sesman-session-for-object (system object &optional no-error)
+ "Retrieve SYSTEM session which contains OBJECT.
+When NO-ERROR is non-nil, don't throw an error if OBJECT is not part of any
+session. In such case, return nil."
+ (let* ((system (or system (sesman--system)))
+ (sessions (sesman--all-system-sessions system)))
+ (or (seq-find (lambda (ses)
+ (seq-find (lambda (x) (equal object x)) (cdr ses)))
+ sessions)
+ (unless no-error
+ (error "%s is not part of any %s sessions"
+ object system)))))
+
+(defun sesman-session-name-for-object (system object &optional no-error)
+ "Retrieve the name of the SYSTEM's session containing OBJECT.
+When NO-ERROR is non-nil, don't throw an error if OBJCECT is not part of
+any session. In such case, return nil."
+ (car (sesman-session-for-object system object no-error)))
+
+(defun sesman-more-recent-p (bufs1 bufs2)
+ "Return t if BUFS1 is more recent than BUFS2.
+BUFS1 and BUFS2 are either buffers or lists of buffers. When lists of
+buffers, most recent buffers from each list are considered. To be used
+primarily in `sesman-more-relevant-p' methods when session objects are
+buffers."
+ (let ((bufs1 (if (bufferp bufs1) (list bufs1) bufs1))
+ (bufs2 (if (bufferp bufs2) (list bufs2) bufs2)))
+ (eq 1 (seq-some (lambda (b)
+ (if (member b bufs1)
+ 1
+ (when (member b bufs2)
+ -1)))
+ (buffer-list)))))
+
+;; path caching because file-truename is very slow
+(defvar sesman--path-cache (make-hash-table :test #'equal))
+(defun sesman-expand-path (path)
+ "Expand PATH with optionally follow symlinks.
+Whether symlinks are followed is controlled by `sesman-follow-symlinks' custom
+variable. Always return the expansion without the trailing directory slash."
+ (directory-file-name
+ (if sesman-follow-symlinks
+ (let ((true-name (or (gethash path sesman--path-cache)
+ (puthash path (file-truename path) sesman--path-cache))))
+ (if (or (eq sesman-follow-symlinks t)
+ vc-follow-symlinks)
+ true-name
+ ;; sesman-follow-symlinks is 'vc but vc-follow-symlinks is nil
+ (expand-file-name path)))
+ (expand-file-name path))))
+
+\f
+;;; Contexts
+
+(cl-defgeneric sesman-context (_cxt-type _system)
+ "Given SYSTEM and context type CXT-TYPE return the context.")
+(cl-defmethod sesman-context ((_cxt-type (eql buffer)) _system)
+ "Return current buffer."
+ (current-buffer))
+(cl-defmethod sesman-context ((_cxt-type (eql directory)) _system)
+ "Return current directory."
+ (sesman-expand-path default-directory))
+(cl-defmethod sesman-context ((_cxt-type (eql project)) system)
+ "Return current project."
+ (let* ((default-directory (sesman-expand-path default-directory))
+ (proj (or
+ (sesman-project (or system (sesman--system)))
+ ;; Normally we would use (project-roots (project-current)) but currently
+ ;; project-roots fails on nil and doesn't work on custom `('foo .
+ ;; "path/to/project"). So, use vc as a fallback and don't use project.el at
+ ;; all for now.
+ ;; NB: `vc-root-dir' doesn't work from symlinked files. Emacs Bug?
+ (vc-root-dir))))
+ (when proj
+ (expand-file-name proj))))
+
+(cl-defgeneric sesman-relevant-context-p (_cxt-type cxt)
+ "Non-nil if context CXT is relevant to current context of type CXT-TYPE.")
+(cl-defmethod sesman-relevant-context-p ((_cxt-type (eql buffer)) buf)
+ "Non-nil if BUF is `current-buffer'."
+ (eq (current-buffer) buf))
+(cl-defmethod sesman-relevant-context-p ((_cxt-type (eql directory)) dir)
+ "Non-nil if DIR is the parent or equals the `default-directory'."
+ (when (and dir default-directory)
+ (string-match-p (concat "^" (sesman-expand-path dir))
+ (sesman-expand-path default-directory))))
+(cl-defmethod sesman-relevant-context-p ((_cxt-type (eql project)) proj)
+ "Non-nil if PROJ is the parent or equal to the `default-directory'."
+ (when (and proj default-directory)
+ (string-match-p (concat "^" (sesman-expand-path proj))
+ (sesman-expand-path default-directory))))
+
+(defun sesman-relevant-link-p (link &optional cxt-types)
+ "Return non-nil if LINK is relevant to the current context.
+If CXT-TYPES is non-nil, only check relevance for those contexts."
+ (when (or (null cxt-types)
+ (member (sesman--lnk-context-type link) cxt-types))
+ (sesman-relevant-context-p
+ (sesman--lnk-context-type link)
+ (sesman--lnk-value link))))
+
+(defun sesman-relevant-session-p (system session &optional cxt-types)
+ "Return non-nil if SYSTEM's SESSION is relevant to the current context.
+If CXT-TYPES is non-nil, only check relevance for those contexts."
+ (seq-some #'sesman-relevant-link-p
+ (sesman-links system session cxt-types)))
+
+(define-obsolete-function-alias 'sesman-linked-sessions 'sesman--linked-sessions "v0.3.2")
+
+(provide 'sesman)
+
+;;; sesman.el ends here
--- /dev/null
+Sesman provides facilities for session management and interactive session
+association with the current contexts (project, directory, buffers etc). See
+project's readme for more details.
--- /dev/null
+;;; xml+-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+(add-to-list 'load-path (directory-file-name (or (file-name-directory #$) (car load-path))))
+\f
+;;;### (autoloads nil nil ("xml+.el") (23997 31593 860347 792000))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; End:
+;;; xml+-autoloads.el ends here
--- /dev/null
+;;; -*- no-byte-compile: t -*-
+(define-package "xml+" "20170727.2351" "Utilities for xml and html trees" '((emacs "24.4") (dash "2.12.0")) :commit "232fa863c08fc159b21dd58c39ea45dce3334895" :keywords '("xml" "html") :authors '(("Ben Dean" . "bendean837NOSPAMgmail.com")) :maintainer '("Ben Dean" . "bendean837NOSPAMgmail.com") :url "https://github.com/bddean/xml-plus")
--- /dev/null
+;;; xml+.el --- Utilities for xml and html trees
+
+;; Author: Ben Dean <bendean837NOSPAMgmail.com>
+;; Version: 0.0.0
+;; Package-Version: 20170727.2351
+;; Package-Requires: ((emacs "24.4") (dash "2.12.0"))
+;; Keywords: xml, html
+;; URL: https://github.com/bddean/xml-plus
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Commentary:
+
+;; Utility functions for xml parse trees.
+;; - `xml+-query-all' and `xml+-query-first' are query functions that search
+;; descendants in node lists. They don't work with namespace-aware parsing yet
+;;
+;; - `xml+-node-text' gets node text
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'dash)
+(require 'subr-x)
+(require 'xml)
+
+(defun xml+--node-matches (node matcher)
+ "Return non-nil if xml/html node NODE matches query selector
+element MATCHER, an attribute-value plist optionally beginning
+with a tag name or list of tag names, e.g. ((p div) :class
+\"content\")
+
+Begin MATCHER with ~ to negate the match
+
+If MATCHER is a list of lists, it matches node if node matches
+any of its members.
+"
+ (if (eq (type-of node) 'string)
+ nil ;; TODO text matching
+ (let ((tags nil)
+ (text nil)
+ (negative nil))
+ (setq matcher (-clone matcher))
+ (if (-every? 'listp matcher)
+ (-any? `(lambda (m) (xml+--node-matches (quote ,node) m)) matcher)
+ (when (eq (car matcher) '~)
+ (setq negative t)
+ (setq matcher (cdr matcher)))
+
+ (when (and matcher
+ (not (keywordp (cl-first matcher))))
+ (setq tags (-flatten (list (pop matcher)))))
+
+ (not (equal ;; xor
+ negative
+ (and
+ ;; Match tags
+ (or (not tags) (memq (xml-node-name node) tags))
+ ;; Match attributes
+ (let ((matches t))
+ (while (and matcher matches)
+ (when (not (keywordp (car matcher)))
+ (error (format "Expected keyword in matcher, got %s"
+ (symbol-name (type-of (car matcher))))))
+ (setq matches
+ (equal (cdr (assoc (intern (substring (symbol-name (car matcher)) 1))
+ (xml-node-attributes node)))
+ (cadr matcher)))
+ (setq matcher (cddr matcher)))
+ matches))))))))
+
+(defun xml+-query--generic (just-one nodes query &optional result must-be-root)
+ "Recursive depth first search to collect one or all nodes in
+NODES matching QUERY."
+ ;; Make cons cell to store a persistent reference to the result
+ (unless result (setq result (cons nil nil)))
+ (if (equal (car query) '>)
+ (xml+-query--generic just-one nodes (cdr query) result t)
+ (when (eq (type-of (car nodes)) 'symbol)
+ (setq nodes (list nodes)))
+
+ (let ((nodes-remaining (-filter (lambda (n) (and n (listp n))) nodes))
+ (node nil))
+
+ (while nodes-remaining
+ (setq node (pop nodes-remaining))
+ (if (xml+--node-matches node (car query))
+ (if (cdr query)
+ (xml+-query--generic just-one (xml-node-children node) (cdr query) result)
+ (setcdr result (append (cdr result) (list node)))
+ (when just-one (setq nodes-remaining nil)))
+ (unless must-be-root
+ (xml+-query--generic just-one (xml-node-children node) query result)))))
+ (reverse (cdr result))))
+
+(defun xml+-query-all (nodes query &optional must-be-root)
+ "Search NODES for matches.
+NODES is a single xml or html node or a list of them.
+
+QUERY is a list of node matchers optionally preceded by the
+symbol `>'. Node matchers are usually descendant matchers. When
+preceded by '> they are direct decendant matchers.
+
+For example, the analog to the Javascript call
+document.querySelectorAll(\"body div#post > p.content\") is:
+
+ (xml+-query-all document '((body)
+ (div :id \"post\") >
+ (p :class \"content\")
+ (a)))
+
+See also `xml-node-matches'"
+ (xml+-query--generic nil nodes query must-be-root))
+
+(defun xml+-query-first (nodes query &optional must-be-root)
+ "Like `xml+-query-all', but only return the first match."
+ (car (xml+-query--generic t nodes query must-be-root)))
+
+(defun xml+-node-text (node)
+ "Get the text of NODE"
+ (string-trim
+ (replace-regexp-in-string "[\s\t\n]+" " "
+ (mapconcat 'concat (xml+-node-text--helper node) " "))))
+
+(defun xml+-node-text--helper (node)
+ "Get the text of NODE"
+ (if (stringp node)
+ (list node)
+ (-reduce-from
+ (lambda (n1 n2) (append n1 (xml+-node-text--helper n2)))
+ nil
+ (xml-node-children node))))
+
+(provide 'xml+)
+;;; xml+.el ends here
--- /dev/null
+Utility functions for xml parse trees.
+ - `xml+-query-all' and `xml+-query-first' are query functions that search
+ descendants in node lists. They don't work with namespace-aware parsing yet
+
+ - `xml+-node-text' gets node text