" bbim2bb.vim " BBim2BB " Version A-00-20140726 " Copyright (C) 2011,2012,2014 Marcos Cruz (programandala.net) " License: " http://programandala.net/license " This file is part of BBim: " http://programandala.net/es.programa.bbim " This program, written in Vim, converts BBim source code " into an actual Beta BASIC program " ready to be imported into a ZX Spectrum emulator " by BBimport. " Note: " This file **must** use the Latin1 (ISO 8859-1) encoding. " ------------------------------ " History " 2011-08-30: First draft version, based on vim2mb.vim, by the same " author. " 2011-08-31: Some improvements, based on sb2sbsasic,vim, by the same " author. " 2011-09-01: Renamed to bbim2bb.vim (formerly bb2bbmgt.vim); " rearranged, based on mbim2mb.vim. " 2011-09-10: Labels implemented. " 2011-09-29: Labels checked and finished; renumbering added; " BBimTokens(). " 2011-09-30: "silent" command used everywhere; custom messages added. " 2012-01-25: BBimLabels() fixed: lonely labels had to be joined with " the next line; optional final ":" removed with labels. These fixes " were first coded for SBim's SBimLabels(). " 2012-01-27: Fixed the greedy subtitution of block comments. " 2012-01-28: New feature: import-time commands (marked with a " semicolon at the start of their lines); their line number is removed. " 2012-01-28: New feature: #firstline command to define the first line " that will be used to renumber the final program in Beta BASIC. " 2012-01-28: Improvement: labels are not case sensitive any more. " 2012-01-28: Improvement: The .BB and .BB.MGT buffers are actually " closed with "bw" ("wq" kept them on the list). " 2012-02-16: Fixed?: "c" parameter removed from the search() in " BBimLabels(). It caused problems in SBim, the parallel project. " 2012-03-05: New experimental metacommands (not used yet): #define and " #substitution. " 2012-03-05: New metacommands: #vim and #include. " 2012-03-06: Changed all "normal gg" to "call cursor(1,1)". " 2012-03-06: BBimInclude() fixed: the filename is get from the current " line with functions instead of normal-mode register commands, because " normal-mode cursor movement commands depend on the iskeyword " variable, what depends on the current syntax: the movement is " different in the .bbim an the .bbim.bb files. " 2012-03-08: Clearer substitutions with nr2char() instead inline bytes. " Added the conversion of embedded bytes (in BASin format: \#nnn). " 2012-11-17: Usage message at the start. Fixed: "nul" to "/dev/null" " 2012-12-06: Fixed: 'normal w"lyw' had to be 'normal ww"lyw'. Detected in " MBim2BB. Improved: '#vim' and '#include' are counted and the count is " included in the final messages. " 2014-06-06: Fix: 'l:includedFiles' was not initialized or updated. " 2014-07-26: " Improvevement: Possible trailing or leading spaces are removed from " every regexp in BBimLabels(), because the source code has been already " cleaned. " Fix: The regexp used to substitute label references with their values lacked " a word ending mark; this caused, e.g., the search for '@theEnd' matched " '@theEnd01'! Beside, also in BBimLabels(), 'set ignorecase' was needed by " search(). This bugs were found during the development of SinBasic " (http://programandala.net/en.program.sinbasic.html). Fix: " ------------------------------ " To-do " Make it possible to write " several programs in the same BBim source file: " they would be renumbered apart but exported together " into one single MGT image, and then imported " and divided with one single operation. " ---------------------------------------------- function! BBimClean() " Clean off all BBim stuff. silent! %s/^\s*#.*$//e " Remove the metacomments silent! %s/\s*\/\/.*$//e " Remove the // line comments silent %s,^\s*\/\*\_.\{-}\*\/,,e " Remove the /* */ block comments silent! %s/^\s*\d\+\s*$//e " Remove lines with the line number only " 2012-01-29 xxx old: "silent! %s/^\s\+//e " Remove empty lines "silent! %s/\n\n\+/\r/eg " Remove empty lines " 2012-01-29 xxx new, untested: silent! %s/^\n//e " Remove empty lines silent %s,^\s*\n,,ge " Remove the empty lines silent! %s/^\s*//eg " Remove main indentation silent! %s/\s\+$//eg " Remove ending blanks silent! %s/\\\s*\n//e " Join the splitted lines silent! %s/^\(\d\+\)\s\+/\1/e " Remove the space after the line number " Experimental, not implemented yet: "silent! %s/^\s*endprog\s\+\([a-zA-Z_-]\+\)/save d*"\1"line 10:delete 10 to/e echo 'Source code cleaned.' endfunction " ---------------------------------------------- " Metacommands function! BBimDefine() " xxx experimental Not used yet. BBimVim() can do this and more. " Execute all #define metacommands. " Syntax: " #define regexp new_content " Empty dictionary to store the substitutions: let l:substitution={} call cursor(1,1) " Go to the top of the file. " Main loop: while search('^\s*#define\s\+\S\+\>','Wc') " Store the parameters into register 'p': normal w"py$ " Copy to a variable: let l:parameters=getreg('p',1) let l:new=matchstr(l:parameters,'\S\+$') " xxx debug check "echo 'parameters='.l:parameters let l:old=matchstr(l:parameters,'^\S\+') let l:new=matchstr(l:parameters,'\S\+$') " xxx debug check "echo 'old='.l:old let l:new=matchstr(l:parameters,'\S\+$') " xxx debug check "echo 'new='.l:new " Store them into the dictionary: let l:substitution[l:old]=l:new " Remove the whole line: normal dd endwhile " Do all substitutions: for l:old in keys(l:substitution) " xxx debug check "echo 'Searching...'.l:old call cursor(1,1) " Go to the top of the file. " Do the subtitution: while search(l:old,'Wc') " xxx debug check 'echo l:old 'label reference found' execute 'silent! substitute/'.l:old.'/'.l:substitution[l:old].'/ei' endwhile endfor endfunction function! BBimSubstitute() " xxx experimental Not used yet. BBimVim() can do this and more. " Execute all #substitute metacommands. " The #substitute's syntax is identical to Vim's substitute command, " but no range or flags can be indicated. " No syntax check is done. " If the #substitute command is not recognized, " it will be part of the Beta BASIC code and will fail at importing-time. " If the #substitute command's syntax is not fine, " it will fail at translation-time. " Examples: " #substitute/first_element/second_element/ " #substitute,first_element,second_element, " Empty list to store the substitutions: let l:substitution=[] call cursor(1,1) " Go to the top of the file. " Get all substitutions: while search('^\s*#substitute[[:punct:]]','Wc') " Store the parameters into register 's': normal w"sy$ " xxx debug check: "echo getreg('s',1) " Store them into the list: call add(l:substitution,getreg('s',1)) " Remove the whole line: normal dd endwhile " xxx debug check: "echo l:substitution " Do all substitutions: for l:item in l:substitution execute 'silent! %substitute'.l:item.'eg' endfor endfunction function! BBimVim() " Execute all #vim metacommands. " Syntax: " #vim Any-Vim-Ex-Command call cursor(1,1) " Go to the top of the file. let l:vimCommands=0 " Counter while search('^\s*#vim\s','Wc') let l:vimCommands += 1 let l:vimCommandLine = line('.') let l:vimCommand=matchstr(getline(l:vimCommandLine),'\S\+.*',4) execute 'silent! '.l:vimCommand call cursor(l:vimCommandLine,1) " Return to the command line. call setline('.','') " Blank the line. endwhile if l:vimCommands==0 echo 'No Vim command found.' elseif l:vimCommands==1 echo 'One Vim command executed.' else echo l:vimCommands 'Vim commands executed.' endif endfunction function! BBimInclude() " Execute all #include commands in the source. " Syntax: " #include file-name " Warning: nested including is possible, but no recursion check is made! call cursor(1,1) " Go to the top of the file. let l:includedFiles=0 " Counter while search('^\s*#include\s','Wc') let l:includedFiles += 1 let l:fileName=matchstr(getline('.'),'\S\+.*',8) call setline('.','') " Blank the line. " ----------- xxx debug check "echo '#include ' l:fileName "echo 'getcwd()=' getcwd() "echo 'Modifications:' "echo ':~' fnamemodify(l:fileName,':~') "echo ':p' fnamemodify(l:fileName,':p') " ----------- execute "silent! r ".l:fileName endwhile if l:includedFiles==0 echo 'No file included.' elseif l:includedFiles==1 echo 'One file included.' else echo l:includedFiles 'files included.' endif endfunction " ---------------------------------------------- " Labels function! BBimGetFirstLine() " Store into s:firstLine the first line number " to be used by the final Beta BASIC program " (old versions of BBimport occupied lines 1-9). " The command #firstline can be used to set " the desired line number. Only the first occurence " of #firstline will be used; it can be anywhere " in the source but always at the start of a line " (with optional indentation). let s:firstLine=1 " default value call cursor(1,1) " Go to the top of the file. if search('^\s*#firstline\s\+[0-9]\+\>','Wc') " Store the number into register 'l': normal ww"lyw " And then into the variable: let s:firstLine=getreg('l',1) endif echo 'First line number: '.s:firstLine endfunction function! BBimLabels() let l:ignoreCaseBackup=&ignorecase set noignorecase " Join lonely labels to the next line: silent %substitute,^\(\(label\s\+\)\?@[0-9a-zA-Z_]\+\)\n,\1:,ei " Empty dictionary to store the line numbers of the labels; the labels will be used as keys: let l:lineNumber={} call cursor(1,1) " Go to the top of the file. " Store every label in the l:lineNumber dictionary: while search('^\(label\s\+\)\?@[0-9a-zA-Z_]\+\>','W') " Store the label into register 'l': normal "l2yw " xxx debug check "echo 'Raw label found: <' . getreg('l',1) . '>' " If 'label' is present, go to the next word and repeat: if tolower(getreg('l',1))=='label @' normal w"l2yw " xxx debug check "echo 'Actual raw label found: <' . getreg('l',1) . '>' endif " Remove possible ending spaces: let l:label=tolower(substitute(getreg('l',1),' ','','g')) " xxx debug check "echo 'Clean label: <' . l:label . '>' " Use the label as the key to store the line number: let l:lineNumber[l:label]=line('.')+s:firstLine-1 " Go to the next word: normal w endwhile " xxx debug check "echo l:lineNumber " Remove all labels: silent! %substitute/^\(label\s\+\)\?@[0-9a-zA-Z_]\+\s*:\?//ei " Substitute every label reference with its line number: for l:label in keys(l:lineNumber) call cursor(1,1) " Go to the top of the file. " Do the subtitution: while search(l:label.'\>','Wc') " xxx debug check "echo l:label "label reference found" execute 'silent! substitute/'.l:label.'\>/'.l:lineNumber[l:label].'/ei' endwhile endfor if l:ignoreCaseBackup set ignorecase else set noignorecase endif echo 'Labels translated.' endfunction " ---------------------------------------------- " Renum function! BBimRenum() " Call the the nl program (part of the Debian coreutils package): execute "silent! %!nl --body-numbering=t --number-format=rn --number-width=5 --number-separator=' ' --starting-line-number=".s:firstLine." --line-increment=1" " In older versions of coreutils, " -v sets the first line number, and -i sets the line increment. " (the long option for -v doesn't work, though the manual mentions it). " Modern versions of nl uses the clearer options " --first-line and --line-increment, see: " http://www.gnu.org/software/coreutils/manual/coreutils.html#nl-invocation " Remove spaces before line numbers " (nl has no option to remove them): silent! %substitute/^\s*//e " Remove line numbers from import-time commands silent! %substitute/^[0-9]\{1,4}\s://e echo 'Line numbers added.' endfunction " ---------------------------------------------- " Character translation function! BBimTokens() " Translate special tokens into its ZX Spectrum code. silent! %s/<=/\=nr2char(199)/eg silent! %s/>=/\=nr2char(200)/eg silent! %s/<>/\=nr2char(201)/eg endfunction function! BBimISOchars() " Translate ISO 8859-1 chars into ZX Spectrum chars. " (Unfinished). " The occupied chars must be redefined " by the final Beta BASIC program, " so this function must be customized ad hoc. " silent! %s/¡/\=nr2char(000)/ge " silent! %s/¿/\=nr2char(000)/ge " silent! %s/Á/\=nr2char(000)/ge " silent! %s/É/\=nr2char(000)/ge " silent! %s/Í/\=nr2char(000)/ge " silent! %s/Ñ/\=nr2char(000)/ge " silent! %s/Ó/\=nr2char(000)/ge " silent! %s/Ú/\=nr2char(000)/ge " silent! %s/Ü/\=nr2char(000)/ge " silent! %s/á/\=nr2char(000)/ge " silent! %s/é/\=nr2char(000)/ge " silent! %s/í/\=nr2char(000)/ge " silent! %s/ñ/\=nr2char(000)/ge " silent! %s/ó/\=nr2char(000)/ge " silent! %s/ú/\=nr2char(000)/ge " silent! %s/ü/\=nr2char(000)/ge endfunction function! BBimBlockGraphs() " Translate BASin format ZX Spectrum block graphics notation " (chars 128-143) silent! %s/\\ /\=nr2char(128)/ge silent! %s/\\ '/\=nr2char(129)/ge silent! %s/\\' /\=nr2char(130)/ge silent! %s/\\''/\=nr2char(131)/ge silent! %s/\\ \./\=nr2char(132)/ge silent! %s/\\ :/\=nr2char(133)/ge silent! %s/\\'\./\=nr2char(134)/ge silent! %s/\\':/\=nr2char(135)/ge silent! %s/\\\. /\=nr2char(136)/ge silent! %s/\\\.'/\=nr2char(137)/ge silent! %s/\\: /\=nr2char(138)/ge silent! %s/\\:'/\=nr2char(139)/ge silent! %s/\\\.\./\=nr2char(140)/ge silent! %s/\\\.:/\=nr2char(141)/ge silent! %s/\\:\./\=nr2char(142)/ge silent! %s/\\::/\=nr2char(143)/ge endfunction function! BBimUDG() " Translate BASin format ZX Spectrum UDG notation " (chars 144-164) silent! %s/\\[Aa]/\=nr2char(144)/ge silent! %s/\\[Bb]/\=nr2char(145)/ge silent! %s/\\[Cc]/\=nr2char(146)/ge silent! %s/\\[Dd]/\=nr2char(147)/ge silent! %s/\\[Ee]/\=nr2char(148)/ge silent! %s/\\[Ff]/\=nr2char(149)/ge silent! %s/\\[Gg]/\=nr2char(150)/ge silent! %s/\\[Hh]/\=nr2char(151)/ge silent! %s/\\[Ii]/\=nr2char(152)/ge silent! %s/\\[Jj]/\=nr2char(153)/ge silent! %s/\\[Kk]/\=nr2char(154)/ge silent! %s/\\[Ll]/\=nr2char(155)/ge silent! %s/\\[Mm]/\=nr2char(156)/ge silent! %s/\\[Nn]/\=nr2char(157)/ge silent! %s/\\[Oo]/\=nr2char(158)/ge silent! %s/\\[Pp]/\=nr2char(159)/ge silent! %s/\\[Qq]/\=nr2char(160)/ge silent! %s/\\[Rr]/\=nr2char(161)/ge silent! %s/\\[Ss]/\=nr2char(162)/ge silent! %s/\\[Tt]/\=nr2char(163)/ge silent! %s/\\[Uu]/\=nr2char(164)/ge endfunction function! BBimChars() let l:ignoreCaseBackup=&ignorecase set noignorecase call BBimISOchars() call BBimTokens() call BBimUDG() call BBimBlockGraphs() " Embedded ASCII codes (BASin format): silent! %s/\\#\(\d\+\)/\=nr2char(submatch(1))/g echo 'Special chars translated.' if l:ignoreCaseBackup set ignorecase else set noignorecase endif endfunction " ---------------------------------------------- " Fake MGT disk image function! BBimMGTfile() " Create a fake MGT disk image from the current BB file silent write " Write the current BB file split " Split the window silent write! %.mgt " Save a copy with the MGT extension added silent edit ++bin %.mgt " Open it in binary mode set noendofline " Don't put an EOL at the end of the file when saving it set fileencoding=latin1 " Translate line feed chars (decimal 10) " into carriage returns chars (decimal 13): silent %!tr "\12" "\15" " Convert it into a fake MGT disk image, " just making it 819200 bytes long: silent %!dd bs=819200 conv=sync 2>> /dev/null echo 'Fake MGT file created.' endfunction " ---------------------------------------------- " BB file function! BBimBBfile() " Create a copy of the current BBim file " with the ".bb" extension added " and open it for editing. silent update " Write the current BBim file if needed split " Split the window silent write! %.bb " Save a copy with the BB extension added silent edit %.bb " Open it for editing set fileencoding=latin1 " xxx note: fileformat does not work here (it makes the renumbering not to work, because all the code is one line): "set fileformat=mac " Force CR (char 13) as end of line " Force an end of line at the end of the file: silent! %substitute/\%$/\r/e silent write echo 'BB file created.' endfunction " ---------------------------------------------- " Main function! BBim2BB() set shortmess=at call BBimGetFirstLine() let s:ignoreCaseBackup=&ignorecase set ignorecase call BBimBBfile() call BBimInclude() call BBimVim() call BBimClean() call BBimLabels() call BBimRenum() call BBimChars() call BBimMGTfile() silent w silent bw echo 'Fake MGT file saved and closed.' set fileformat=mac " Force CR (char 13) as end of line silent w silent bw echo 'BB file saved and closed.' if s:ignoreCaseBackup set ignorecase else set noignorecase endif echo 'Done!' endfunction " Shortkey ',bb' in normal mode " to create a Beta BASIC file: nmap ,bb :call BBim2BB() echo "BBim2BB loaded." echo "Activate it with the keys ',bb' (comma, B and B), in normal mode, on your BBim source."