Initial development history of Vimclair BASIC

Description of the page content



Started with the code of BBim2BB.

New: do...loop, do...loop until and do...loop while implemented.


New: First draft of do until...loop, do while...loop and nested loops.


Fix: Some local variables have been changed to script variables.


Fix: All nine combinations of do...loop work fine.

New: Nested loops finished.

New: exit do implemented.

New: exit for implemented.

New: else...endif implemented.

New: The TAP file is created (with the BAS2TAP converter).

New: Procedures (without parameters): defproc, endproc, exit proc, proc.


New: Syntax description of labels.


New: First changes to implement procedure parameters.

Improvement: The directory of the source file is the working directory. This lets #include paths relative to it. XXX not tested yet.


Improvement: Long if...else...endif structures; the previous else...endif method is removed.

New: #procedurecall lets to configure the command used to call the a procedure (the default is call), or make it empty.

Change: Version A-01: first usable version, with loops and long conditional structures.

New: Version A-02: long conditionals can be nested.

Fix: #procedureCall was not parsed.

Fix: Now s:procedureCall works also when empty.


New: Version A-03: the program saves copies of the file after every conversion step, for debugging.

Change: The project and all its files are renamed and reorganized.

Fix: Some procedures were not converted, because the search position was not restored in the loop.

Fix: Removed a feature of BBim: Remove line numbers from import-time commands.

Fix: --body-numbering=a was needed by the nl tool. Otherwise empty lines that holded a lonely label were not numbered, and the label values didn't match anymore.


This code:

      let i$=inkey$
    loop until i$<>""
    loop until inkey$=""

Was converted to:

28 let i$=inkey$
29 if not (i$<>"") then go to 29

30 if not (inkey$="") then go to 32

The reason was the order of some operations. It's fixed but the code still needs some revision.


Improvement: ".vbas" is removed from the BAS filename; and ".vbas.bas" is removed from the TAP filename.

Fix: The jump before an #!else!# was not created because of a wrong expression in the append().

Fix: The last empty line of the BAS file is removed, so BAS2TAP does not shows a warning anymore.


Change: #renumLine instead of #firstLine.

New: #runLine, #filename.


Change: Now the code is cleaned before executing the Vim commands. Otherwise certain substitutions don't work, for example because the splitted lines have not been joined yet.

Improvement: Simpler code in functions that retrieve the config values from the source.


Improvement: A starting not function is removed from expressions that have to be negated. Formerly, a not was added in all cases. Also useless surrounding parens are removed.


Fix: Sometimes the file included with #included got merged into a different place. The code was:

  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.
    execute "silent! r ".getcwd().## / ## .l:filename

