Page 1 of 2

New Function Template - return ANY string safely and easily

Posted: 05 Jun 2011 00:00
by dbenham
Note: the code in this first post is out-of-date. Refer to the top post on the 2nd page of this thread for info on updated code. The post I am referring to is dated 23 Jun 2011 18:12

Summary: By packaging jeb's safe return technique with Ed Dyreen's concept of macros with arguments, I have developed a small macro library that enables simple conversion of any function into one that supports the return of any string supported by DOS. These converted functions are reliable even when called while delayed expansion is enabled.

The Problem: The function template provided by dostips.com works great for most "normal" situations. It is able to return numbers and simple strings accross the ENDLOCAL boundry. But if fails under the following circumstances:

1) The return string contains special characters such as ^ | < > & etc. Special characters must be either enclosed in quotes or escaped (but not both) if they are to be returned properly.

2) The function was called while delayed expansion was enabled and the return string contains ^ and/or ! The exclamation point must be escaped, even if it is within quotes, and the ^ must be escaped twice.

3) The return string contains carriage return <CR> or line feed <LF>. These are not normally found in DOS strings, but it is possible, and they can be extremely useful (especially <LF>). These characters require special handling when making assignments accross the ENDLOCAL boundry.

Writing generic code that knows how to deal with all three situations above is extremely tricky. I didn't think it was even possible, but jeb contributed amazing techniques to New functions :chr :asc :str2hex :hex2str that appear to be bullet proof.

The only problem left was how to easily apply jeb's safe return technique to any functions that require it. It is a significant amount of code.

I briefly tried to encapsulate the code into a function of its own, but quickly ran into a roadblock - A function cannot perform an ENDLOCAL for a SETLOCAL that was executed before the function was called. So jeb's technique could never be successfully deployed as functions.

The Solution: After first seeing Ed Dyreen's macros in the "SET /a -- Random Number?" post (why are we limited to 2 URLs per post? :cry: ), and experimenting with the syntax in my own Batch "macros" with arguments post, I realized I finally had the tools to package jeb's safe return technique as a convenient, easy to deploy macro library.

The function upgrade only requires calling a simple initialization macro at the top of the function, and replacing the ENDLOCAL block at function end with one or two additional macro calls. The macros that return a single value only add 7 msec to the function processing time.

I plan on a more comprehensive discussion of this library in my batch thread in the near future. But I wanted to make these macros available to function developers as soon as possible.

Included in this post are 4 files - each of which is self documenting:

1) Macro_RtnLib.bat - the focus of this post

2) Macro_TimerLib.bat - an auxilliary macro library that supports timing of batch operations - used in the following two demos to show the performance impact of adding the new functionality.

3) ucase.bat - a demonstration showing how to create a function that returns a single string using the macro library. I chose to convert a useful existing function :toUpper. I renamed the modified versions to :ucase to differentiate them from the original. Sample output is included after the code.

4) rtnTwoStrings.bat - a demonstration showing how to create a function that returns multiple string values using the macro library. I didn't have a useful existing function returning multiple values. So I created a very silly function for demonstration purposes only. I did not bother to include sample output for this file because it is so similar to the output of ucase.bat


Macro_RtnLib.bat - this file contains full documentation

Code: Select all

@echo off
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This library defines macros and variables useful for creating other macros
:: and functions. Of particular note are macros that enable the return of any
:: string value(s) across the ENDLOCAL border. Routines built with these
:: macros are safe to call even when delayed expansion is enabled.
::
:: The library is designed to be installed in a directory in your PATH.
:: Any batch file that requires it can include it by simply placing the
:: following line of code at the top before any SETLOCAL:
::
::   IF NOT DEFINED macro\load.Macro_RtnLib CALL Macro_RtnLib
::
:: In this way the library becomes resident in your command shell environment
:: where it is available to any batch file that may need it. The IF condition
:: prevents unneccessary reloads of the same library.
::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: FUNCTION TEMPLATES
::
:: Function returning a single value that may contain any characters
:: Echoes the return value if RtnVar not specified
:: -------------------------------
:: :func inputVar [RtnVar]
::   %macro_InitFcnRtn%
::   setlocal
::   {do whatever you need to do}
::   set rtnValue={your return value}
::   %macro_Call% ("%errorlevel% 1 rtnValue %~2") %macro.AnyRtn1%
::   %macro_RtnAny%
:: exit /b
::
::
:: Function returning a single value that may contain any characters
:: EXCEPT <carriage return> or <line feed>
:: Echoes the return value if RtnVar not specified
:: -------------------------------
:: :func inputVar [RtnVar]
::   %macro_InitFcnRtn%
::   setlocal
::   {do whatever you need to do}
::   set rtnValue={your return value}
::   %macro_Call% ("%errorlevel% 1 rtnValue %~2") %macro.Rtn1%
:: exit /b
::
::
:: Function returning multiple values that may contain any characters
:: -------------------------------
:: :func inputVar RtnVar1 RtnVar2
::   %macro_InitFcnRtn%
::   setlocal
::   {do whatever you need to do}
::   set rtnVal1={your first return value}
::   set rtnVal2={your second return value}
::   %macro_Call% ("%errorlevel% 1 rtnVal1:%~2,rtnVal2:%~3") %macro.AnyRtnN%
::   %macro_RtnAny%
:: exit /b
::
::
:: Function returning multiple values that may contain any characters
:: EXCEPT <carriage return> or <line feed>
:: -------------------------------
:: :func inputVar RtnVar1 RtnVar2
::   %macro_InitFcnRtn%
::   setlocal
::   {do whatever you need to do}
::   set rtnVal1={your first return value}
::   set rtnVal2={your second return value}
::   %macro_Call% ("%errorlevel% 1 rtnVal1:%~2,rtnVal2:%~3") %macro.RtnN%
:: exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::


::define a Carriage Return string, only useable as !CR!
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"

::define a Line Feed (newline) string (normally only used as !LF!)
set LF=^


::Above 2 blank lines are required - do not remove

::define a Line Feed string that can be used as %xLF%
set ^"xLF=^^^%LF%%LF%^%LF%%LF%"

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

::define FOR /F options that preserve the entire line
set macro_ForEntireLine=^^^^^^^"eol^^^^=^^^^^^^%LF%%LF%^%LF%%LF%^^^%LF%%LF%^%LF%%LF%^^^^ delims^^^^=^^^^^^^"

:: A simple macro used to call macros with arguments
:: Usage:
::
::    %macro_call% ("arg1 arg2 arg3...") %macro.macroName%
::
set macro_Call=for /f "tokens=1-26" %%a in

