[Перевод] Самый странный лексический синтаксис, который я обнаружила, исследовав 42 языка программирования

9a4a9fe24af2d3d83efdb8e5cb41dddc.png

Программирование — это не только алгоритмы и логика, но и удивительное разнообразие синтаксиса языков. Работая над новым средством подсветки синтаксиса для llamafile, разработчик Justine Tunney* исследовала 42 языка программирования — от классического C и экзотического Tcl до мощного Ruby. 

Justine делится своими открытиями о том, насколько причудливым и непредсказуемым может быть лексический синтаксис. Например, триграфы в C — устаревший инструмент для поддержки клавиатур с ограниченными символами, фиксированные длины строк в FORTRAN, вложенные комментарии в Haskell или строки с двойными квадратными скобками в Lua. Ruby вообще оказался чуть ли не самым сложным языком для подсветки из-за его контекстно-зависимого синтаксиса.

Под катом вы найдете описание разработки инструмента подсветки и исследование того, как языки программирования решают одни и те же задачи по-разному. Если вам интересны синтаксис, языковые особенности и сложности лексического анализа — эта статья для вас.

*Обращаем ваше внимание, что позиция автора может не всегда совпадать с мнением МойОфис

В этом месяце я исследовала 42 языка программирования, чтобы создать новое средство подсветки синтаксиса для llamafile.  И сегодня я поделюсь некоторыми примерами самых удивительных, а иногда и жутковатых синтаксисов, которые я когда-либо видела.

Я выбрала следующие языки: Ada, Assembly, BASIC, C, C#, C++, COBOL, CSS, D, FORTH, FORTRAN, Go, Haskell, HTML, Java, JavaScript, Julia, JSON, Kotlin, ld, LISP, Lua, m4, Make, Markdown, MATLAB, Pascal, Perl, PHP, Python, R, Ruby, Rust, Scala, Shell, SQL, Swift, Tcl, TeX, TXT, TypeScript и Zig. Этот список покрывает почти все языки в индексе TIOBE, кроме Scratch, который нельзя подсветить, поскольку в нем вместо текста используются блоки.

Как написать код для подсветки синтаксиса

Реализовать подсветку синтаксиса на самом деле несложно. Вы, вероятно, сможете написать такой код очень быстро — например, за время собеседования при приеме на работу. Мои любимые инструменты для этого C++ и GNU gperf. Самая сложная проблема здесь — избежать необходимости выполнять кучу сравнений строк, чтобы определить, является ли что-то ключевым словом или нет. Большинство разработчиков просто используют хэш-таблицу, но gperf позволяет создать её идеальный вариант. Например:

4322b2627c98ef00fb2dee62a559c4b2.png

gperf был первоначально разработан для GCC, и это отличный способ выжать из него максимум производительности. Если вы выполните команду gperf для приведенного выше кода, она сгенерирует этот.c-файл. Вы заметите, что его хэш-функция должна учитывать только один символ, чтобы получить поиск без коллизий. Именно это делает его совершенным в плане производительности. Не уверена, что кому-то может понадобиться подсвечивать синтаксис языка C со скоростью 35 МБ в секунду, но я теперь могу это делать, даже несмотря на то, что определила около 4000 ключевых слов для этого языка. Благодаря gperf эти слова не замедляют работу.

Остальное сводится к конечным автоматам. Для создания базового инструмента подсветки синтаксиса вам не нужны ни flex, ни bison, ни ragel. Вам просто нужен цикл for и оператор switch. По крайней мере, в моем случае, когда я обращала внимание только на строки, комментарии и ключевые слова. Если бы я хотела подсвечивать такие вещи, как имена функций языка C, то, вероятно, мне пришлось бы делать настоящий синтаксический разбор. Но если сосредоточиться на самом главном, то мы выполняем только лексический анализ. В качестве примера смотрите файл highlight_ada.cpp.

Демо

Все исследования, о которых вы прочтете на этой странице, были направлены на одно — создание нового инструмента подсветки синтаксиса llamafile. Это, пожалуй, самое сильное преимущество llamafile перед ollama на сегодняшний день, поскольку ollama вообще не делает подсветку синтаксиса. Вот демонстрация ее работы на Windows 10 с моделью Meta LLaMA 3.2 3B Instruct. Обратите внимание, что эти llamafiles будут работать и на MacOS, Linux, FreeBSD и NetBSD.

