Pull to refresh

Редкий язык — кодинг без IDE, но с удобством

Reading time 10 min
Views 31K
Можно спорить до посинения на тему IDE vs Notepad/VIM/Emacs. Ровно до тех пор, пока IDE есть. Однако, запросто можно наткнуться на язык (или диалект), который использовать придется, а IDE под него либо не существует, либо только платная и дорогая, либо уже настолько устарела, что запускается только в эмуляторе раритетной ОС. А работать надо. К счастью, существуют универсальные инструменты, которые нужно только выбрать и «заточить» по руке. Далее описывается вариант использования связки VIM, транслятора и немного ctags под редкий сегодня вариант ассемблера.

Есть только транслятор

Вскоре после начала своей работы в коллективе разработчиков я столкнулся с ассемблером для 1806ВМ2, грубо говоря, PDP-11. Причем, целевое устройство — некая плата с горстью деталек для преобразования и обработки сигналов, а инструментальная машина — ПК с Windows (без вариантов). Разрабатываемые устройства разные, и программы к ним — от трех сотен до трех тысяч строк кода.

Когда я влился в этот процесс, оказалось, что проекту соответствует папка с названием устройства. В папке лежит ассемблерный файл (все asm-файлы имеют одинаковое имя, так настроена компиляция), bat-файл без параметров, который вызывает другой bat-файл с параметрами, а тот, в свою очередь, запускает exe-файл ассемблера (лежащий, естественно, тут же). Кроме того, в папке проекта можно найти бинарник и листинг, а также программку преобразования бинарника в файл прошивки ПЗУ и вспомогательный текстовый файл для преобразования, еще одну программку преобразования файла прошивки в формат, понятный нашему программатору, файл прошивки (устаревший с тех пор, как программу стали писать во flash вместо ПЗУ), плюс dirinfo.txt, в который крайне редко кто-нибудь что-нибудь писал.

Работа происходила так: в консольном файловом менеджере текст программы правился встроенным редактором. После правки запускался первый батничек (важно не перепутать!). Далее смотрим консольный вывод на наличие ошибок. Если синтаксических косяков нет — программа заливается в эмулятор ПЗУ, проверяем работоспособность на «железе». Так программа дописывается и проверяется, пока не начнет выполнять то, что от нее ожидается. Тогда сформированная прошивка записывается в ПЗУ (позднее ПЗУ заменили на flash).

Первое время я работал так, как принято. Было не до удобства — надо разобраться в системе команд, в особенностях построения программы, в аппаратной части устройств, для которых пишется софт. Когда что-то стало получаться, начали возникать мысли о благоустройстве. В первую очередь хотелось подсветку синтаксиса, запуск компилятора из редактора и нормальный поиск с заменой.

Муки выбора

Сначала я попробовал в разного рода интернетах найти IDE, ведь это же PDP-11, то есть, ДВК, БК и УКНЦ, должно же что-то найтись! И нашлось, но работало исключительно в эмуляторе УКНЦ. Мне не понравились ни консольная IDE, ни эмулятор, ни необходимость запуска эмулятора для среды, но это было не самое главное. Хуже всего то, что результирующий бинарник отличался от бинарника, созданного «традиционным способом». Разбираться в причинах не стал — не хватало еще допиливать раритеты в эмуляторах, или, того хуже, писать корректирующий софт. Решил организовывать свое рабочее пространство, с преферансом и куртизанками.

Стал искать текстовый редактор посерьезнее. Из того, что мне попадалось в процессе поиска, больше всего понравился UltraEdit (особенно в варианте UEStudio). В нем было все, что мне хотелось, плюс еще плюшечки — сравнение текстовых файлов, перекодировка, колоночное выделение, запуск не только компилятора, но и других конструируемых макросов с использованием имени текущего файла, расширение панели инструментов. Все хорошо — но платно, а по моим доходам тогда — и недешево. Попробовал MiBEditor — продукт впечатляющий (для бесплатного и сделанного в одну каску), но не то. Помогло легкое увлечение Linux. Я узнал о VIM, попробовал им пользоваться, ощутил его возможности и обнаружил версию для Wndows.

Предварительная настройка

Первое, что сделал — настроил внешний вид и поведение в vimrc: темная цветовая схема wombat (светло-серым по черному, подправленная по своим предпочтениям), нумерация строк, увеличенная до 85 ширина по-умолчанию из-за номеров строк, умные отступы и табы, отключенные bak-файлы, автосмена рабочей папки по открытому файлу. Включил виртуальный звонок, который моргает, а не бибикает, отключил панель инструментов (зачем-то пока оставил меню, хотя не пользуюсь), скрыл мышь при печати, повесил переключение между открытыми split буферами по Ctrl-Tab.

