When converting from a batch function to a macro, you:
1) might have to change the code, to run within an "()" statement, and
2) you have to create a variable, that contains the above source code.
Especially when performing the second step, you can easily oversee some
characters to escape, so the result is buggy.
I nearly got mad when converting my floating point functions to macros:
viewtopic.php?f=3&t=4925
.
As a result i've written a tool that does the second step automatically.
I've added its source at the end of this topic, so it is easier to copy
the example, too.
I think it was easier to define each macro in its own file, so you need
an additional package file to add some single macros (and some additional
information) to the resulting macro laoder batch file.
Here is an example consisting of four text files (macro creation <2 secs):
- "envStatusPackage.txt"
- "getExtensionsState.txt"
- "getDelayedExpansionState.txt"
- "runFromCmdLine.txt"
The package file "envStatusPackage.txt":
Code: Select all
:: Author: Ulf Schneider aka penpen
:: Version: 1.0.0
:: Date: 16.08.2012
::
:: This batch file uses the result of the Topic on "www.dostips.com":
:: Create nul and all ascii characters with only batch
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=5326
:: Teamwork of carlos, penpen, aGerman, dbenham, einstein1969
:: I'm using my own simpler version (that is less fast to create the table data)
::
@echo off
call :mainEntryPoint %*
if defined errorlevel exit /b %errorLevel%
:createCharacterFiles
:: #####################################
:: This is an older version in an slightly different variation.
:: It may be replaced by any newer (== faster) version: See link above.
:: In addition i have used the actual directory ".", instead of ".\characters"
REM This code creates 256 files containing one single Byte each from 0x00 until 0xFF
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
>"t.tmp" type nul
(
for %%A in (0 1 2 3 4 5 6 7 8 9 A B C D E F) do for %%B in (0 1 2 3 4 5 6 7 8 9 A B C D E F) do for /L %%N in (0x%%A%%B, 1, 0x%%A%%B) do (
makecab /d compress=off /d reserveperfoldersize=%%N /d reserveperdatablocksize=26 "t.tmp" "%%A%%B.chr"
type "%%A%%B.chr" | ((for /l %%I in (1 1 38) do pause)&(findstr "^">"temp.tmp"))
copy /y "temp.tmp" /a "%%A%%B.chr" /b
)
copy /y nul + nul /a "1A.chr" /a
) > nul
del "t.tmp" "temp.tmp"
:: #####################################
goto :eof
:initialize
set "workDir=%~dp0."
set "sourceDir="
set "dummyFile=dummy.dat"
set "tempFile=temp.tmp"
set "inputFile=%~1"
set "outputFile=%~2"
if defined outputFile (set redirectToOutput=^>"!outputFile!") else set "redirectToOutput="
for %%a in ("%~1") do set "packageDir=%%~dpa."
if not defined packageDir set "packageDir=%%~dpa"
:: check input
if defined inputFile if not exist "!inputFile!" set "inputFile="
if not defined inputFile (
echo(Usage: %~n0[%~x0] source [target]
echo( source file to load a package of batch programs from
echo( target file to store the created macro package file
echo(
echo( Converts a package of macros to a loader batch source.
echo( Uage is at your own risk.
echo(
echo(
echo([package source file]
echo(The package source file must be:
echo( - stored in ANSI,
echo( - readable using set "var=" ^& set /P "var=" ^(no poison character combination^),
echo( - writable using echo ^^^!var^^^!, and
echo( - it must have this form:
REM This might be some a little bit pedestrian, but it is also handy to avoid to:
REM - define a grammar,
REM - implement a deterministic finite automaton that recognizes the grammar,
REM - compiler, ...
REM - ...
echo( +------------------- samplePackage.txt -------------------+
echo( ^| ^|
echo( ^| #packInfo Some info ^|
echo( ^| #packInfo text the ^|
echo( ^| #packInfo with ^|
echo( ^| #packInfo ^|
echo( ^| ^|
echo( ^| #packInfo other ^|
echo( ^| #packInfo ^|
echo( ^| #packInfo Some text ^|
echo( ^| ^|
echo( ^| #macroDef "sampleFile.txt" ^|
echo( ^| #macroDef Some ^|
echo( ^| #macroDef macro ^|
echo( ^| #macroDef info ^|
echo( ^| #macroDef text. ^|
echo( ^| ^|
echo( ^| #macroDef "anotherSampleFile.txt" ^|
echo( ^| #macroDef Some ^|
echo( ^| #macroDef macro ^|
echo( ^| #macroDef info ^|
echo( ^| #macroDef text2. ^|
echo( ^| ^|
echo( ^| ^|
echo( +---------------------------------------------------------+
echo(
echo(
echo([macro source file]
echo(The referenced macro source file lines are detected as:
echo( - macro comments if the first 3 chars equals "REM" ^(case sensitive^), the
echo( fourth character is a space or a tab and if there is NO doublequote in
echo( this line ^(because this would break the macro comment^)
echo( - code lines else
echo(
echo(All macro source files must:
echo( - be stored in ANSI format,
echo( - have a maximum file size of 8191 bytes
echo(
echo(All leading and trailing newline characters will be removed.
echo(It is assumed that no macro makes any use of carriage return characters:
echo(Such character will be cut off silently.
echo(It is assumed that no macro makes any use of NUL characters:
echo(Such character will be replaced by the space character silently.
echo(The poison character ^('^^^!'^) is escaped within macro comments.
echo(
echo(
echo([destination file]
echo(The file "table.dat" is needed to create the target file. This file is created
echo(automatically if it does not exist. If a file "table.dat" exists then it has to
echo(contain the binary content [0x20, 0x01 : 0xFF], else there may be unexpected
echo(results.
echo(
echo(A '$' character and the name of the source file ^(without the extension^) is the
echo(macro name:
echo(If the file is "macro.txt" then the resultig macro name is "$macro".
echo(
echo(If no target file is defined then the source is written to screen.
echo(
echo(If the conversion leads to macros of a bigger size than 8192 bytes, it may not
echo(work: You have to test the result, for example by using the set command.
echo(The converted lines must fit in one environment variable ^(^<^= 8192 characters^).
echo(
echo(If you want to replace the tabulator, then just define the environment variable
echo("tab" with that value.
echo(
exit /b 1
)
:: setup temporary directory
if defined temp (set "tempDir=!temp!") else if defined tmp (set "tempDir=!tmp!") else (set "tempDir=.")
cmd /A /D /E:ON /V:ON /C "@for /L %%a in (0, 0, 0) do @( set "subDir=^^^!random^^^!" & if NOT exist "!tempDir!\%~n0_^^^!subDir^^^!" exit ^^^!subDir^^^!) "
set "tempDir=!tempDir!\%~n0_!errorlevel!"
2>nul md "!tempDir!"
set "dummyFile=!tempDir!\!dummyFile!"
set "tempFile=!tempDir!\!tempFile!"
:: create table.dat and load table
if NOT exist "!workDir!\table.dat" (
echo(File "table.dat" with binary ascii data [0x20, 0x01 : 0xFF] does not exist.
echo(This file is needed, so it will be created: This may take a while.
pushd "!tempDir!"
REM create single byte binary files
call :createCharacterFiles
REM create table.dat
>nul copy "20.chr" "table.dat"
for %%l in (1 2 3 4 5 6 7 8 9 A B C D E F) do (>nul copy "table.dat" /b + "0%%~l.chr" /b + "table.dat" /b)
for %%h in (1 2 3 4 5 6 7 8 9 A B C D E F) do for %%l in (0 1 2 3 4 5 6 7 8 9 A B C D E F) do (>nul copy "table.dat" /b + "%%~h%%~l.chr" /b + "table.dat" /b)
REM move "table.dat" to working directory
move "table.dat" "!workDir!"
REM delete single byte binary files (tempDir does not contain any other files)
del /q "!tempDir!\."
popd
echo(Successfully created file: "table.dat".
)
if exist "!workDir!\table.dat" (
set "table="
<"!workDir!\table.dat" set /P "table="
) else (
echo(Unexpected error on creating file: "table.dat".
exit /b 0
)
set "tabulator=!table:~0x09,1!"
if defined tab for %%r in ("!tab!") do set "tabulator=!tabulator:%tabulator%=%%~r!"
:: create "!dummyFile!" := [ CR^8192 ]
if not exist "!dummyFile!" (
setlocal enableDelayedExpansion
>nul copy /Y nul "!dummyFile!" /A
for /F "usebackq" %%a in ("!dummyFile!") do set "SUB=%%a"
for /F %%a in ('copy /Z "!dummyFile!" nul') do set "CR=%%a"
set "CRS=!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!!CR!"
set "CRS=!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!"
set "CRS=!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!!CRS!"
>"!dummyFile!" echo(!CRS!!SUB!
>nul copy /Y "!dummyFile!" /A "!tempFile!" /B
>nul copy /Y /B "!tempFile!" + "!tempFile!" "!dummyFile!"
del "!tempFile!"
endlocal
)
goto :eof
:finalize
:: delete temporary directory (and all files in it)
if defined tempDir (
del /q "!tempDir!\."
rd "!tempDir!"
)
goto :eof
:loadMacroPackageData
:: load macro package info
for %%t in ("#packInfo" "#macroDef") do (
set "%%~t="
set "%%~t.length=0"
)
for /F "tokens=* delims=:" %%a in ('findstr /N "^^" "!inputFile!" ') do set "lines=%%~a"
set /A "lines+=0"
(
for /L %%l in (1, 1, !lines!) do (
set "line="
set /P "line="
set "token=!line:~0,9!"
if NOT defined line (
REM forward packageInfo, or macro if needed
set "token="
if defined #packInfo set "token=#packInfo"
if defined #macroDef set "token=!token! #macroDef"
for %%t in (!token!) do set "%%~t="
set "token="
) else if NOT "!token!" == "#packInfo" if NOT "!token!" == "#macroDef" (
echo( @REM
echo( @REM Unknown directive in macro source package on line %%~l.
echo( @REM This line will be ignored: "!line!"
echo( @REM
set "token="
)
if defined token (
REM replace tabulator if wanted (tab defined)
if defined tab for %%t in ("!table:~0x09,1!") do for %%r in ("!tab!") do set "line=!line:%%~t=%%~r!"
for %%t in ("!token!") do (
if not defined %%~t (
set /A "%%~t=(%%~t.length+=1)"
for %%b in ("!%%~t.length!") do set /A "%%~t[%%~b].length=0"
)
for %%b in ("!%%~t.length!") do (
set /A "%%~t[%%~b].length+=1"
for %%i in ("!%%~t[%%~b].length!") do (
set "%%~t[%%~b][%%~i]=!line:~9!"
)
)
)
)
)
) <"!inputFile!"
set "#packInfo="
set "#macroDef="
goto :eof
:escapeSourceData
:: %~1 macro id
set "macroId=%~1"
for %%a in (!#macroDef[%macroId%][1]:~1!) do for %%b in ("%%~a") do set "source.file=!packageDir!\%%~a"
set "line= "
set /A "sourceType=0", "commentType=1"
set "lastType=!sourceType!"
set "returnCounts=0"
set "isFirstLine=true"
for /F "tokens=2" %%a in ('^(fc /B "!source.file!" "!dummyFile!" ^& echo^(0 0A 0A ^)^| findstr "^^[0-9]"') do (
set "hex=0x%%~a"
if NOT !hex! EQU 0x0A (
set "line=!line!%%~a "
) else (
set "buffer=!line:~1,11!"
if NOT defined buffer (
if NOT defined isFirstLine set /A "returnCounts+=1"
) else (
if defined escaped (
if defined tab for %%t in ("!table:~0x09,1!") do set "escaped=!escaped:%%~t=%tab%!"
echo(!escaped!
if !lastType! EQU !commentType! if NOT !returnCounts! EQU 0 (
echo(%%]%%
set "lastType=sourceType"
)
for /L %%b in (1, 1, !returnCounts!) do echo(%%\n%%
set "returnCounts=0"
)
set "type=!commentType!"
if NOT "!buffer!" == "52 45 4D 20" (
if NOT "!buffer!" == "52 45 4D 09" set "type=!sourceType!"
) else if NOT "!line:22=!" == "!line!" set "type=!sourceType!"
set "inString=!type!"
set "escaped="
for %%c in (!line!) do (
if 0x%%~c EQU 0x22 (
set "escaped=!escaped!!table:~0x%%~c,1!"
set /A "inString=1-inString"
) else if !inString! EQU 1 (
rem may be escaped within strings: ^!
if !type! == !commentType! (
if 0x%%~c EQU 0x21 ( set "escaped=!escaped!^^!table:~0x%%~c,1!"
) else set "escaped=!escaped!!table:~0x%%~c,1!"
) else if 0x%%~c EQU 0x5E ( set "escaped=!escaped!%%Ti%%"
) else if 0x%%~c EQU 0x21 ( set "escaped=!escaped!%%Ei%%"
) else ( set "escaped=!escaped!!table:~0x%%~c,1!"
)
) else (
rem must be escaped outside strings: ^!&()<>|
if 0x%%~c EQU 0x5E ( set "escaped=!escaped!%%To%%"
) else if 0x%%~c EQU 0x21 ( set "escaped=!escaped!%%Eo%%"
) else if 0x%%~c EQU 0x26 ( set "escaped=!escaped!^^&"
) else if 0x%%~c EQU 0x28 ( set "escaped=!escaped!^^("
) else if 0x%%~c EQU 0x29 ( set "escaped=!escaped!^^)"
) else if 0x%%~c EQU 0x3C ( set "escaped=!escaped!^^<"
) else if 0x%%~c EQU 0x3E ( set "escaped=!escaped!^^>"
) else if 0x%%~c EQU 0x7C ( set "escaped=!escaped!^^|"
) else ( set "escaped=!escaped!!table:~0x%%~c,1!"
)
)
)
if !type! EQU !commentType! (
if NOT !lastType! EQU !type! echo(%%[%%
(set escaped=::"!escaped:~3!"^^^^)
) else (
if NOT !lastType! EQU !type! echo(%%]%%
(set escaped=!escaped!%%\n%%)
)
set "lastType=!type!"
set "line= "
set "buffer="
set "isFirstLine="
)
)
)
if !lastType! EQU !commentType! (
if defined escaped echo(!escaped!
echo(%%]%%
) else (
if defined escaped echo(!escaped:~0,-4!
)
for %%a in (
"macroId", "source.file", "line", "sourceType", "commentType", "lastType",
"returnCounts", "isFirstLine", "hex", "buffer", "escaped", "inString"
) do set "%%~a="
goto :eof
:createPackage
call :loadMacroPackageData
:: build macro package loader
(
echo(@echo off
echo(:: define info header
echo(::
echo(!tabulator!:: This batch file is automatically created using "blockToMacro.bat".
echo(!tabulator!:: You may find it on "www.dostips.com".
echo(
echo(!tabulator!::
echo(!tabulator!:: macro^(s^) loaded into memory by this batch file:
for /L %%m in (1, 1, !#macroDef.length!) do for %%n in (!#macroDef[%%~m][1]!) do (
echo(!tabulator!:: - $%%~nn
)
echo(!tabulator!::
echo(
echo(!tabulator!::
echo(!tabulator!:: supported modes of operation:
echo(!tabulator!:: ---------------------------------------------+---+---+---+---
echo(!tabulator!:: extensions ^(e/d: en/disabled^) ^| e ^| e ^| e ^| d
echo(!tabulator!:: delayed expansion ^(e/d: en/disabled^) ^| e ^| d ^| d ^|e/d
echo(!tabulator!:: run from command line ^(y/n: yes/no^) ^|y/n^| y ^| n ^|y/n
echo(!tabulator!:: ---------------------------------------------+---+---+---+---
echo(!tabulator!:: macro behaviour ^(l/e: load/executable^) ^|l+e^| l ^|l+e^| -
echo(!tabulator!::
echo(
echo(!tabulator!::
echo(!tabulator!:: This batch overwrites the following environment variable names
echo(!tabulator!:: of the current context, and will finally undefine them:
echo(!tabulator!:: - Eo
echo(!tabulator!:: - To
echo(!tabulator!:: - Ei
echo(!tabulator!:: - Ti
echo(!tabulator!::
for /L %%b in (1, 1, !#packInfo.length!) do (
echo(
echo(!tabulator!::
for /L %%i in (1, 1, !#packInfo[%%~b].length!) do (
echo(!tabulator!::!#packInfo[%%~b][%%~i]!
)
echo(!tabulator!::
)
echo(::
echo(:: end define info header
echo(
echo(
echo(:: define check for valid system state
echo(::
echo(:: If the extensions are disabled, the set command throws an "syntax error",
echo(:: when assigning an equal sign ^('='^):
echo(:: This is essential for complex macros, so the extensions MUST be enabled.
echo(::
echo(2^> nul set "extensionsEnabled=true" ^|^| ^(
echo(!tabulator!echo^(Error: The extensions are disabled, no macro will be loaded into memory.
echo(
echo(!tabulator!rem simulate: exit /b 1
echo(!tabulator!rem if extensions are disabled the label :EOF is not found
echo(!tabulator!call
echo(!tabulator!goto exit
echo(^)
echo(::
echo(:: end define check for valid system state
echo(:: extensions: enabled
for /L %%m in (1, 1, !#macroDef.length!) do (
REM setup macro Name
for %%n in (!#macroDef[%%~m][1]!) do set "macroName=$%%~nn"
REM write the macro to the output
echo(
echo(
echo(:: define !macroName!
for /L %%i in (2, 1, !#macroDef[%%~m].length!) do (
echo(::!#macroDef[%%~m][%%~i]!
)
echo(set "Eo=1"
echo(if "^!Eo^!" == "1" ^(
echo(!tabulator!set "Eo=^^^^^^^^^^^^^^^!"
echo(!tabulator!set "Ei=^^^^^^^!"
echo(!tabulator!set "To=^^^^^^^^"
echo(!tabulator!set "Ti=^^^^"
echo(^) else ^(
echo(!tabulator!set "Eo=^!"
echo(!tabulator!set "Ei=^!"
echo(!tabulator!set "To=^^^^"
echo(!tabulator!set "Ti=^^"
echo(^)
echo(
echo(setlocal enableExtensions disableDelayedExpansion
echo(set LF=^^
echo(
echo(
echo(set ^^^"\n=^^^^^^%%LF%%%%LF%%^^%%LF%%%%LF%%^^^<nul ^^^^^^^"
echo(set ^^^"[=%%%%~#^^^<nul ^^^^^^%%LF%%%%LF%%^^^<nul ^^^^^^^"
echo(set ^^^"]=^^^<nul^^^^^^%%LF%%%%LF%%^^%%LF%%%%LF%%^^^<nul ^^^^^^^"
echo(endlocal ^& ^(
echo(REM ==================== define { // !macroName! ====================
echo(
echo(
<nul set /P "=for %%%%# in ^(""^) do set !macroName!="
call :escapeSourceData "%%~m"
echo(
echo(
echo(REM ==================== } // !macroName! ====================
echo(^)
echo(endlocal
echo(set "Eo="
echo(set "Ei="
echo(set "To="
echo(set "Ti="
echo(::
echo(:: end define !macroName!
)
echo(
echo(::
echo(:: All macros loaded to memory: return success ^(errorLevel == 0^).
echo(exit /b 0
echo(
echo(:exit
echo(
)
goto :eof
:mainEntryPoint
echo(started: %time%
setlocal enableExtensions enableDelayedExpansion
call;
call :initialize %*
if errorlevel 1 goto :eof
%redirectToOutput% (
call :createPackage
)
call :finalize
endlocal
echo(finsihed: %time%
goto :eof
penpen
Edit 1: Added doublequotes around the first file parameter of "fc", to allow spaces in this filename, thanks to aGerman for finding this bug.
Edit 2: Removed the (not needed) memory mapping, and some bugs.
Edit 3: Removed the "slow code" part (much faster now).
Edit 4: Corrected the documentation, replaced:
- "#define packageInfo" with "#packInfo"
- "#define macro" with "#macroDef"