Новый интерфейс с подсветкой и чат-ботом сделал llamafile настолько приятным в использовании (при том, что модели с открытым весом, такие как gemma 27b, сейчас весьма неплохие), что мне теперь все реже хочется обращаться к Claude.

Примеры удивительного лексического синтаксиса

Итак, давайте поговорим о тех видах лексического синтаксиса, которые удивили меня при написании моего средства подсветки.

C

Несмотря на то, что язык С претендует на простоту, он содержит несколько самых странных лексических элементов среди всех языков. Для начала, у нас есть триграфы, которые, вероятно, были придуманы для того, чтобы помочь европейцам использовать язык C при работе с клавиатурой, на которой не было символов #,[, \,^,{,|,} и ~. Вы можете заменить эти символы на ?=,?(,?/,?),?',? <,??!,??> и ?-. Согласитесь, очень интуитивные обозначения. Это означает, что, например, такой код на языке C — вполне корректен.

6b187cd771d6c3d0b7265cc8b9c9bce9.png

Так было, по крайней мере, до того, как в стандарте С23 были удалены триграфы. Однако компиляторы всегда будут поддерживать этот синтаксис для устаревшего ПО, поэтому хороший инструмент подсветки синтаксиса тоже должен его поддерживать. Но если триграфы официально мертвы, это не значит, что комитеты по стандартизации не придумали взамен другой странный синтаксис.

Рассмотрим универсальные символы:

e6c58c52d2992470edb3b082577723e5.png

Эта возможность пригодится тем, кто хочет, например, использовать имена переменных с арабскими символами, сохраняя при этом чистоту исходного кода в ASCII. Не совсем понимаю, зачем это может кому-либо понадобится. Я надеялась, что смогу воспользоваться этим и написать:

8c38d647cc4907deb6eb1fd51ecfb6c8.png

Но увы, если универсальные символы не используются в определенных плоскостях UNICODE, одобренных комитетом по стандартам, GCC выдает ошибку.

Следующий пример — один из моих любимых. Знаете ли вы, что однострочный комментарий в C может занимать несколько строк, если в конце строки использовать обратный слэш?

5db4a7e7b5fcb21383b1ec0176c0b4c8.png

Большинство других языков этой возможности не поддерживают. Даже языки, которые в своем исходном коде допускают экранирование обратным слэшем (например, Perl, Ruby и Shell), не поддерживают этой особенности C. Насколько мне известно, ее поддерживают Tcl и GNU Make. Инструменты для подсветки синтаксиса, такие как Emacs и Pygments, зачастую делают это неправильно. Хотя Vim, похоже, всегда обрабатывает обратный слэш правильно.

Вот еще один хороший пример ошибки Emacs: директива препроцессора null. Одна из первых вещей, которую можно заметить при чтении исходного кода v6, — это то, что большинство файлов .c начинаются так:

41a1b0f0e0d17fffd30b99f6d2238c3c.png

Предположительно, это было сделано для того, чтобы обойти некоторые странности, но этот код остается актуальным и по сей день. Его даже можно использовать для каких-нибудь полезных целей, например, чтобы скрывать комментарии от cc -C -E:

6da768b609a8678ffc2939c881796cd8.png

Haskell

Каждый программист на C знает, что в многострочный комментарий нельзя вставлять многострочный комментарий. Например:

92ecc39fdc9239c9141564591514a993.png

Однако это возможно в Haskell. Они наконец-то исправили ошибку. Хотя и приняли для этого другой синтаксис.

2a81f41dc182f884d8b0168c63467df5.png

D

Можно подумать, что в языке D впервые исправлена ошибка рекурсивных комментариев языка C. Но вместо этого D принял обе формы синтаксиса комментариев C как есть:

737880216b9ab6c7835b18bb4e5f50e4.png

При этом вводится третий тип синтаксиса — для рекурсии комментариев.

6617d84732d636395e58d15aea8a93b0.png

На мой взгляд, из всех языков D лучше всего документировал свой лексический синтаксис. Он формализован, и в нем изложены все подробности, которые мне нужно было знать, например, шестнадцатеричные строки и heredoc.

9affc45596835bc05b130961e76b5f9d.png

Tcl

Больше всего в Tcl меня удивило то, что идентификаторы могут содержать в себе кавычки. Например, эта программа выведет a"b:

b8e6b0f96cef659b0b3437276a7bc2c3.png