Выбрал шрифт, имеющий русские символы:
set guifont=Courier_New:h10:cRUSSIAN    " выбор шрифта

Добавил кое-что для раскладки, чтобы было удобно работать по-русски:
set keymap=russian-jcukenwin    " русская раскладка
set iskeyword=@,48-57,_,192-255	" добавка рус. симв. к ключевым словам
set iminsert=0                  " но по-умолчанию - английская
set imsearch=0                  " и при поиске — английская
lang ctype Russian_Russia.1251

Повесил переключатель раскладки на F12:
imap <F12> <C-^>
cmap <F12> <C-^>

Теперь при английской раскладке прекрасно работают все клавиатурные команды, а переключать язык ввода надо не системным Ctrl-Shift, а VIM'овским F12, текущий язык отображается в нижней строчке: «-- ВСТАВКА --» для английского, либо «-- ВСТАВКА (язык) --» для русского. Небольшое неудобство — обычно раскладка клавиатуры у меня русская, а тут надо не забывать при запуске VIM переключать на английскую.

Все перечисленное записал в отдельный vimrc_rg, который подключил в файле настроек по умолчанию:
source $VIM/_vimrc_rg

Еще поставил несколько плагинов, но постоянно пользуюсь буквально одним — QuickBuf (qbuf.vim, script_id=1910), который позволяет переключаться между открытыми буферами в одном окне по F4. Теперь мне не нужны вкладки (к которым успел привыкнуть в UltraEdit), и даже кажутся неудобными. Иногда вспоминаю про установленный Visual mark (script_id=1026), который делает выделенные закладки, как в UltraEdit.

Так началось использование VIM в качестве основного редактора plain-text-файлов. Одно из моих требований — нормальный поиск и замена — идет «из коробки», только научись пользоваться. Плюс подсветка многих языков и автоотступы — стало очень удобно править командные файлы, например.

Настройка подсветки и транслятора

Дальше ввел новый тип файла — a1806, для подсветки и встраивания компиляции. Создал в корне папки vimfiles файл filetype.vim:
	augroup filetypedetect
	au BufNewFile,BufRead *.msa	setf a1806
	au BufNewFile,BufRead *.a51	setf asm8051
	augroup END

Далее некоторое время стал давать соответствующие расширения ассемблерным файлам — msa для 1806, а51 для ассемблера MCS-51, чтоб не путать с asm, по умолчанию заданным для интеловского ассемблера.

Потом в vimfiles\syntax сделал файл a1806.vim, переделанный из файла подсветки какого-то другого языка. Ключевые слова нашел в интернетах, текстовой информации по PDP-11 навалом. Есть и пара бумажных книг по УКНЦ и СМ ЭВМ, но с веб-страничек проще копировать.
В этот файл воткнул кроме прочего такую строчку:
syn match a1806Error        "#@.*\|[a-zA-Z0-9_$]\{7,}:"

При быстром наборе текста и вводе косвенной адресации я часто путал @# и #@, а этот шаблон выделяет ошибку еще при наборе. Так можно определить и другие часто встречающиеся опечатки.

Подсветка синтаксиса есть. Теперь компиляция. Поразбирался с утилитой make и ctags, стало понятно, что здесь они не помощники. Есть ассемблер, но нет линкера — проект на файлы-модули не разбить. Цель для make будет всего одна, файл для нее — тоже, нечего и заморачиваться. То же и с ctags — файл один, искать по нему особо нечего, метки и так легко находятся «звездочкой». В длинных файлах полезнее закладки, которые в VIM есть и так. Но ассемблер в каждой папке проекта — это фи! Делаю папку bin в корне диска C:\, прописываю ее в PATH (потом положу туда же make и ctags для другого ассемблера). Туда кладу ассемблер и mk1806.bat, где помимо микрохелпа и проверки валидности переданных параметров стирается бинарник и листинг, а потом вызывается ассемблер:
:VALIDDIR
CD %2 %3 %4 %5 %6 %7 %8 %9
:WORK2
if not exist *.bin goto NOBIN
attrib -r *.bin
del *.bin
:NOBIN
if not exist *.lst goto NOLST
attrib -r *.lst
del *.lst
:NOLST
if exist %1 C:\bin\cmplr1806\asm.exe %1 -m
if not exist %1 echo Error: File %1 not exist!