After many tries, this new method proved to work as expected:

  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(## . ## ,## // <<< start of included file  ## .l:filename) 
    call append(## . ## ,## // >>> end of included file  ## .l:filename) 
    let l:filecontent=readfile(getcwd().## / ## .l:filename)
    call append(## . ## ,l:filecontent)

Version A-04.


Description improved.


#define implemented, just to define tags without value, in order to use them as switches for conditional conversion.

Start of the conditional conversion (#if[un]def[ined], #else, #endif). Not finished yet.


Fix: the IF without ENDIF error didn't show the line number.

New: VimclairDefined(), needed by VimclairConditionalConversion().

Improvement: #runLabel instead of #runLine.

Version A-05.


Conditional conversion caused strange problems, but the reason was #ifdef or #ifnded being used in splitted lines. Example:

    if not anExit and complement=theOutside then \
      // No explicit way out, so use the previous location instead
      let anExit=previousLocation:\
      #ifdef debug
        debug["Using previous location instead: "+str$ previousLocation]

The problem is the let line is splitted, because also the debug[] command belongs to the same if structure. But when the debug tag is not defined, the debug[] line is removed and the let line is joined to whatever line comes next, out of the if structure!

So the problem is not Vimclair BASIC. In this particular case, the solution is to change the order of the text lines in the code:

    if not anExit and complement=theOutside then \
      // No explicit way out, so use the previous location instead
      #ifdef debug
        debug["Using previous location instead: "+str$ previousLocation]:\
      let anExit=previousLocation


Fix: In VimclairIfEndif, this could cause some problems, because of the optional spaces:

    let l:condition=substitute(getline('.'), '^if\s*\(.\{-}\)\s*then$', '\1', '')

Now spaces are mandatory:

    let l:condition=substitute(getline('.'), '^if\s\+\(.\{-}\)\s\+then$', '\1', '')


Change: def proc and end proc are allowed only with one space (formerly any number of spaces were valid). The alternative defproc and endproc are recognized as usual.

The rest of double keywords are changed accordingly. Now all of them are recognized with one single space or no space.

Change: The metacomments' syntax is changed: now they must have at least one following space. This makes some things easier and prevents possible name clashes with future directives.

Improvement: echoerr is used for all error messages.

Fix: s:runLabel was not properly reseted at the start of VimclairRunLabel().

Fix: match() regexp in VimclairNot() was delimited by double quotes.

Improvement: The target .bas file is closed before it is created, just in case it was open to examine it. Formerly an error happened and the convertion didn't work fine. try and endtry are used:

  let s:basFilename=expand('%:r').'.bas'
  silent execute 'bd '.s:basFilename
  catch /E94:/
  silent execute 'write! '.s:basFilename


Changes in the main function, VimclairBASIC(): VimclairInclude() is called only once, and the configuration is done next. This makes it possible to use #define directives in included files. Maybe there are some disadvantages too, but achiving recursive including with recursive conditional conversion seems too complex and unnecessary.


Change: The nmap ,vb and its help text are moved from the converter to vim/ftplugin/vimclairbasic.vim.


Fix: the default filename used in the ZX Spectrum header of TAP was the sourcename without filename extension; now the path is removed. beside, the final string (default or configured by its label) is truncated to a maximum length of 10 characters. Otherwise bas2tap could give an error.

New:, the command version of the converter.

Change: The converter is moved from vimclair_basic.vim to vim/plugins/vbas2tap.vim.


Version A-06. Now the command line wrapper can be used in any directory.


Version A-07. Now the converter works also with zmakebas. The new directive #tapmaker lets to set the converter (bas2tap is the default).

New: Notation for 16-bit embedded values, in decimal or hex:

  let a$="{{1}}{{0xFFFF}}{{256}}"

In the example above, the string with contain chars 1, 0, 255, 255, 0 and 1.


Version A-08. #runLabel' is removed: '#run' is used instead, and it accepts labels or line numbers. No need to create a label at the top only for the autorun.


Version A-09:

Fixed a bug in the VimclairVim() function, noticed during its conversion to fsb.

Before the fix:

  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 setline('.','')
    call cursor(l:vimCommandLine,1) " Return to the command line.

After the fix:

  while search('^\s*#vim\s\+','Wc')

    let l:vimCommands += 1
    let l:vimCommandLine = line('.')
    let l:vimDirective=matchstr(getline(l:vimCommandLine),'^\s*#vim\s\+')
    let l:vimCommand=strpart(getline(l:vimCommandLine),strlen(l:vimDirective)+1)

    " Blank the line that contained the command before executing
    " it, because a command could modify itself in such a way
    " that its line would be splitted, what would be a problem
    " later:
    call setline('.','')

    execute 'silent! '.l:vimCommand
    call cursor(l:vimCommandLine,1) " Return to the command line.

The old code worked fine only when #vim was at the start of the line. Beside, there was a chance a command could modify its own line in unpredictable ways.

Change: BAS2TAP is no longer the default converter: If the #tapmaker is not used or its content is invalid, a warning will be shown and no TAP file will be created.


Change: All the #vim directives are extracted from the source before being executed (on their original line). This prevents a directive from modifing the code of other directive.

New: #previm directive. It works the same way than #vim, but it's executed first. This makes it possible to use Vim command to modify other #vim directives. For example, a Vim function can be used to enumerate the values of constants converted by #vim directives.

function! VimclairDoVim(directive)

  " Search for '#vim' or '#previm' directives, depending on the argument,
  " and execute their Vim commands.
  " Syntax:
  " #previm Any-Vim-Ex-Command
  " #vim Any-Vim-Ex-Command

  call cursor(1,1) " Go to the top of the file.

  " Empty dictionary to store the Vim commands; their line
  " number, padded with zeroes, will be used as key:
  let l:command={}

  " Search for all directives and store their line numbers and
  " Vim commands

  let l:directiveExpr='^\s*'.a:directive.'\s\+'
  while search(l:directiveExpr,'Wc')
    let l:key=matchstr('00000000'.string(line('.')),'.\{8}$')
    let l:line=getline(line('.'))
    let l:command[l:key]=strpart(l:line,matchend(l:line,l:directiveExpr))
    call setline('.','') " blank the line

  if len(l:command)

    " Execute all Vim commands

    for l:key in sort(keys(l:command))
      call cursor(str2nr(l:key),1)
      " XXX TODO make 'silent' configurable
      " XXX with 'silent', wrong regexp in substitutions are hard to notice!
      execute 'silent! '.l:command[l:key]

    if len(l:command)==1
      echo "One '".a:directive."' directive executed'"
      echo len(l:command)." '".a:directive."' vim directives executed"


  call VimclairSaveStep(strpart(a:directive,1).'_directives')


function! VimclairVim()

  " Search for all '#previm' and '#vim' directives and execute
  " their Vim commands.

  call VimclairDoVim('#previm')
  call VimclairDoVim('#vim')

  " Remove the empty lines (this is not done also after
  " executing the '#previm' directives , in order to help
  " comparing the position of the directives in both steps file,
  " if needed for debugging; besides, it would be of no use):

  silent! %s/^\n//e


Version A-10.


Some comments are fixed and updated.


A local Git repository has been created from the development backups, in order to resume the development and publish the project in GitHub.


The project is uploaded to GitHub.

This page will not be updated anymore.