Вы даже можете вставлять кавычки внутрь имен переменных, однако ссылаться на них можно будет только в том случае, если вы используете нотацию ${a"b}, а не $a"b.

6c806a37b9b2ff21ad7bc88e46be5908.png

JavaScript

В JavaScript есть встроенный лексический синтаксис для регулярных выражений. Однако при невнимательности легко ошибиться. Рассмотрим следующий пример:

7959592efe541d55081b00656bbebb5f.png

Когда я впервые писала свой лексический анализатор, я просто находила закрывающий слэш и полагала, что все слэши внутри регулярного выражения будут экранированы. Но когда я попыталась подсветить минифицированный код, оказалось, что это не так. Если слэш стоит внутри квадратных кавычек для набора символов, то его не нужно экранировать.

Теперь перейдем к еще более странным вещам.

Есть несколько невидимых символов UNICODE, которые называются РАЗДЕЛИТЕЛЬ СТРОК (u2028) и РАЗДЕЛИТЕЛЬ ПАРАГРАФОВ (u2029). Я не знаю, где эти кодовые точки могут пригодиться, но стандарт ECMAScript определяет их как символы конца строки, что фактически приравнивает их к \n. Поскольку это символы Trojan Source, я настроила свой Emacs на отображение их как  и . Однако большинство ПО не учитывает эти символы и часто отображает их как вопросительные знаки. Также, насколько я знаю, ни один язык, кроме D, не делает этого. Я смогла воспользоваться этим для SectorLISP, поскольку это позволило мне создать полиглоты C + JavaScript.

подсветка синтаксиса javascript//¶`

6673447888d7680a6cf0fcb36afdfa3c.png

Вот так я вставляю код на C в файлы JavaScript.

подсветка синтаксиса С//¶`

А вот так я вставляю JavaScript в исходный код на C. Примером части рабочего кода, в котором я это сделала, является файл lisp.js, на который я ссылаюсь в своем посте в блоге SectorLISP. Он запускается в браузере, и его можно также скомпилировать с помощью GCC и запустить локально. llamafile умеет правильно подсвечивать синтаксис для такого кода, но мне еще предстоит найти другое средство подсветки синтаксиса, которое тоже так делает. Вряд ли это имеет значение, поскольку я сомневаюсь, что какая-либо LLM когда-нибудь сгенерирует такой код. Но размышлять об этих заковыристых случаях, конечно, интересно.

Shell

Мы все знакомы с синтаксисом heredoc в скриптах Shell, например

ad83767999d245013f146b42876183f1.png

Этот синтаксис позволяет поместить $foo в строку heredoc, хотя существует синтаксис с кавычками, который отключает подстановку переменных.

589b7785ffb22f3a3226cad77a39018c.png

Если вы хотите запутать своих коллег, то отличный способ злоупотребить этим синтаксисом — это заменить маркер heredoc на пустую строку. В таком случае heredoc будет заканчиваться на следующей пустой строке. Например, эта программа выведет «hello» и «world» в двух строках:

1708f5ace9e383990fead05d53f5f2a7.png

В языках, поддерживающих heredoc (Shell, Ruby и Perl), также возможно вставлять несколько heredoc в одну строку.

1745eaa0d794b6b6a1a77823295565bb.png

Еще один момент, на который следует обратить внимание при работе с языком shell, — он похож на Tcl в том смысле, что специальные символы вроде #, с которых, как вы можете подумать, всегда начинается комментарий, на самом деле могут быть корректным кодом в зависимости от контекста. Например, внутри ссылки на переменную # может использоваться для удаления префикса. Следующая программа выведет слово «there».

d13cb260b3b7dbfcd9900565262fdbf4.png

Интерполяция строк

Знаете ли вы, что с точки зрения подсветки синтаксиса строка языка Kotlin может начинаться с ", а заканчиваться символом {? Так работает синтаксис интерполяции строк этого языка. Многие языки позволяют вставлять ссылки на имена переменных в строки, но TypeScript, Swift, Kotlin и Scala доводят интерполяцию строк до крайности, поощряя встраивание реального кода внутрь строк.

4bdc5675fa28980961971aeb7a49d420.png

Поэтому, для подсветки строк в Kotlin, Scala и TypeScript, нужно учитывать фигурные скобки и поддерживать стек состояний парсера. В TypeScript это относительно просто, и требуется лишь добавить пару состояний в ваш конечный автомат. Однако в Kotlin и Scala все гораздо сложнее, поскольку они поддерживают синтаксис как двойных, так и тройных кавычек, и в любой из них могут быть интерполированные значения. В итоге получилось около 13 независимых состояний, необходимых конечному автомату только для лексического анализа строк. Swift также поддерживает тройные кавычки для интерполированного синтаксиса "\(var)", однако для его поддержки потребовалось всего 10 состояний.

Swift

У Swift свой уникальный подход к проблеме встраивания строк внутрь строки. Он позволяет окружать строки «двойная кавычка»,»«тройная кавычка»« и /регулярное-выражение/ произвольным количеством знаков #hash#, которые должны быть зеркально отображены с каждой стороны. Это позволяет писать вот такой код:

26fec7944793eb5af4522a397356d6b4.png

C#

C# поддерживает синтаксис многострочных строк Python с тройными кавычками, но с одной изюминкой, присущей только этому языку. C# решает проблему «встраивания строки в строку», позволяя вам делать строки с четверными или даже с пятерными кавычками, если хотите. Сколько бы кавычек вы ни поставили с левой стороны, именно столько будет использоваться для завершения строки на другом конце.

351c4c8dca95beac4a049fde3433bde2.png

По моему мнению, так и должно быть, потому что для конечного автомата так проще декодировать. Для классических строк Python с тройными кавычками нужны дополнительные правила, чтобы убедиться, что это либо один символ двойной кавычки, либо ровно три. Если допустить произвольное число кавычек, то потребуется меньше правил для проверки. В итоге вы получаете более мощный и выразительный язык, который проще реализовать. Именно такое мы привыкли ожидать от Microsoft.

Что еще они придумают?

c0d3b1226272f0fa28af1767e3e3c809.jpg

FORTH

Обычно, чем код проще для компьютера, тем сложнее понять его человеку, и FORTH — яркое тому подтверждение. FORTH, вероятно, самый простой язык из всех существующих, потому что он выделяет в качестве лексем все, что ограничено символами пробела. Даже синтаксис начала строки — это лексема. Например:

b00944f2a2a9c00e9e1a93325e380d5f.png

означает то же самое, что сказать «hello world» на любом другом языке.

FORTRAN и COBOL

Один из вариантов использования llamafile, который я себе представляю, заключается в том, что он поможет банковской системе не рухнуть, когда все программисты на FORTRAN и COBOL уйдут на пенсию. Допустим, вас только что наняли для поддержки секретного мейнфрейма, полного конфиденциальной информации, написанной на языке COmmon Business-Oriented Language. Благодаря llamafile вы можете попросить управляемую вами в автономном режиме систему ИИ, например, Gemma 27b, написать за вас код на COBOL и FORTRAN. Она не может печатать перфокарты, но может выделять синтаксис перфокарт. Вот как выглядит код на FORTRAN с правильно подсвеченным синтаксисом:

184e857f6f450c1872f44b75799f888d.png

В FORTRAN существуют следующие правила фиксированных столбцов:

  • Если в столбце 1 поставить *, c или C, то строка станет комментарием

  • Помещение символа, отличного от пробела, в столбец 6 позволяет расширить строку за пределы 80 символов

  • Метки создаются путем размещения цифр в столбцах 1–5.

Теперь приведем несколько кодов COBOL с правильно подсвеченным синтаксисом.

332c0cef000ac64a4fa7e1f068cabb37.png

В COBOL действуют следующие правила:

  • Поместив * в столбец 7, вы превращаете строку в комментарий

  • Знак — в столбце 7 позволяет расширить строку за пределы 80 символов

  • Номера строк указаны в столбцах 1–6.

Zig

Zig имеет уникальное решение для многострочных строк, которые снабжаются двумя обратными слэшами.

0425c4ac983d957770bb12193e426240.png

Что мне нравится в этом синтаксисе — он избавляет нас от необходимости вызывать textwrap.dedent(), как это приходилось делать при работе со строками Python в тройных кавычках. Недостаток заключается в том, что точка с запятой выглядит некрасиво. Это синтаксис строк, который на самом деле следует рассмотреть в одном из языков, не требующих точки с запятой, например, в Go, Scala, Python и т.д.

Lua

В Lua уникальный синтаксис многострочных строк, и он использует подход, похожий на C# и Swift, когда дело доходит до решения проблемы «встраивания строки внутрь строки». Для этого используются двойные квадратные скобки, между которыми можно поместить произвольное количество знаков равенства.

afc07012aec5864053afcda3ee7cc95e.png

Что действительно интересно — он позволяет делать это и с комментариями.

ecef90110beb4fcbd405da95161317dd.png

Ассемблер

Одним из самых сложных языков для подсветки синтаксиса — это ассемблер, из-за фрагментарности всех его различных диалектов. Я пыталась создать нечто в llamafile, что неплохо справлялось бы с синтаксисом AT&T, nasm и т.д. Вот синтаксис nasm:

292dcd1c20945806f57c784cc81d01ba.png

А вот синтаксис AT&T:

62220c6eae2e59be3c7bab7262c33a09.png

И вот синтаксис GNU:

c60c736ee10e57e66c4a5f4e25cb8a91.png

При работе с ключевыми словами я обнаружила, что проще всего рассматривать первый идентификатор в строке (за которым не следует двоеточие), как ключевое слово. Благодаря этому большинство кодов ассемблера, которые я пробовала, выглядят вполне понятно.

Синтаксис комментариев очень сложный. Мне нравятся оригинальные комментарии UNIX, в которых требовался только один слэш. Ассемблер GNU поддерживает их и по сей день, но только если они находятся в начале строки (в ассемблере UNIX их изначально можно было поместить куда угодно, поскольку в то время у ассемблера не было возможности выполнять арифметические действия). Clang вообще не поддерживает фиксированные комментарии, так что их использование в открытом исходном коде, к сожалению, уже нецелесообразно.

Но дальше все становится еще интереснее. Дополнительная странность оригинального ассемблера UNIX заключается в том, что он не использовал для символьных литералов закрывающую кавычку. Поэтому там, где мы указываем 'x', чтобы получить код 0×78 для символа x, в оригинальном исходном коде UNIX вы указываете 'x. Это еще одна особенность, которую продолжает поддерживать ассемблер GNU, но, к сожалению, не LLVM. В любом случае, поскольку есть большой объем кода, использующего этот синтаксис, любой хороший инструмент для подсветки синтаксиса должен его поддерживать.

Ассемблер GNU позволяет заключать идентификаторы в кавычки, поэтому вы можете поместить в идентификатор практически любой символ.

Наконец, при подсвечивании кода ассемблера недостаточно просто подсветить код ассемблера. Ассемблер обычно используется в сочетании с препроцессором C или m4. Поверьте, это встречается часто во множестве программ с открытым исходным кодом. Поэтому строки, начинающиеся с dnl, m4_dnl или C, также должны считаться комментариями.

Ada

Ada — удивительно простой язык для лексического анализа. Но есть одна вещь, которую я еще не до конца поняла — это использование одинарных кавычек. В Ada могут быть символьные литералы, как в C, например, 'x'. Но одинарная кавычка может использоваться и для ссылки на атрибуты, например, Foo'Size. Одинарная кавычка позволяет даже вставлять выражения и вызывать функции. Например, следующий код:

7d0eb4de3808d9438007cf516623957c.png

выведет:

bd3a6e18694a9716b606ad8042dd6628.png

потому что мы объявляем символ, присваиваем ему значение, а затем передаем его через функцию Image, которая преобразует его в представление String.

BASIC

Давайте поговорим о BASIC (Универсальном коде символических инструкций для начинающих/ Beginner’s All-purpose Symbolic Instruction Code). Копаясь в репозиториях, которые я клонировала в git, я наткнулась на эту старую программу на Commodore BASIC, которая разрушила многие мои представления о подсветке синтаксиса.

506c489af627857baa4cd97d71bb9a1e.png

Заметим, что эта конкретная реализация BASIC не требует закрывающей кавычки в строках, имена переменных снабжены этими странными сигилами, а ключевые слова типа goto легко выделяются из идентификаторов.

В Visual BASIC также есть странный синтаксис литерала даты:

870443c69bce95658fec6f7ba1e9e10e.png

Это сложно разобрать лексически, потому что в VB есть даже директивы препроцессора.

53a5fcafb71b80ad875660bf68b8aca3.png

Perl

Одним из самых сложных языков для подсветки является Perl. Он существует как бы между оболочками и языками программирования и наследует сложность обоих. Сегодня Perl не так популярен, как раньше, но его влияние все еще велико. В Perl регулярные выражения (regex) сделались объектом первого класса, и то, как работают регулярные выражения в Perl, с тех пор переняли многие другие языки программирования, такие как Python. Однако сам лексический синтаксис регулярных выражений по-прежнему остается достаточно уникальным.

Например, в Perl вы можете заменить текст, подобно редактору потоков sed, следующим образом:

584909b58325f444a9f30acf8eedad52.png

Как и sed, Perl позволяет заменять слеши произвольными символами пунктуации, что упрощает вставку слэшей в регулярном выражении.

135245fdd71763d8ae1a0c9a3cbb935e.png

Возможно, вы не знали, что это можно сделать и с зеркальными символами; в этом случае вам нужно вставить дополнительный символ:

aa51412515826ee7de9c36a6d32cd3be.png

Однако s/// — не единственная странная вещь, которую нужно выделять как строку. В Perl есть множество других магических префиксов.

5adaa163f0868adb7d367ac8328ca20f.png

Сложность выделения таких элементов состоит в том, что вам нужно учитывать контекст, чтобы случайно не перепутать, что y/x/y/ — это формула деления. К счастью, в Perl это сделать довольно просто, поскольку переменные всегда могут быть снабжены сигилами, которые обычно равны $ для скаляров, @ для массивов и % для хэшей.

133793dfbd816313dcf622c941a384e4.png

Это помогает нам избежать необходимости парсинга грамматики языка.

В Perl также есть странное соглашение о написании man-страниц внутри исходного кода. В принципе, любое =слово в начале строки отметит начало страницы, а =cut — ее конец.

8a9dc5ca4072972026b2e3339184dde6.png

Ruby

Из всех языков я оставила на закуску самый интересный — Ruby. Перед вами язык, синтаксис которого ускользает от всех попыток его понять. Ruby — это объединение всех более ранних языков, и он даже формально не документирован. В руководстве по этому языку есть раздел о синтаксисе Ruby, но он весьма краток. Когда бы я ни пыталась протестировать подсветку синтаксиса, объединяя все файлы .rb на жестком диске, всегда найдется тот или иной файл, который найдет способ сломать ее.

5ea2c331183d3a13ca5ead0af7aad6a8.png

Поскольку Ruby поддерживает синтаксис обратных кавычек, например, var =`echo hello`, я не совсем понимаю, как определить, что приведенная выше обратная кавычка не должна подсвечиваться как строка. А вот еще один пример:

2c17464e3bf503c6b927f9b9495823e4.png

В Ruby есть оператор <<, а также поддержка heredoc (как в Perl и Shell). Поэтому я не совсем понимаю, как определить, что приведенный выше код не является heredoc. И да, этот код действительно существует. Даже Emacs неправильно его обрабатывает. Из всех 42 языков, которые я исследовала, этот, пожалуй, шокировал меня сильнее всего. Возможно, это тот случай, когда нельзя произвести лексический анализ Ruby без синтаксического разбора (парсинга). Не уверена, что даже с разбором в этом можно разобраться.

Но подождите, дальше будет еще интереснее. На самом деле это правильный код Ruby:

Вот так.

467f31f0946fcb518eb6911f9c2e081e.png

Сложность поддерживаемых языков

Если бы я оценивала сложность языков программирования по тому, сколько строк кода нужно для подсветки синтаксиса, то FORTH был бы самым простым языком, а Ruby — самым сложным.

125 highlight_forth.cpp

132 highlight_m4.cpp

149 highlight_ada.cpp

160 highlight_lisp.cpp

163 highlight_test.cpp

166 highlight_matlab.cpp

186 highlight_cobol.cpp

199 highlight_basic.cpp

200 highlight_fortran.cpp

211 highlight_sql.cpp

216 highlight_tcl.cpp

218 highlight_tex.cpp

219 highlight.cpp

220 highlight_go.cpp

225 highlight_css.cpp

225 highlight_pascal.cpp

230 highlight_zig.cpp

235 highlight_make.cpp

239 highlight_ld.cpp

263 highlight_r.cpp

266 highlight_lua.cpp

282 highlight_csharp.cpp

282 highlight_rust.cpp

297 highlight_python.cpp

300 highlight_java.cpp

321 highlight_haskell.cpp

335 highlight_markdown.cpp

337 highlight_js.cpp

340 highlight_html.cpp

371 highlight_typescript.cpp

387 highlight_kotlin.cpp

387 highlight_scala.cpp

447 highlight_asm.cpp

449 highlight_c.cpp

455 highlight_swift.cpp

521 highlight_d.cpp

570 highlight_shell.cpp

583 highlight_perl.cpp

1042 highlight_ruby.cpp

© Habrahabr.ru