SBim
Descripción del contenido de la página
Preprocesador para S*BASIC
«SBim» significa «S*BASIC improved», esto es, «S*BASIC mejorado». «S*BASIC» quiere decir tanto SuperBASIC (el BASIC original de Sinclair QL) como su versión mejorada SBASIC.
SBim es por una parte un formato mejorado para escribir código en S*BASIC; por otra, es un preprocesader escrito en Vim que traduce el código fuente al formato propio de S*BASIC, listo para ser interpretado.
El formato SBim
Mejoras que aporta el formato SBim respecto al S*BASIC original:
- Comentarios de línea con comillas simples.
- Líneas en blanco.
- Líneas partidas.
- Indentación con tabuladores o espacios indistintamente (la predeterminada es con espacios).
- Etiquetas en lugar de números de línea.
- Primer número de línea.
- Inclusión de otros ficheros fuente.
Comentarios
En SBim puede usarse las comillas simples para los comentarios de línea, como es habitual en otras versiones de BASIC. Estos comentarios serán eliminados durante la conversión del código a S*BASIC.
Estos comentarios pueden estar al inicio de la línea (precedidos opcionalmente por espacios o tabuladores), siempre que estén seguidos por un espacio (o nada). Si no están al inicio de la línea deben estar precedidos por un espacio y seguidos por otro espacio (o nada).
Por supuesto, los comentarios de línea propios de S*BASIC, con rem
o REMark
, pueden seguir usándose y serán conservados.
Líneas partidas
Una línea de código fuente puede ser dividida en más de una línea de texto. SBim usa una barra inclinada hacia la izquierda («\»), como es habitual en diversos lenguajes de programación.
En S*BASIC «\» es un separador de impresión que en ocasiones puede quedar al final de la línea de código. Para evitar la ambigüedad en dichos casos se puede añadir el separador de instrucciones, los dos puntos («:»).
La indentación de las líneas de texto segunda y siguientes de una línea de código dividida no es tenida en cuenta, será eliminada en la línea de código resultante. Pero los espacios antes del signo «\» sí se conservarán, y a menudo son imprescindibles, como muestra el siguiente ejemplo, en que también se ilustra el uso original de «\» como operador de impresión:
let \
d=18871215
print\
"Saluton, \
mondo!":\
list
print\ \
"Bonan matenon"
print\:
clear
El código anterior convertido a S*BASIC:
1 let d=18871215
2 print "Saluton, mondo!":list
3 print\"Bonan matenon"
4 print\:
5 clear
Etiquetas
En SBim se usan etiquetas en lugar de números de línea.
Los nombres de las etiquetas deben empezar siempre por el signo de la arroba, tanto al definirlas como al referenciarlas. El resto de caracteres pueden ser letras del código ASCII, dígitos o el signo de subrayado («_»), en cualquier orden o combinación. No se hace distinción entre minúsculas y mayúsculas. No hay longitud máxima para el nombre de una etiqueta (salvo la impuesta por el lenguaje de programación de Vim, cualquiera que sea).
Para definir una etiqueta basta que su nombre esté al comienzo de una línea de código (puede haber espacios o tabuladores antes):
gosub @routine1
stop
@routine1
print "Routine 1"
@wait
if not rnd then goto @wait
return
Para mayor claridad, puede usarse el comando label
para definir las etiquetas:
gosub @routine1
stop
label @routine1
print "Routine 1"
label @wait
if not rnd then goto @wait
return
Cualquier palabra que en el código fuente cumpla las condiciones para ser una etiqueta será tomada como tal, independientemente de su posición en el código. Esto permite incluir etiquetas dentro de cadenas o en cálculos, como haríamos con los propios números de línea:
restore @my_data+index
Sin embargo eso puede tener efectos indeseados. Por ejemplo, dado el siguiente código:
label @blabla
print "Hola"
print "Mi correo-e es: superbasic@blablabla.info"
print "Y mi otro correo-e es: superbasic@blabla.info"
El resultado final en S*BASIC sería el siguiente, con una sustitución indeseada en la última línea:
2 print "Hola"
3 print "Mi correo-e es: superbasic@blablabla.info"
4 print "Y mi otro correo-e es: superbasic1.info"
Para evitar esas posibles coincidencias debe usarse algún medio de camuflar la falsa etiqueta, como en los ejemplos siguientes:
rem Arroba camuflada:
print "Y mi otro correo-e es: superbasic";chr$(64);"blabla.info"
rem Arroba separada:
print "Y mi otro correo-e es: superbasic@";"blabla.info"
Primer número de línea
Puede definirse el primer número de línea que se usará para renumerar el programa final, con el comando #firstline
, que debe estar al inicio de cualquier línea del programa (los espacios o tabuladores previos no serán tenidos en cuenta):
#firstline 9000
Si hay más de un comando #firstline
solo el primero será tenido en cuenta. Si no se usa este comando, el primer número de línea será el uno.
Inclusión de otros ficheros fuente
Es posible incluir otros ficheros fuente dentro del principal con la directiva #include
:
#include lib/routine.bas
A su vez, el fichero incluido puede contener otras directivas #include
.
Metaetiquetas y conversión condicional
#define
: definir metaetiquetas.#ifdef
,#ifundef
,#else
y#endif
: incluir un zona de código si ha sido o no definida una metaetiqueta.
#define debugging
#define qdos
#ifdef debugging
print "I'm debugging"
#endif
#ifdef qdos
' QDOS version
#else
' SMSQ/E version
#endif
Posibles mejoras
Entre las ideas consideradas para futuras versiones del formato SBim están las siguientes:
#macro
: definir macros.#renum
: renumerar definiendo el intervalo.- Hacer las estructuras
#ifdef
e#ifndef
anidables.
Instalación
Para instalar SBim basta ejecutarmake install
en el directorio del programa. Los ficheros propios de Vim se instalarán en <~/.vim>, pero el comando sbim
se copiará en <~/bin>. Si se desea usar en su lugar, hay que editar el valor de la variable BINDIR
en el fichero make uninstall
.
Uso
Cuando se usa el editor Vim para editar ficheros con extensión «.sbim», o ficheros que incluyen una línea de configuración de Vim con el comando filetype=sbim
, estará disponible el atajo de teclado _bas para convertir el código en formato estándar de S*BASIC. El resultado será un fichero con el mismo nombre que el actual y en su mismo directorio, pero con la extensión _bas añadida.
SBim puede usarse también como un comando, en cuyo caso puede indicarse el nombre del fichero de salida. Ejemplos:
sbim inputfile.bas
sbim inputfile.bas outputfile.bas
Código fuente
El fichero principal de SBim es el conversor de código:
" sbim.converter.vim
" (~/.vim/sbim.vim)
" This file is part of SBim
" http://programandala.net/es.programa.sbim.html
" Last modified 201710181334
" See change log at the end of the file
" ==============================================================
" Description
" This VimL program converts S*BASIC source code written in the
" SBim format to an ordinary S*BASIC file ready to be loaded by
" a S*BASIC interpreter.
" ==============================================================
" Author and license
" Author: Marcos Cruz (programandala.net), 2011, 2012, 2015,
" 2016, 2017
" You may do whatever you want with this work, so long as you
" retain the copyright/authorship/acknowledgment/credit
" notice(s) and this license in all redistributed copies and
" derived works. There is no warranty.
" ==============================================================
if exists("*SBim")
" Function `SBim` is already defined.
finish
endif
" ==============================================================
" Generic functions {{{1
function! Trim(input_string)
" Remove trailing spaces from a string.
" Reference:
" http://stackoverflow.com/questions/4478891/is-there-a-vimscript-equivalent-for-rubys-strip-strip-leading-and-trailing-s
return substitute(a:input_string, '^\s*\(.\{-}\)\s*$', '\1', '')
endfunction
" ==============================================================
" Cleaning {{{1
function! SBimClean()
silent %s,\t\+, ,ge " Remove tabs
echo 'Tabs removed.'
silent %s,\(^\s*\|\s\+\)'\(\s.*\)\?$,,e " Remove line comments
echo 'Comments removed.'
silent %s,^\s*\n,,ge " Remove the empty lines
echo 'Empty lines removed.'
silent %s,^\s*\(.\+\)\s*$,\1,e " Remove blanks
echo 'Indentation and blanks removed.'
silent %s,\\\s*\n,,e " Join the splitted lines
echo 'Splitted lines joined.'
endfunction
" ==============================================================
" Renum
function! SBimRenum()
" Put line numbers;
" remove spaces from blank lines;
" save the file.
" 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:renumLine." --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 all spaces from lines that only contain spaces:
":%s/^\s\+$//
" Remove spaces before line numbers
"(nl has no option to remove them):
silent %substitute/^\s*//e
echo 'Line numbers added.'
endfunction
function! SBimGetRenumLine()
" Store into s:renumLine the first line number
" to be used by the final S*BASIC program
" The directive `#renum` can be used to set
" the desired line number. Only the first occurence
" of `#renum` will be used; it can be anywhere
" in the source but always at the start of a line
" (with optional indentation).
let s:renumLine=1 " default value
call cursor(1,1)
if search('^\s*#renum\s\+[0-9]\+\>','Wc')
let l:valuePos=matchend(getline('.'),'^\s*#renum\s*')
let s:renumLine=strpart(getline('.'),l:valuePos)
call setline('.','')
endif
echo 'Renum line: '.s:renumLine
endfunction
" ==============================================================
" #include {{{1
function! SBimInclude()
" Execute all '#include' directives.
" Syntax:
" #include filename
" 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('.',"' <<< start of included file ".l:filename)
call append('.',"' >>> end of included file ".l:filename)
let l:filecontent=readfile(s:sourceFileDir.'/'.l:filename)
call append('.',l:filecontent)
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
" ==============================================================
" Conditional conversion {{{1
function! SBimDefine()
" Search and execute all '#define' directives.
" There can be any number of '#define' directives, but they
" must be alone on their own source lines (with optional
" indentation).
call cursor(1,1) " Go to the top of the file
while search('^\s*#define\s\+','Wc')
let l:definition=getline('.')
let l:tagPos=matchend(l:definition,'^\s*#define\s\+')
let l:tag=strpart(l:definition,l:tagPos)
if !empty(l:tag)
call add(s:definedTags,l:tag)
endif
call setline('.','')
endwhile
let l:tags=len(s:definedTags)
if l:tags==1
echo l:tags.' #define directive'
elseif l:tags>1
echo l:tags.' #define directives'
endif
endfunction
function! SBimDefined(needle)
" Is _needle_ a defined tag?
" echo "XXX About to search for the <".a:needle."> tag!"
let l:found=0 " XXX needed, but why? Otherwise, error: undefined variable
for l:tag in s:definedTags
" echo 'XXX tag: '.l:tag
let l:found=(l:tag==a:needle)
if l:found
break
endif
endfor
return l:found
endfunction
function! SBimConditionalConversion()
" Parse and interpret all conditional conversion directives.
" XXX TODO -- Make the structure nestable.
" Syntax:
"
" #if[n]def tag
" ...
" #else
" ...
" #endif
" Note: The conditions can not be nested.
call cursor(1,1)
let l:unresolvedCondition=0 " flag
while search('^\s*#if\(n\)\?def\s\+.\+$','Wc')
let l:else=0 " flag
" echo 'XXX first #if[n]def found'
while line('.')<line('$') " not at the end of the file?
let l:currentLine=getline('.')
if l:currentLine=~'^\s*#ifdef\s\+.\+'
" #IFDEF
" echo 'XXX #ifdef found'
if l:unresolvedCondition
echoerr '`#if[n]def` structures can not be nested'
break
else
call SBimIfdef()
let l:unresolvedCondition=1
endif
elseif l:currentLine=~'^\s*#ifndef\s\+.\+'
" #IFNDEF
" echo 'XXX #ifndef found ----------------------'
if l:unresolvedCondition
echoerr '`#ifndef` structures can not be nested'
break
else
call SBimIfdef()
let l:unresolvedCondition=1
endif
" elseif l:currentLine=~'^\s*#elseifdef\s\+.\+'
" " #ELSEIFDEF
" call setline('.','')
" if !l:unresolvedCondition
" " XXX TODO
" endif
" elseif l:currentLine=~'^\s*#elseifndef\s\+.\+'
" " #ELSEIFNDEF
" call setline('.','')
" if !l:unresolvedCondition
" " XXX TODO
" endif
elseif l:currentLine=~'^\s*#else\s*$'
" #ELSE
" echo 'XXX #else found'
if l:else
echoerr 'More than one `#else` in a `#if[n]def` structure'
break
else
let l:else=1
call setline('.','')
let s:keepSource=!s:keepSource
endif
elseif l:currentLine=~'^\s*#endif\s*$'
" #ENDIF
" echo 'XXX #endif found'
call setline('.','')
let l:unresolvedCondition=0
break
else
if l:unresolvedCondition && !s:keepSource
call setline('.','')
endif
endif
call cursor(line('.')+1,1) " go to the next line
endwhile
if l:unresolvedCondition
echoerr '`#if[n]def` or `#endif` at line '.l:ifLineNumber
endif
endwhile
echo 'Conditional conversion done'
endfunction
function! SBimIfdef()
let l:ifLineNumber=line('.')
let l:tagPos=matchend(getline('.'),'^\s*#if\(n\)\?def\s\+')
let l:tag=Trim(strpart(getline('.'),l:tagPos))
" echo 'XXX l:tag='.l:tag
let l:tagMustBeDefined=(getline('.')=~'^\s*#ifdef')
" echo 'XXX l:tagMustBeDefined='.l:tagMustBeDefined
let l:tagIsDefined=SBimDefined(l:tag)
" echo 'XXX l:tagIsDefined='.l:tagIsDefined
let s:keepSource=(l:tagMustBeDefined && l:tagIsDefined) || (!l:tagMustBeDefined && !l:tagIsDefined)
" echo 'XXX s:keepSource='.s:keepSource
call setline('.','')
endfunction
" ==============================================================
" Labels
function! SBimLabels()
" Translate the line labels.
" Join lonely labels to the next line:
silent! %substitute,^\s*\(\(label\s\+\)\?@[0-9a-zA-Z_]\+\)\s*\n,\1:,ei
" Create an empty dictionary to store the line numbers of the
" labels; the labels will be used as keys:
let l:lineNumber={}
" Go to the top of the file:
normal gg
" Store every label into the dictionary:
while search('\(^\|:\)\s*\(label\s\+\)\?@[0-9a-zA-Z_]\+\>','Wc')
" Store the label in the 'l' register:
normal "l2yw
" Debug message:
" echo 'Raw label found: <' . getreg('l',1) . '>'
" If the label is the second one in a line, remove the semicolon:
" if stridx(getreg('l',1),':')==0
" call setreg('l',strpart(getreg('l',1),1))
" endif
" If 'label' is present, go to the next word and repeat:
if tolower(getreg('l',1))=='label @'
normal w"l2yw
" Debug message:
" echo 'Actual raw label found: <' . getreg('l',1) . '>'
endif
" Remove possible ending spaces:
let l:label=tolower(substitute(getreg('l',1),' ','','g'))
" Debug message:
"echo 'Clean label: <' . l:label . '>'
" Use the label as the key to store the line number:
let l:lineNumber[l:label]=line('.')+s:renumLine-1
" Go to the next word:
normal w
endwhile
" Debug message:
"echo l:lineNumber
" Remove the label definitions:
silent! %substitute/^\s*\(label\s\+\)\?@[0-9a-zA-Z_]\+\s*:\?\s*//ei
" Substitute every label reference with its line number:
for l:label in keys(l:lineNumber)
" Go to the top of the file:
normal gg
" Debug message:
"echo 'About to translate label '.l:label
" Do the subtitution:
while search(l:label,'W')
" Debug message:
"echo 'Label '.l:label.' found'
"execute "silent! substitute,".l:label."\\>,".l:lineNumber[l:label].","
silent! execute "substitute,".l:label."\\>,".l:lineNumber[l:label].","
" Debug message:
"echo l:label.' --> '.l:lineNumber[l:label]
endwhile
endfor
echo 'Labels translated.'
endfunction
" ==============================================================
" Output file
function! SBimOutputFile(outputFile)
if empty(a:outputFile)
let l:outputFile=expand('%').'_bas'
else
let l:outputFile=a:outputFile
endif
" Filename of the source file, without path
"let s:sourceFilename=fnamemodify(expand('%'),':t')
" Absolute directory of the source file
let s:sourceFileDir=fnamemodify(expand('%'),':p:h')
silent update " Write the current SBim file if needed
split " Split the window
" Save a copy of the input file as output file:
silent execute 'write! '.l:outputFile
" Open the output file for editing:
silent execute 'edit '.l:outputFile
set fileencoding=latin1
set fileformat=unix " Force NL (char 10) as end of line
echo 'S*BASIC file created.'
endfunction
" ==============================================================
" Main
function! SBim(outputFile)
let s:ignoreCaseBackup=&ignorecase
set ignorecase
let s:definedTags=[] " a list for the '#define' tags
call SBimOutputFile(a:outputFile)
call SBimInclude()
call SBimGetRenumLine()
call SBimDefine()
call SBimConditionalConversion()
call SBimClean()
call SBimLabels()
call SBimRenum()
silent w
silent bw
echo 'S*BASIC file saved and closed.'
if s:ignoreCaseBackup
set ignorecase
else
set noignorecase
endif
echo 'Done!'
endfunction
" Shortkey '_bas' in normal mode to create an S*BASIC file:
nmap <silent> _bas :call SBim("")<CR>
" ==============================================================
" Change log
" 2011-08-13: First version, named sb2sbasic.vim, based on
" vim2mb.vim ("Vim to MasterBASIC"), by the same author. [vim2mb
" was the origin of MBim].
"
" 2011-08-16: Added removing of blanks (tabs or spaces) at the
" end of lines; added start line parameter for renumbering;
" created alternative shortcut key to create boot files.
"
" 2011-08-25: Added C style comments, block and inline.
"
" 2011-09-01: Renamed to sbim2bas.vim; own renum function
" instead of using that of line_numbers.vim; all functions
" renamed with the "SBim" prefix.
"
" 2011-09-26: Renamed to sbim2sb.vim; some functions renamed;
" some comments improved.
"
" 2011-10-04: Block comments fixed with '\{-}'; 'silent' added
" to many commands; 'e' flag added to substitutions; custom
" messages added.
"
" 2012-01-25: New function SBimLabels(), based on BBimLabels()
" from BBim. Fixed: lonely labels had to be joined with the
" next line; optional final ":" removed with labels.
"
" 2012-01-26: The character to split lines now is backslash
" instead of the vertical bar.
"
" 2012-01-28: Improvement: labels are not case sensitive any
" more.
"
" 2012-01-28: New feature: #firstline command to define the
" first line that will be used to renumber the final program in
" S*BASIC.
"
" 2012-01-28: Improvement: The S*BASIC buffer is actually closed
" with "bw" ("wq" kept it on the list).
"
" 2012-01-29: Improvement: Only one regex, simpler and fixed, to
" remove empty lines (the old three regex left the first line of
" the file blank).
"
" 2012-01-29: Fixed: Spaces or tabs at the end of line, after
" the split bar, are ignored.
"
" 2012-02-01: Bug found. The label translation loop doesn't end
" in some cases.
"
" 2012-02-15: Fixed? The 'c' parameter was removed from search()
" in the label translation loop.
"
" 2015-12-26: Changed the layout of the source. Renamed the
" program to "SBim". Changed the license.
"
" 2016-01-13: Fixed `SBimGetFirstLine()`: the directive was
" yanked instead of the line number.
"
" 2016-01-19: Typo. Updated header.
"
" 2016-01-25: Added `#include` directive.
"
" 2017-09-12: Make the output file configurable. Improve the
" line comments with one mandatory sourrounding space. Allow
" Bash-style line comments at the end of the line. Add
" BASIC-style line comments with a single quote.
"
" 2017-09-20: Remove support for C-style and Bash-style
" comments. Fix the call in the map (missing parameter).
"
" 2017-09-21: Replace the C-style comment added by
" `SBimInclude()` with the new BASIC-style comment.
"
" 2017-09-28: Add a check to prevent the code from being loaded
" twice.
"
" 2017-09-29: Prepare implementation of conditional conversion
" (`#define`, `#ifdef`, `#ifndef`) -- code copied from
" Imbastardizer.
"
" 2017-10-17: Fix and tidy Imbastardizer's code about
" conditional conversion.
"
" 2017-10-18: Rename `#firstline` to `#renum`, `s:firstLine` to
" `s:renumLine` and `GetFirstLine` to `GetRenumLine`. Improve
" function `GetRenumLine`.
" vim: textwidth=64:ts=2:sw=2:sts=2:et
Descargas
También se puede descargar la última versión 2.0.0 en desarrollo de SBim en GitHub.