:: A simple macro used at the top of a macro definition to prepare
:: for a return by any of the Rtn macros.
set macro_InitRtn=setlocal^^^&set "NotDelayed=!"^^^&set "macro_inFcn="

:: A simple macro used at the top of a function definition to prepare
:: for a return by any of the Rtn macros.
set macro_InitFcnRtn=setlocal^&set "NotDelayed=!"^&set "macro_inFcn=true"

:: A simple macro for Rtn macro internal use
set "macro_RtnAnyPrefix=for /f "tokens=1-3" %%1 in ("!replace!") do for %%4 in ("!LF!") do "

::macro.AnyRtn1  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]
::
::  Returns the contents of variable ValueVar across the ENDLOCAL border at the
::  end of a function or macro. The number of ENDLOCAL executions is controlled
::  by the EndLocalCnt which should match the number of times SETLOCAL was used
::  within the calling function/macro.
::
::  The return value is stored in RtnVar
::  or the value is printed if RtnVar is not specified.
::  The ERRORLEVEL is set to ErrLvl
::
::  Numeric values ErrLvl and EndLocalCnt may be passed using any expression
::  supported by SET /A.
::
::  This macro can return a string containing any combination of characters
::  supported by DOS, and the macro works regardless whether the target return
::  environment has enabled or disabled delayed expansion.
::
::  In order for a function to use AnyRtn1, the calling function must start with
::  %%macro_InitFcnRtn%% at the top, and %%macro_RtnAny%% must immediately follow
::  the call to %%macro.AnyRtn1%%.
::
::  In order for a macro to use AnyRtn1, the calling macro must start with
::  %%macro_InitRtn%% at the top. Any call to a macro that uses AnyRtn1 must be
::  followed by %%macro_RtnAny%%. However, the macro call and %%macro_RtnAny%%
::  must not share a common statement block.
::
set macro.AnyRtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.AnyRtn1.ErrLvl=(%%~a), macro.AnyRtn1.EndLocalCnt=(%%~b)"%\n%
  set "rtn=!%%~c!"%\n%
  set ^"replace=%% ^"^"^" !CR!!CR!^"%\n%
  set "macro_RtnAny=!macro_RtnAnyPrefix!endlocal&endlocal"%\n%
  for /l %%N in (1,1,!macro.AnyRtn1.EndLocalCnt!) do set "macro_RtnAny=!macro_RtnAny!&endlocal"%\n%
  if "%%~d" equ "" (echo:!rtn!) else (%\n%
    if defined rtn (%\n%
      set "rtn=!rtn:%%=%%~1!"%\n%
      set ^"rtn=!rtn:^"=%%~2!^"%\n%
      if defined CR for %%A in ("!CR!") do set "rtn=!rtn:%%~A=%%~3!"%\n%
      for %%A in ("!LF!") do set "rtn=!rtn:%%~A=%%~4!"%\n%
      if not defined NotDelayed (%\n%
        set "rtn=!rtn:^=^^!"%\n%
        call set "rtn=%%^rtn:^!=""^!%%" ! %\n%
        set "rtn=!rtn:""=^!"%\n%
      )%\n%
    )%\n%
    set "macro_RtnAny=!macro_RtnAny!&set "%%~d=!rtn!" ^!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro_RtnAny=!macro_RtnAny!&exit /b !macro.AnyRtn1.ErrLvl!"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" == "1" (%\n%
      set "macro_RtnAny=!macro_RtnAny!&(2>nul set =)"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" neq "0" (%\n%
      set "macro_RtnAny=!macro_RtnAny!&cmd /c exit !macro.AnyRtn1.ErrLvl!"%\n%
  )%\n%
)

::macro.AnyRtnN  ErrLvl  EndLocalCnt  ValueVar1:RtnVar1[,ValueVar2:RtnVar2]...
::
::  Returns the contents of multiple variables across the ENDLOCAL border at
::  the end of a function or macro. The number of ENDLOCAL executions is
::  controlled by the EndLocalCnt which should match the number of times
::  SETLOCAL was used within the calling function/macro. The errorlevel is
::  set to ErrLvl.
::
::  A pair of variable names must be specified for each output value - the
::  name of the variable containing the value followed by a colon followed
::  by the name of the variable that is to receive the value. Multiple pairs
::  are delimited by commas. The list of outputs cannot contain any spaces,
::  and the variable names cannot contain asterisk (*), question mark (?),
::  colon (:) or comma (,).
::
::  Numeric values ErrLvl and EndLocalCnt may be passed using any expression
::  supported by SET /A.
::
::  This macro can return strings containing any combination of characters
::  supported by DOS, and the macro works regardless whether the target return
::  environment has enabled or disabled delayed expansion.
::
::  In order for a function to use AnyRtn1, the calling function must start with
::  %%macro_InitFcnRtn%% at the top, and %%macro_RtnAny%% must immediately follow
::  the call to %%macro.AnyRtn1%%.
::
::  In order for a macro to use AnyRtnN, the calling macro must start with
::  %%macro_InitRtn%% at the top. Any call to a macro that uses AnyRtnN must be
::  followed by %%macro_RtnAny%%. However, the macro call and %%macro_RtnAny%%
::  must not share a common statement block.
::
set macro.AnyRtnN=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.AnyRtn1.ErrLvl=(%%~a), macro.AnyRtn1.EndLocalCnt=(%%~b)"%\n%
  set ^"replace=%% ^"^"^" !CR!!CR!^"%\n%
  set "macro_RtnAny=!macro_RtnAnyPrefix!endlocal&endlocal"%\n%
  for /l %%N in (1,1,!macro.AnyRtn1.EndLocalCnt!) do set "macro_RtnAny=!macro_RtnAny!&endlocal"%\n%
  for %%c in (%%~c) do for /f "tokens=1,2 eol=: delims=:" %%c in ("%%c") do (%\n%
    set "rtn=!%%~c!"%\n%
    if defined rtn (%\n%
      set "rtn=!rtn:%%=%%~1!"%\n%
      set ^"rtn=!rtn:^"=%%~2!^"%\n%
      if defined CR for %%A in ("!CR!") do set "rtn=!rtn:%%~A=%%~3!"%\n%
      for %%A in ("!LF!") do set "rtn=!rtn:%%~A=%%~4!"%\n%
      if not defined NotDelayed (%\n%
        set "rtn=!rtn:^=^^!"%\n%
        call set "rtn=%%^rtn:^!=""^!%%" ! %\n%
        set "rtn=!rtn:""=^!"%\n%
      )%\n%
    )%\n%
    set "macro_RtnAny=!macro_RtnAny!&set "%%~d=!rtn!" ^!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "macro_RtnAny=!macro_RtnAny!&exit /b !macro.AnyRtn1.ErrLvl!"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" == "1" (%\n%
      set "macro_RtnAny=!macro_RtnAny!&(2>nul set =)"%\n%
  ) else if "!macro.AnyRtn1.ErrLvl!" neq "0" (%\n%
      set "macro_RtnAny=!macro_RtnAny!&cmd /c exit !macro.AnyRtn1.ErrLvl!"%\n%
  )%\n%
)