Батник принимает два параметра — имя файла и путь к нему. Чтобы не гадить выводу make, режим отображения команд в файле отключен (@ ECHO OFF).

В vimfiles\ftplugin сделал файл a1806.vim:
" Language:	1806 assembler 
" Maintainer:
" Last Change:
" включает для данного буфера текущую директорию по директории открытого файла
lcd %:h
" устанавливает программу компиляции
setlocal makeprg=C:\bin\mk1806.bat\ %:t\ %:p:h
" устанавливает опцию автозаписи перед компиляцией
setlocal autowrite
nmap <buffer> <F9> :make<CR>
imap <buffer> <F9> <Esc>:make<CR>
" устанавливает формат строки ошибок
set efm=%-G,%-G\ BK-10/11%.%#,%-P\ Opened\ %f,%Eline\ %l\\,%.%#,%Z%m,%-GFile\ compiled%.%#

Errorformat был определен опытным путем (документации-то на asm.exe нет никакой вообще), то есть, запускал ассемблирование файла без ошибок и с разными специально сделанными ошибками, изучал консольный вывод, составлял шаблон по справке VIM.
Пример вывода с ошибкой (номера строк добавлены вручную для наглядности):
1 
2   BK-10/11 Assembler 
3 
4   Opened 1.ASM
5  Line 61,Address 000320:
6  Bad operator [AKSJDFH].
7   Opened 1.lst
8 
9  File compiled 346 bytes.
10

Тогда формат ошибок будет состоять из таких элементов:
  • %-G, — игнорировать пустую строку, если встретится. («G» — информация общего характера, «%-» — исключить из вывода, а отсутствие чего-либо после «G» говорит о том, что имеется в виду пустая строка). Будет применено к 1, 3, 8 и 10 строкам.
  • %-G\ BK-10/11%.%#, — здесь '%.%#' используется для создания шаблона регулярного выражения ' .*', то есть, строка, начинающаяся с « BK-10/11» (пробел для команды «:set» экранируется «\ »), игнорируется. Будет применено к строке 2.
  • %-P\ Opened\ %f, — разбирается однострочное сообщение (%-P) с именем файла (%f) после слова « Opened», «%-P» говорит, что строку не выводим, но имя файла запоминаем, нижеследующие ошибки относятся к нему. Будет применено к строкам 4 и 7.
  • %Eline\ %l\\,%.%#, — начало многострочного сообщения об ошибке (%E), где после слова «line » идет номер строки (%l), потом запятая («\\,» — запятую надо экранировать обратным слэшем, но его надо продублировать для команды «:set»), потом какая-то строка («%.%#» = «.*»), которая участвует в выводе (в нашем случае — адрес, хотя выведется в виде номера ошибки — «320 error», это не баг, а фича).
  • %Z%m, — конец многострочного сообщения (%Z) с собственно сообщением об ошибке (%m). Шаблоны %E и %Z работают вместе для пар строк типа 5 и 6.
  • %-GFile\ compiled%.%# опять же информация общего характера, выкинутая из вывода, правило для строки 9.

В соответствии с шаблонами efm строки 1..4 и 7..10 не выводятся, хотя в 4 и 7 запоминается имя файла. Если строк 5 и 6 нет — вывод пустой, ошибок нет. Строк типа 5 и 6 может быть несколько пар. Каждая из этих пар сформирует элемент списка сообщений об ошибке.

Voilà! Имеем компиляцию с автосохранением по F9. При обнаружении синтаксической ошибки VIM сразу ставит курсор на строку кода, соответствующую первой ошибке из списка найденных.

Не хватает отладчика — но здесь он не поможет, так как слишком много вещей завязано на прерывания, прямой доступ к памяти и постороннее «железо». Не осилил пока в полной мере сравнение файлов, встроенное в VIM, пользуюсь в основном WinMerge. И все равно стало гораздо удобнее.

Расширение возможностей

А потом 1806 у нас стал понемногу отходить, и началась работа с устройствами на базе процессора NM6403. Тоже на ассемблере. И, чтобы не извращаться с придумыванием очередного расширения для файлов исходника, я добавил в vimfiles\ftplugin файл asm.vim:
	if getline(1) =~ '^;.*a1806\>'
	  set filetype=a1806
	endif

	if getline(1) =~ '^//.*a6403\>'
	  set filetype=a6403
	endif

