Creating a macro package loader using "packageToMacro.bat"

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Creating a macro package loader using "packageToMacro.bat"

#1 Post by penpen » 30 Aug 2014 16:31

Creating a macro package loader using "packageToMacro.bat"

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"
Last edited by penpen on 29 Jul 2016 11:12, edited 4 times in total.

aGerman
Expert
Posts: 4705
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Creating a macro package loader using "packageToMacro.ba

#2 Post by aGerman » 31 Aug 2014 03:15

That's quite helpful :D I'll do some tests with existing macros later.

Although there's an issue if you run it in a directory with spaces that you should fix. It happened in packageToMacro.bat where I got an error from FC ...

Regards
aGerman

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Creating a macro package loader using "packageToMacro.ba

#3 Post by penpen » 31 Aug 2014 04:38

I've fixed this issue (:oops: i always forget to test on spaced file-/pathnames...).

penpen

aGerman
Expert
Posts: 4705
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Creating a macro package loader using "packageToMacro.ba

#4 Post by aGerman » 31 Aug 2014 08:35

It seems to work fine now (with the limitations you mentioned in the code). Well done!

Now please rewrite the code to convert a conventional sub routine to a macro with parameter passing and the whole shebang. (just kidding :lol: but wouldn't that be awesome?)

Regards
aGerman

penpen
Expert
Posts: 2009
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Creating a macro package loader using "packageToMacro.ba

#5 Post by penpen » 01 Sep 2014 05:21

I've removed the memory mapping, because all lines have to fit in an environment variables in escaped form anyway.
In addition i've removed unneeded escaping in the macro comment.

The program is now up to 5 times faster on bigger packages:
The conversion of my float package now needs only ~33 seconds.

penpen

Post Reply