::macro.Rtn1  ErrLvl  EndLocalCnt  ValueVar  [RtnVar]
::
::  Returns the contents of variable ValueVar across the ENDLOCAL border at the
::  end of a function or macro. The number of ENDLOCAL executions is controlled
::  by the EndLocalCnt which should match the number of times SETLOCAL was used
::  within the calling function/macro.
::
::  The return value is stored in RtnVar
::  or the value is printed if RtnVar is not specified.
::  The ERRORLEVEL is set to ErrLvl
::
::  Numeric values ErrLvl and EndLocalCnt may be passed using any expression
::  supported by SET /A.
::
::  This macro can return a string containing any combination of characters
::  supported by DOS, except for 0x0A <Line Feed> or 0x0C <Carriage Return>.
::  The macro works regardless whether the target return environment has
::  enabled or disabled delayed expansion.
::
::  In order for a function to use Rtn1, the calling function must start with
::  %%macro_InitFcnRtn%% at the top.
::
::  In order for a macro to use Rtn1, the calling macro must start with
::  %%macro_InitRtn%% at the top.
::
set macro.Rtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Rtn1.ErrLvl=(%%~a), macro.Rtn1.EndLocalCnt=(%%~b)+2"%\n%
  set "rtn=!%%~c!"%\n%
  if defined macro_inFcn (%\n%
      set "errCmd=exit /b !macro.Rtn1.ErrLvl!"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (%\n%
      set "errCmd=rem"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "1" (%\n%
      set "errCmd=set ="%\n%
  ) else (%\n%
      set "errCmd=cmd /c exit !macro.Rtn1.ErrLvl!"%\n%
  )%\n%
  if "%%~d" equ "" (echo:!rtn!) else if defined rtn (%\n%
    if not defined NotDelayed (%\n%
      set "rtn=!rtn:^=^^!"%\n%
      set "rtn=!rtn:"=""Q!^"%\n%
      call set "rtn=%%^rtn:^!=""E^!%%" ! %\n%
      set "rtn=!rtn:""E=^!"%\n%
      set "rtn=!rtn:""Q="!^"%\n%
    )%\n%
    set ^"var=!var:^"=^^^"!^"%\n%
    set "var=!var:&=^&!"%\n%
    set "var=!var:|=^|!"%\n%
    set "var=!var:<=^<!"%\n%
    set "var=!var:>=^>!"%\n%
    set "var=!var:(=^(!"%\n%
    set "var=!var:)=^)!"%\n%
  )%\n%
  for /f "delims=" %%e in ("!errCmd!") do for /f %macro_ForEntireLine% %%v in ("!rtn!") do (%\n%
    for /l %%n in (1,1,!macro.Rtn1.EndLocalCnt!) do endlocal%\n%
    if "%%~d" neq "" set "%%~d=%%v" !%\n%
    %%e 2^>nul%\n%
  )%\n%
)

::macro.RtnN  ErrLvl  EndLocalCnt  ValueVar1:RtnVar1[,ValueVar2:RtnVar2]...
::
::  Returns the contents of multiple variables across the ENDLOCAL border at
::  the end of a function or macro. The number of ENDLOCAL executions is
::  controlled by the EndLocalCnt which should match the number of times
::  SETLOCAL was used within the calling function/macro. The errorlevel is
::  set to ErrLvl.
::
::  A pair of variable names must be specified for each output value - the
::  name of the variable containing the value followed by a colon followed
::  by the name of the variable that is to receive the value. Multiple pairs
::  are delimited by commas. The list of outputs cannot contain any spaces,
::  and the variable names cannot contain asterisk (*), question mark (?),
::  colon (:) or comma (,).
::
::  Numeric values ErrLvl and EndLocalCnt may be passed using any expression
::  supported by SET /A.
::
::  This macro can return strings containing any combination of characters
::  supported by DOS, except for 0x0A <Line Feed> or 0x0C <Carriage Return>.
::  The macro works regardless whether the target return environment has
::  enabled or disabled delayed expansion.
::
::  In order for a function to use RtnN, the calling function must start with
::  %%macro_InitFcnRtn%% at the top.
::
::  In order for a macro to use RtnN, the calling macro must start with
::  %%macro_InitRtn%% at the top.
::
set macro.RtnN=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Rtn1.ErrLvl=(%%~a), macro.Rtn1.EndLocalCnt=(%%~b)+2"%\n%
  set "cmd="%\n%
  for /l %%n in (1,1,!macro.Rtn1.EndLocalCnt!) do set "cmd=!cmd!endlocal!lf!"%\n%
  for %%c in (%%~c) do for /f "tokens=1,2 eol=: delims=:" %%c in ("%%c") do (%\n%
    set "rtn=!%%~c!"%\n%
    if defined rtn (%\n%
      if not defined NotDelayed (%\n%
        set "rtn=!rtn:^=^^!"%\n%
        set "rtn=!rtn:"=""Q!^"%\n%
        call set "rtn=%%^rtn:^!=""E^!%%" ! %\n%
        set "rtn=!rtn:""E=^!"%\n%
        set "rtn=!rtn:""Q="!^"%\n%
      )%\n%
      set ^"var=!var:^"=^^^"!^"%\n%
      set "var=!var:&=^&!"%\n%
      set "var=!var:|=^|!"%\n%
      set "var=!var:<=^<!"%\n%
      set "var=!var:>=^>!"%\n%
      set "var=!var:(=^(!"%\n%
      set "var=!var:)=^)!"%\n%
    )%\n%
    set "cmd=!cmd!set "%%~d=!rtn!"^!!lf!"%\n%
  )%\n%
  if defined macro_inFcn (%\n%
      set "cmd=!cmd!exit /b !macro.Rtn1.ErrLvl!!lf!"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (%\n%
      rem errorlevel set to 0 by default%\n%
  ) else (%\n%
      set "cmd=!cmd!cmd /c exit !macro.Rtn1.ErrLvl!!lf!"%\n%
  )%\n%
  for /f "delims=" %%v in ("!cmd!") do %%v%\n%
)

set macro\load.%~n0=1



Macro_TimerLib.bat - documentation embedded within file.

Code: Select all

@echo off
:: This batch file will fail if called while delayed expansion is enabled.
::
:: This batch file defines macros that are usefull for performing timing
:: operations within a batch file
::
:: Typical Usage:
::
::   if not defined macro\load.macro_timer call macro_timer
::   ... <do whatever>...
::   %macro_call% ("t1") %macro.getTime%
::   ... <code to be timed goes here> ...
::   %macro_call% ("t2") %macro.getTime%
::   %macro_call% ("t1 t2 tm") %macro.diffTime%
::   echo It took %tm% seconds to execute

::define a Line Feed (newline) string (normally only used as !LF!)
set LF=^


::Above 2 blank lines are required - do not remove

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

:: A simple macro used to call macros with arguments
:: Usage:
::
::    %macro_call% ("arg1 arg2 arg3...") %macro.macroName%
::
set macro_Call=for /f "tokens=1-26" %%a in

::set macro.args.GetTime=  [RtnVar]
::
::  Computes the current time of day measured as 1/100th seconds past midnight
::
::  Sets RtnVar = result
::  or displays result if RtnVar not specified ("""")
::
::
set macro.GetTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "t=0"%\n%
  for /f "tokens=1-4 delims=:." %%A in ("!time: =0!") do set /a "t=(((1%%A*60)+1%%B)*60+1%%C)*100+1%%D-36610100"%\n%
  for %%v in (!t!) do endlocal^&if "%%~a" neq "" (set "%%~a=%%v") else echo:%%v%\n%
)

::macro.args.DiffTime=  StartTime  StopTime  [RtnVar]
::
::  Computes the elapsed time between StartTime and
::  StopTime and formats the result as HH:MM:SS.DD
::
::  StartTime and StopTime must be integral values
::  representing 1/100th seconds past midnight. These
::  values are typically gotten via calls to macro.GetTime
::
::  Sets RtnVar=result
::  or displays result if RtnVar not specified
::
::  DiffTime will properly handle elapsed times that span
::  midnight. However it cannot handle times that
::  reach 24 hours or more.
::
::  Note that StartTime and StopTime may be passed using
::  any numeric expression supported by SET /A%xLF%
::
set macro.DiffTime=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "DD=(%%~b)-(%%~a)"%\n%
  if !DD! lss 0 set /a "DD+=24*60*60*100"%\n%
  set /a "HH=DD/360000, DD-=HH*360000, MM=DD/6000, DD-=MM*6000, SS=DD/100, DD-=SS*100"%\n%
  if "!HH:~1!"=="" set "HH=0!HH!"%\n%
  if "!MM:~1!"=="" set "MM=0!MM!"%\n%
  if "!SS:~1!"=="" set "SS=0!SS!"%\n%
  if "!DD:~1!"=="" set "DD=0!DD!"%\n%
  for %%v in (!HH!:!MM!:!SS!.!DD!) do endlocal^&if "%%~c" neq "" (set "%%~c=%%v") else echo:%%v%\n%
)

set macro\load.%~n0=1




WernerGg discovered that the :toLower, :toUpper and toCamelCase have a serious bug in that they may fail depending on the name of the string variable that is passed in. (see the toLower Name dependent? post). I based the :ucase functions on a modified version of his solution to the problem. The ucase function capabable of returning any string is significantly faster than the original bugged version of :toUpper!

ucase.bat: This demonstrates some of the potential problems returning strings, and shows how the macros solve the problems. Timings of the various functions are included at the end.

Code: Select all

@echo off
cls

if not defined macro\load.macro_RtnLib   call macro_RtnLib
if not defined macro\load.macro_TimerLib call macro_TimerLib

setlocal disableDelayedExpansion
set "state=DISABLE"

:top
set simpleTest=a simple test
set aTestString=variable names should not matter
set theAmpersandTest="This & that " ^& the other thing
set caretTest=Square area=width^^2 ^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"
set exclaimTest=Hello world! No answer? What a drag!
set excitedCaretTest=Square area=width^^2 ^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"
set lineFeedTest=line1%xLF%line2

setlocal enableDelayedExpansion
set xxxxxx_carriageReturnTest=the!CR!answer

setlocal %state%DelayedExpansion

for %%a in (
  simpleTest
  aTestString
  theAmpersandTest
  caretTest
  exclaimTest
  excitedCaretTest
  xxxxxx_carriageReturnTest
  lineFeedTest
) do (
  echo ----------------------------------
  echo Delayed expansion is %state%D
  echo:
  set %%a
  echo:

  echo testing :ucase1
  call :ucase1 %%a result
  set result
  echo:

  echo testing :ucase2
  call :ucase2 %%a result
  set result
  echo:

  echo testing :ucase3
  call :ucase3 %%a result
  set result
  echo:

  echo testing :toUpper
  call :toUpper %%a
  set %%a
  echo:
)

if "%state%"=="ENABLE" goto :continue
endlocal
endlocal
set "state=ENABLE"
goto :top

:continue
echo ============================================
set string=test
for %%c in (toUpper ucase1 ucase2 ucase3) do (
  %macro_call% ("t1") %macro.getTime%
  for /l %%n in (1,1,100) do call :%%c string result
  %macro_call% ("t2") %macro.getTime%
  %macro_call% ("t1 t2 result") %macro.diffTime%
  echo %%c time x 100 = !result!
)

exit /b
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: FUNCTION DEFINITIONS
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

:toUpper str -- converts lowercase character to uppercase
::           -- str [in,out] - valref of string variable to be converted
:$created 20060101 :$changed 20080219 :$categories StringManipulation
:$source http://www.dostips.com
if not defined %~1 EXIT /b
for %%a in ("a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"
            "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"
            "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"
            "ö=Ö" "ü=Ü") do (
    call set %~1=%%%~1:%%~a%%
)
EXIT /b


:ucase1  strVar  [rtnVar]
::
:: Fixes variable name beginning with lower case "a" bug
:: Echoes result if rtnVar not specified
::
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  if defined str for %%a in (
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"
    "ö=Ö" "ü=Ü"
  ) do set "str=!str:%%~a!"
  ( endlocal
     if "%~2" neq "" (set %~2=%str%) else echo %str%
  )
exit /b


:ucase2  strVar  [rtnVar]
::
:: Fixes variable name beginning with lower case "a" bug
:: Echoes result if rtnVar not specified
::
:: Output can now contain any combination of characters except
:: <carriage return> or <line feed>
::
:: Works the same if called while delayed expansion is enabled or disabled
::
  %macro_InitFcnRtn%
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  if defined str for %%a in (
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"
    "ö=Ö" "ü=Ü"
  ) do set "str=!str:%%~a!"
  %macro_Call% ("!errorlevel! 1 str %~2") %macro.Rtn1%
exit /b


:ucase3  strVar  [rtnVar]
:
: Fixes variable name beginning with lower case "a" bug
: Echoes result if rtnVar not specified
:
: Output can now contain absolutely any combination of characters
: including <carriage return> and <line feed>
:
: Works the same if called while delayed expansion is enabled or disabled
:
  %macro_InitFcnRtn%
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  if defined str for %%a in (
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"
    "ö=Ö" "ü=Ü"
  ) do set "str=!str:%%~a!"
  %macro_Call% ("!errorlevel! 1 str %~2") %macro.AnyRtn1%
  %macro_RtnAny%
exit /b


Results of ucase.bat: The timing results at the end demonstrate that there
is very little overhead in calling the safe return macros.

Code: Select all

----------------------------------
Delayed expansion is DISABLED

simpleTest=a simple test

testing :ucase1
result=A SIMPLE TEST

testing :ucase2
result=A SIMPLE TEST

testing :ucase3
result=A SIMPLE TEST

testing :toUpper
simpleTest=A SIMPLE TEST

----------------------------------
Delayed expansion is DISABLED

aTestString=variable names should not matter

testing :ucase1
result=VARIABLE NAMES SHOULD NOT MATTER

testing :ucase2
result=VARIABLE NAMES SHOULD NOT MATTER

testing :ucase3
result=VARIABLE NAMES SHOULD NOT MATTER

testing :toUpper
aTestString="ü=Ü"TestString:ü=Ü

----------------------------------
Delayed expansion is DISABLED

theAmpersandTest="This & that " & the other thing

testing :ucase1
'THE' is not recognized as an internal or external command,
operable program or batch file.
result="THIS & THAT "

testing :ucase2
result="THIS & THAT " & THE OTHER THING

testing :ucase3
result="THIS & THAT " & THE OTHER THING

testing :toUpper
theAmpersandTest="This & that " & the other thing

----------------------------------
Delayed expansion is DISABLED

caretTest=Square area=width^2 ^^ ^^^ ^^^^ ^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"

testing :ucase1
result=SQUARE AREA=WIDTH2 ^ ^ ^^ ^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase2
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase3
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :toUpper
caretTest=SQUARE AREA=WIDTH2     "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

----------------------------------
Delayed expansion is DISABLED

exclaimTest=Hello world! No answer? What a drag!

testing :ucase1
result=HELLO WORLD! NO ANSWER? WHAT A DRAG!

testing :ucase2
result=HELLO WORLD! NO ANSWER? WHAT A DRAG!

testing :ucase3
result=HELLO WORLD! NO ANSWER? WHAT A DRAG!

testing :toUpper
exclaimTest=HELLO WORLD! NO ANSWER? WHAT A DRAG!

----------------------------------
Delayed expansion is DISABLED

excitedCaretTest=Square area=width^2 ^^ ^^^ ^^^^ ^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"

testing :ucase1
result=SQUARE AREA=WIDTH2 ^ ^ ^^ ^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase2
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase3
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :toUpper
excitedCaretTest=SQUARE AREA=WIDTH2     "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

----------------------------------
Delayed expansion is DISABLED

xxxxxx_carriageReturnTest=the
answer

testing :ucase1
result=THEANSWER

testing :ucase2
result=THE
ANSWER

testing :ucase3
result=THE
ANSWER

testing :toUpper
xxxxxx_carriageReturnTest=THEANSWER

----------------------------------
Delayed expansion is DISABLED

lineFeedTest=line1
line2

testing :ucase1
'LINE2' is not recognized as an internal or external command,
operable program or batch file.
'LINE2' is not recognized as an internal or external command,
operable program or batch file.
result=LINE1

testing :ucase2
result=LINE1

testing :ucase3
result=LINE1
LINE2

testing :toUpper
lineFeedTest=LINE1

----------------------------------
Delayed expansion is ENABLED

simpleTest=a simple test

testing :ucase1
result=A SIMPLE TEST

testing :ucase2
result=A SIMPLE TEST

testing :ucase3
result=A SIMPLE TEST

testing :toUpper
simpleTest=A SIMPLE TEST

----------------------------------
Delayed expansion is ENABLED

aTestString=variable names should not matter

testing :ucase1
result=VARIABLE NAMES SHOULD NOT MATTER

testing :ucase2
result=VARIABLE NAMES SHOULD NOT MATTER

testing :ucase3
result=VARIABLE NAMES SHOULD NOT MATTER

testing :toUpper
aTestString="ü=Ü"TestString:ü=Ü

----------------------------------
Delayed expansion is ENABLED

theAmpersandTest="This & that " & the other thing

testing :ucase1
'THE' is not recognized as an internal or external command,
operable program or batch file.
result="THIS & THAT "

testing :ucase2
result="THIS & THAT " & THE OTHER THING

testing :ucase3
result="THIS & THAT " & THE OTHER THING

testing :toUpper
theAmpersandTest="This & that " & the other thing

----------------------------------
Delayed expansion is ENABLED

caretTest=Square area=width^2 ^^ ^^^ ^^^^ ^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"

testing :ucase1
result=SQUARE AREA=WIDTH2   ^ ^^ "CUBE VOLUME=WIDTH3 ^ ^ ^^"

testing :ucase2
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase3
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :toUpper
caretTest=SQUARE AREA=WIDTH2     "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

----------------------------------
Delayed expansion is ENABLED

exclaimTest=Hello world! No answer? What a drag!

testing :ucase1
result=HELLO WORLD

testing :ucase2
result=HELLO WORLD! NO ANSWER? WHAT A DRAG!

testing :ucase3
result=HELLO WORLD! NO ANSWER? WHAT A DRAG!

testing :toUpper
exclaimTest=HELLO WORLD! NO ANSWER? WHAT A DRAG!

----------------------------------
Delayed expansion is ENABLED

excitedCaretTest=Square area=width^2 ^^ ^^^ ^^^^ ^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"

testing :ucase1
result=SQUARE AREA=WIDTH2   ^ ^^ "CUBE VOLUME=WIDTH3 ^ ^ ^^"

testing :ucase2
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :ucase3
result=SQUARE AREA=WIDTH^2 ^^ ^^^ ^^^^ ^^^^^^^^ "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

testing :toUpper
excitedCaretTest=SQUARE AREA=WIDTH2     "CUBE VOLUME=WIDTH^3 ^^ ^^^ ^^^^!"

----------------------------------
Delayed expansion is ENABLED

xxxxxx_carriageReturnTest=the
answer

testing :ucase1
result=THEANSWER

testing :ucase2
result=THEANSWER

testing :ucase3
result=THE
ANSWER

testing :toUpper
xxxxxx_carriageReturnTest=THEANSWER

----------------------------------
Delayed expansion is ENABLED

lineFeedTest=line1
line2

testing :ucase1
'LINE2' is not recognized as an internal or external command,
operable program or batch file.
'LINE2' is not recognized as an internal or external command,
operable program or batch file.
result=LINE1

testing :ucase2
result=LINE1

testing :ucase3
result=LINE1
LINE2

testing :toUpper
lineFeedTest=LINE1

============================================
toUpper time x 100 = 00:00:07.30
ucase1 time x 100 = 00:00:00.90
ucase2 time x 100 = 00:00:01.58
ucase3 time x 100 = 00:00:01.65


rtnTwoStrings.bat - Uses a silly function to demonstrate how to return multiple values across the ENDLOCAL border.

Code: Select all

@echo off
cls

if not defined macro\load.macro_RtnLib call macro_RtnLib
if not defined macro\load.macro_TimerLib call macro_TimerLib

setlocal disableDelayedExpansion
set "state=DISABLE"

:top
set simpleTest=a simple test
set theAmpersandTest="This & that " ^& the other thing
set caretTest=Square area=width^^2 ^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"
set exclaimTest=Hello world! No answer? What a drag!
set excitedCaretTest=Square area=width^^2 ^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^^^^ "cube volume=width^3 ^^ ^^^ ^^^^!"
set lineFeedTest=line1%xLF%line2

setlocal enableDelayedExpansion
set xxxxxx_carriageReturnTest=the!CR!answer

setlocal %state%DelayedExpansion

for %%a in (
  simpleTest
  aTestString
  theAmpersandTest
  caretTest
  exclaimTest
  excitedCaretTest
  xxxxxx_carriageReturnTest
  lineFeedTest
) do (
  echo ----------------------------------
  echo Delayed expansion is %state%D
  echo:
  set %%a
  echo:

  echo testing :RtnTwoStrings1
  call :RtnTwoStrings1 %%a rtn1 rtn2
  set rtn
  echo:

  echo testing :RtnTwoStrings2
  call :RtnTwoStrings2 %%a rtn1 rtn2
  set rtn
  echo:

  echo testing :RtnTwoStrings3
  call :RtnTwoStrings3 %%a rtn1 rtn2
  set rtn
  echo:
)

if "%state%"=="ENABLE" goto :continue
endlocal
endlocal
set "state=ENABLE"
goto :top

:continue
echo ============================================
set string=test
for %%c in (RtnTwoStrings1 RtnTwoStrings2 RtnTwoStrings3) do (
  %macro_call% ("t1") %macro.getTime%
  for /l %%n in (1,1,100) do call :%%c string rtn1 rtn2
  %macro_call% ("t2") %macro.getTime%
  %macro_call% ("t1 t2 result") %macro.diffTime%
  echo %%c time x 100 = !result!
)

exit /b


:RtnTwoStrings1 StrVar Rtn1Var Rtn2Var
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  set "rtn1="Part1:!str!"%
  set "rtn2="!str!:Part2"
  set err=3
  (endlocal
    set "%~2=%rtn1%
    set "%~3=%rtn2%
    exit /b %err%
  )
exit /b


:RtnTwoStrings2 StrVar Rtn1Var Rtn2Var
  %macro_InitFcnRtn%
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  set "rtn1="Part1:!str!"%
  set "rtn2="!str!:Part2"
  %macro_call% ("err 1 rtn1:%~2,rtn2:%~3") %macro.RtnN%
exit /b


:RtnTwoStrings3 StrVar Rtn1Var Rtn2Var
  %macro_InitFcnRtn%
  setlocal enableDelayedExpansion
  set "str=!%~1!"
  set "rtn1="Part1:!str!"%
  set "rtn2="!str!:Part2"
  %macro_call% ("err 1 rtn1:%~2,rtn2:%~3") %macro.AnyRtnN%
  %macro_RtnAny%
exit /b



Edits:

18-June-2011 - Fixed bug in macro_Rtn1 in file Macro_RtnLib.bat: Macro was generating a syntax error when called without optional RtnVar.

28-Sep-2011 - See note at top of this thread


Dave Benham

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 07:46
by Ed Dyreen
'
G R E A T . W O R K 8)

But why do u use the .bat extension instead of .cmd ? I was always told windows handles cmd files better than .bat files .
Or you just want it to be compatible ?

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 17:53
by dbenham
EdDyreen wrote:But why do u use the .bat extension instead of .cmd ? I was always told windows handles cmd files better than .bat files .

I'd never heard of this, so I googled for differences. According to Wikipedia, "the only known difference between .cmd and .bat file execution is that in a .cmd file the ERRORLEVEL variable changes even on a successful command that is affected by Command Extensions (when Command Extensions are enabled), whereas in .bat files the ERRORLEVEL variable changes only upon errors."

I confirmed this difference with the following code:

Code: Select all

@echo off
echo file name = %~nx0
set =
echo errorlevel after SET error = %errorlevel%
set test=junk
echo errorlevel after SET success = %errorlevel%

Output of test.bat:

Code: Select all

file name = test.bat
The syntax of the command is incorrect.
errorlevel after SET error = 1
errorlevel after SET success = 1

Output of test.cmd

Code: Select all

file name = test.cmd
The syntax of the command is incorrect.
errorlevel after SET error = 1
errorlevel after SET success = 0


Dave

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 18:58
by nitt
dbenham wrote:
EdDyreen wrote:But why do u use the .bat extension instead of .cmd ? I was always told windows handles cmd files better than .bat files .

I'd never heard of this, so I googled for differences. According to Wikipedia, "the only known difference between .cmd and .bat file execution is that in a .cmd file the ERRORLEVEL variable changes even on a successful command that is affected by Command Extensions (when Command Extensions are enabled), whereas in .bat files the ERRORLEVEL variable changes only upon errors."

I confirmed this difference with the following code:

Code: Select all

@echo off
echo file name = %~nx0
set =
echo errorlevel after SET error = %errorlevel%
set test=junk
echo errorlevel after SET success = %errorlevel%

Output of test.bat:

Code: Select all

file name = test.bat
The syntax of the command is incorrect.
errorlevel after SET error = 1
errorlevel after SET success = 1

Output of test.cmd

Code: Select all

file name = test.cmd
The syntax of the command is incorrect.
errorlevel after SET error = 1
errorlevel after SET success = 0


Dave


Oh cool, I never knew that. That's neat!

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 19:00
by Cleptography
I'm not sure if I am right here but I also believe that .cmd are used for gp scripts whereas .bat do not run as. My memory may be way off here but I think I remember learning this somewhere awhile back.

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 22:32
by Cleptography
CMD and BAT are to files that are used to automate certain tasks that are repetitive and are used frequently by the user. Creating a CMD or BAT file is just like using a simple programming language with the advantages of the ability to use command line instructions and to execute other applications with or without parameters. CMD and BAT files are very similar to each other with very minor differences. BAT is a very old file type that has been around since the advent of DOS. It was carried over by Microsoft when it developed Windows away from DOS. The CMD file type was developed by Microsoft to be used for the implementation of Windows NT command scripts but is also in use by the newer versions of Windows that are based on Windows NT.

BAT was created to interact with COMMAND.COM, the command interpreter of DOS. Microsoft adopted most of the DOS commands into their new interpreter named CMD. EXE. CMD was created to interface with CMD.EXE and it breaks compatibility with COMMAND.COM. Another key difference is in how they handle the errorlevel variable. When using BAT, this variable is only changed once an actual error occurs and no change in state occurs when the each command executes successfully. This is not true for CMD as the errorlevel variable would still change state even if no errors occur. Programmers should take note of this when creating elaborate scripts as it may cause a little bit of confusion.

Aside from those minor differences, CMD and BAT are identical to each other. Most users who create simple scripts to clear or transfer files around should not encounter any problem. For users of the more recent versions of Windows, BAT and CMD are pretty much interchangeable as CMD.EXE would interpret and execute the commands in both files. Although most users are aware of this fact, a lot of the older people who had a chance to work with DOS and its batch files still use the BAT extension; simply out of habit and familiarity.

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 22:43
by Ed Dyreen

A script that is designed for cmd.exe can be named .cmd to prevent accidental execution on Windows 9x.

Here is a list of cmd.exe features that are not supported by command.com:

* Long filenames (exceeding the 8.3 format)
* Command history
* Tab completion
* Escape character: ^ (Use for: \ & | > < ^)
* Directory stack: PUSHD/POPD
* Integer arithmetic: SET /A i+=1
* Search/Replace/Substring: SET %varname:expression%
* Command substitution: FOR /F (existed before, has been enhanced)
* Functions: CALL :label

Re: New Function Template - return ANY string safely and eas

Posted: 05 Jun 2011 22:56
by Cleptography

"Wikipedia is wrong. Only cmd.exe is used to run batch files, whether they are .cmd or .bat. Vista runs batch files under cmd.exe in exactly the same manner as xp."

Use your brain and stop plagiarizing the internet Ed. :| :? :lol:

http://stackoverflow.com/questions/148968/windows-batch-files-bat-vs-cmd

Re: New Function Template - return ANY string safely and eas

Posted: 06 Jun 2011 06:41
by jeb
Cleptography wrote:Vista runs batch files under cmd.exe in exactly the same manner as xp."


That's obviously wrong 8)

Code: Select all

set "caret=^"
call echo This only crashes on Vista%%caret%%

Btw. Do not wait to long to kill the process, else you can restart your computer :twisted:

jeb

Re: New Function Template - return ANY string safely and eas

Posted: 06 Jun 2011 12:42
by Cleptography
jeb wrote:
Cleptography wrote:Vista runs batch files under cmd.exe in exactly the same manner as xp."


That's obviously wrong 8)

Code: Select all

set "caret=^"
call echo This only crashes on Vista%%caret%%

Btw. Do not wait to long to kill the process, else you can restart your computer :twisted:

jeb

Yes jeb I am aware that it is wrong, why it is in quotes it was pulled from the site that Ed had pulled his information from. OMG is that a little personality that just came through on your part. Everyone kept saying you were a technological robot sent back from time built by Microsoft and that you would only respond in zeros and ones. :lol:

Re: New Function Template - return ANY string safely and eas

Posted: 17 Jun 2011 23:14
by dbenham
I fixed a bug in macro.Rtn1 within file Macro_RtnLib.bat. The macro was generating a syntax error if it was called without the optional RtnVar parameter.

Near the end of the macro, set "%%~d=%%v" !%\n% was changed to if "%%~d" neq "" set "%%~d=%%v" !%\n%

Here is the complete fixed macro:

Code: Select all

set macro.Rtn1=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set /a "macro.Rtn1.ErrLvl=(%%~a), macro.Rtn1.EndLocalCnt=(%%~b)+2"%\n%
  set "rtn=!%%~c!"%\n%
  if defined macro_inFcn (%\n%
      set "errCmd=exit /b !macro.Rtn1.ErrLvl!"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "0" (%\n%
      set "errCmd=rem"%\n%
  ) else if "!macro.Rtn1.ErrLvl!" == "1" (%\n%
      set "errCmd=set ="%\n%
  ) else (%\n%
      set "errCmd=cmd /c exit !macro.Rtn1.ErrLvl!"%\n%
  )%\n%
  if "%%~d" equ "" (echo:!rtn!) else if defined rtn (%\n%
    if not defined NotDelayed (%\n%
      set "rtn=!rtn:^=^^!"%\n%
      set "rtn=!rtn:"=""Q!^"%\n%
      call set "rtn=%%^rtn:^!=""E^!%%" ! %\n%
      set "rtn=!rtn:""E=^!"%\n%
      set "rtn=!rtn:""Q="!^"%\n%
    )%\n%
    set ^"var=!var:^"=^^^"!^"%\n%
    set "var=!var:&=^&!"%\n%
    set "var=!var:|=^|!"%\n%
    set "var=!var:<=^<!"%\n%
    set "var=!var:>=^>!"%\n%
    set "var=!var:(=^(!"%\n%
    set "var=!var:)=^)!"%\n%
  )%\n%
  for /f "delims=" %%e in ("!errCmd!") do for /f %macro_ForEntireLine% %%v in ("!rtn!") do (%\n%
    for /l %%n in (1,1,!macro.Rtn1.EndLocalCnt!) do endlocal%\n%
    if "%%~d" neq "" set "%%~d=%%v" !%\n%
    %%e 2^>nul%\n%
  )%\n%
)

I also edited the original post for this thread to include the fix.

Dave Benham

Re: New Function Template - return ANY string safely and eas

Posted: 20 Jun 2011 15:29
by Acy Forsythe
Dave,

Would you mind giving an example using Macro_RtnLib with your ToLower macro from the Macros with Arguments thread: viewtopic.php?f=3&t=1827

I've got the function part down, I'm just having a hell of a time with the syntax of using this with a Macro...

Code: Select all

set macro_ToLower=do^
  %macro_InitFcnRtn%^
  setlocal enableDelayedExpansion^
  ^&set "str=!%%~a!"^
  ^&(for %%A in (^
     "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"^
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"^
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"^
    "Ö=ö" "Ü=ü"^
   ) do set "str=!str:%%~A!")^
  ^&(for /f "delims=" %%v in ("!str!") do endlocal^&set "%%~a=%%~v")


I can't figure out where the macro call to AnyRtn1 should go...

Re: New Function Template - return ANY string safely and eas

Posted: 20 Jun 2011 16:35
by dbenham
Acy Forsythe wrote:Would you mind giving an example using Macro_RtnLib with your ToLower macro from the Macros with Arguments thread:

I just finished working out some kinks with my macro design last weekend. I will post an example either in my original batch thread or a new thread within the next couple days. I'll try to remember to put a link here as well.

I'm glad to hear there is interest in the macros beyond Ed, Cleptography and myself :!: :D

Dave Benham

Re: New Function Template - return ANY string safely and eas

Posted: 20 Jun 2011 21:03
by Acy Forsythe
Definitely interested :) Just behind the learning curve a little. Until recently I never had the need to go beyond the basics of a straight forward for loop, some conditionals and echo redirection.

Re: New Function Template - return ANY string safely and eas

Posted: 21 Jun 2011 10:05
by Acy Forsythe
So I realized I was using the original version of your ToLower with all the ^'s everywhere and I changed it...

Code: Select all

@echo off

call macro_RtnLib


::define a Line Feed (newline) string (normally only used as !LF!)
set LF=^


::Above 2 blank lines are required - do not remove

::define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

set macro_ForEntireLine=^^^^^^^"eol^^^^=^^^^^^^%LF%%LF%^%LF%%LF%^^^%LF%%LF%^%LF%%LF%^^^^ delims^^^^=^^^^^^^"

:: A simple macro used to call macros with arguments
:: Usage:
::
::    %macro_call% ("arg1 arg2 arg3...") %macro.macroName%
::
set macro_Call=for /f "tokens=1-26" %%a in


set macro_ToLower=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=!%%~a!"%\n%
  for %%A in (%\n%
    "A=a" "B=b" "C=c" "D=d" "E=e" "F=f" "G=g" "H=h" "I=i"%\n%
    "J=j" "K=k" "L=l" "M=m" "N=n" "O=o" "P=p" "Q=q" "R=r"%\n%
    "S=s" "T=t" "U=u" "V=v" "W=w" "X=x" "Y=y" "Z=z" "Ä=ä"%\n%
    "Ö=ö" "Ü=ü"%\n%
  ) do set "str=!str:%%~A!"%\n%
  for /f %macro_forEntireLine% %%v in ("!str!") do endlocal^&set "%%~a=%%~v"%\n%
)

set macro_StrLen=do (%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=A!%%~a!"%\n%
  set "len=0"%\n%
  for /l %%A in (12,-1,0) do (%\n%
    set /a "len|=1<<%%A"%\n%
    for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"%\n%
  )%\n%
  for %%v in (!len!) do endlocal^&if "%%~b" neq "" (set "%%~b=%%v") else echo %%v%\n%
)

set macro_ToUpper=do (%\n%
  %macro_InitRtn%%\n%
  setlocal enableDelayedExpansion%\n%
  set "str=!%%~a!"%\n%
  for %%a in (%\n%
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"%\n%
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"%\n%
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "ä=Ä"%\n%
    "ö=Ö" "ü=Ü"%\n%
  ) do set "str=!str:%%~a!"%\n%
  %macro_Call% ("!errorlevel! 1 str %~2") %macro.AnyRtn1%%\n%
  %%macro_RtnAny%%
)
set macro\load.%~n0=1


Code: Select all

'%macro_RtnAny%' is not recognized as an internal or external command,
operable program or batch file.
do was unexpected at this time.

C:\batch>set macro_toUpper

macro_ToUpper=do (
  setlocal&set "NotDelayed=!"&set "macro_inFcn="
  setlocal enableDelayedExpansion
  set "str=!%~a!"
  for %a in (
    "a=A" "b=B" "c=C" "d=D" "e=E" "f=F" "g=G" "h=H" "i=I"
    "j=J" "k=K" "l=L" "m=M" "n=N" "o=O" "p=P" "q=Q" "r=R"
    "s=S" "t=T" "u=U" "v=V" "w=W" "x=X" "y=Y" "z=Z" "Σ=─"
    "÷=╓" "ⁿ=▄"
  ) do set "str=!str:%~a!"
  for /f "tokens=1-26" %a in ("!errorlevel! 1 str ") do (

C:\batch>



The other two macros appear to be defined correctly, but I haven't tried to use them.

Here is the batch I was using to test with:

Code: Select all

@echo off
PUSHD C:\Batch
cls

:: Clear variables
set result=
set macro\load.macro_rtnLib=
set macro\load.macro_StrLib=
set Macro_call=
set macro_ToUpper=
Set macro_ToLower=
Set macro_Strlen=
set macro_InitRtn=
set macro_InitFcnRtn=
set macro_RtnAnyPrefix=
set macro_InitRtn=
set macro.anyrtn1=
set macro.anyrtnN=
set macro.rtn1=
set macro_rtnAny=
set macro.rtnn=
set macro_forentireline=

:::::::::::::::::: Include :::::::::::::::::::::
:: Include macro_RtnLib
:: Include macro_TimerLib
::::::::::::::::::::::::::::::::::::::::::::::::
if not defined macro\load.macro_RtnLib call macro_RtnLib
if not defined macro\load.macro_StrLib call macro_StrLib

setlocal enableDelayedExpansion

set "result=This should be in all caps."
%Macro_call% ("result") %macro_ToUpper%
set result

POPD
Pause
exit /b



So That's where I'm stuck for now...