В ассемблерные файлы с расширением .asm начал вписывать первую строчку
«;a1806» или «//a6403» соответственно. Добавил еще подсветку синтаксиса vimfiles\syntax\a1806.vim (написанную очень просто и быстро по «Описанию языка ассемблера») и такой вот vimfiles\ftplugin\a6403.vim:
" Language:	6403 assembler 
" Maintainer:	
" Last Change:
setlocal makeprg=c:\bin\make.exe
" включает для данного буфера текущую директорию по директории открытого файла
lcd %:h
" устанавливает опцию автозаписи перед компиляцией
setlocal autowrite
nmap <buffer> <F9> :make<CR>
imap <buffer> <F9> <Esc>:make<CR>
nmap <buffer> <F10> :make TAGS<CR>
imap <buffer> <F10> <Esc>:make TAGS<CR>
set efm=\"%f\"\\,%l\ :\ %m

Чтобы это работало, написал Makefile, который лежит в каждой папке-проекте и соседствует с bat-файлом запуска компилятора, используемым остальными разработчиками. В моем Makefile есть цель TAGS, а также вызов этой цели из цели ALL.
TAGS :
	@c:/bin/ctags.exe --options=c:/bin/a6403.ctags $(SRC_ASM) *.asi

В $(SRC_ASM) перечислены ASM-исходники, а в *.asi лежат include-файлы.

В упомянутой папке C:\bin лежит make.exe, ctags.exe, а также a6403.ctags, передаваемый в качестве параметра для ctags.exe:
--langdef=a6403
--langmap=a6403:.asm
--language-force=a6405
--regex-a6403=/^<([a-zA-Z0-9_]+)>/\1/L,labels/
--regex-a6403=/^[ \t]*const[ \t]+([a-zA-Z0-9_]+)/\1/C,consts/
--regex-a6403=/^[ \t]*#define[ \t]+([a-zA-Z0-9_]+)/\1/D,defs/
--regex-a6403=/^[ \t]*global[ \t]+([a-zA-Z0-9_]+)[ \t]*:[ \t]*(word|long)/\1/V,vars/

ctags «из коробки» понятия не имеет о NM6403. Первая строчка определяет новое имя языка. Вторая строчка рассказывает о расширении файла. Расширения пишутся подряд, например, “.asm.asi.inc”. Точка без символа за ней предполагает использование файлов без расширения. Впрочем, не особо полезная опция — я не указал здесь расширение .asi, однако указал *.asi при вызове ctags — и утилита просканировала все, что надо. Четвертая строчка заставляет использовать указанные выше расширения именно для этого языка, а не для языка, встроенного в ctags для таких же расширений. Остальные строчки описывают метку вида «<Label>», константу вида «const Name», макроопределение вида «#define Expression» и глобальную переменную вида «global Name: word» соответственно — те сущности, к определениям которых мне хотелось бы иметь возможность «прыгнуть». Здесь же хочу напомнить про команду gf — go file, переход к файлу, имя которого под курсором — очень удобно для просмотра include-файлов.

Еще одна тонкость: на рабочем компьютере установлено несколько разных инструментов разработки, в том числе, IDE. Некоторые из них несут на борту собственные make, а пути к своим бинарникам при установке прописывают в PATH. Причем, некоторые make нервно относятся к Makefile, сделанным по правилам GNU make. И вообще могут даже быть командными файлами. Так что пришлось путь до используемого make (а заодно и ctags) везде писать полностью — это проще и надежнее, чем переупорядочивать PATH и контролировать его после каждой установки очередной IDE/CAD.

Итого

Теперь у меня есть возможность удобно работать с разными ассемблерами, не заморачиваясь с расширениями файлов, «прыгать» по коду, разбросанному по разным файлам, компилировать, не выходя из редактора, попадать на строку с синтаксической ошибкой после компиляции. Можно очень легко по аналогии расширить область действия связки VIM + транслятор + ctags другими языками, если придется их использовать. Можно добавить удобства в работу с tags, выбрав подходящий плагин. Можно прикрутить make с настройкой на нужный транслятор через Makefile, можно и сам транслятор напрямую. Ну, не IDE, конечно, но ведь и не блокнот же!

Немного грустно, что удобством и мощью VIM и ctags в коллективе пользуюсь я один. Никого не зацепило. Может, кому-нибудь эти наработки окажутся полезными?

P. S. Спасибо всем, кто осилил до конца.
Tags:
Hubs:
+71
Comments 16
Comments Comments 16

Articles