BrainF*** Interpreter in Batch

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: BrainF*** Interpreter in Batch

#46 Post by dbenham » 23 Jul 2014 20:54

einstein1969 wrote:Next step? a little more speed. This is very difficult... but we will do!

The execution of Hanoi_MOD.bf is now fast but more speed is necessary for a reasonable speed (for demo porpouse)
I think we need at least a 400% performance increase to get hanoi to be almost tolerable. I don't see that happening.

einstein1969 wrote:How increment the speed?

These are some tricks:

- problem with TMP/TEMP/USERPROFILE and FOR/F

I think that may set TMP=C:\ or other short path and leave the TMP variable.
(about 5-10% OFF)
I can't reproduce your findings. But it should be easy to modify my code on your own to see how it works for you. There are two places where I clear the environment. There is already a mechanism to skip important variables that can be expanded.

einstein1969 wrote:- use the counter of infinite loop. This drop off one variable in some cases. Examples for variable "cycles"
I took your advice in my version 6. It is a good idea, but I'm not seeing much improvement.

einstein1969 wrote:- Remove other not used variables.
:?: :? I don't see what else can be removed.

einstein1969 wrote:- Fine Tuning for Order of variable in the environment. Variables most used at the TOP.
Feel free to experiment with my code on your own. My guess this is splitting hairs, and it will not make a significant difference. We've limited the size of the environment, so it shouldn't make much difference.

einstein1969 wrote:- Reduce dimension of variables name and code for example C_1223=S ...
I tried this, as well as some other ideas to reduce the size of the SET /A "instruction". I was able to reduce the size of the instruction set significantly, but I did not see any performance increase. So I reverted back to the original notation for better readability. I've appended 3 versions to this post in case you want to experiment.

einstein1969 wrote:- It is possible merge 3 instructions into one?
Well, my code already allows indefinite concatenation of + - and [-] But they are rare, and normally pointless. Otherwise, the answer is NO. It is not possible to get the correct result if any ops are concatenated after < > [ ]. There is one special case with room for optimization. Some programs use [-] to clear a value, followed by a series of + to set a fixed positive value. My current code will perform 2 computations. But they could be merged into a single computation that sets the end value directly. Hanoi uses this construct, but it is not particularly common. I don't think the optimization will be noticeable.

einstein1969 wrote:- Other Ideas?
I can't think of any.

einstein1969 wrote:EDIT : the set /a ip+=1 instruction can be merged with precedence SET and replicated. The performance could be increase.
Possibly, but I think it will be negligible.


Below are versions 5.6, 5.7, and 5.8. They are all derived from my version 5 (without my version 6 enhancements). The goal was to reduce the size of the SET /A instructions in order to conserve memory used by environment space. I didn't see any performance improvement, so I abandoned this approach.

5.5)
- mp becomes m
- ip becomes i
- m[n] becomes m.n
- c[n] becomes c.n

5.7) Same as 5.6, plus a number of constant string snippets that are repeated with each instruction are stored in variables. The variables are then referenced in the instruction with delayed expansion.

5.8) Same as 5.7, plus removed the quotes surrounding each SET /A instruction.


EDIT - Fixed the definition of LF in 5.6 and 5.7, removed the errant spaces
version 5.6

Code: Select all

::BF.BAT - BrainF*** interpreter in Batch, dbenham version 5.6
::
:: Usage:
::
::   bf sourceFile [-option [value]]...
::
:: Options
::
::   -i Filename  = read Input from file Filename.
::
::   -z           = <Ctrl-Z> terminates keyboard input (forces EOF).
::
::   -e Value     = EOF value: Sets value read into memory upon End-Of-File.
::                  Default is no change to memory.
::
::   -ee          = terminate with Error if attempt is made to read past EOF.
::
::   -eo          = terminate with Error if +/- results in Overflow.
::
::   -em          = terminate with Error if < yields a negative Memory address.
::
::   -ea          = terminate with Error on Any fault. Same as -ee -eo -em
::
::   -s           = Silent mode. Don't print initialization messages, elapsed
::                  times, or run statistics. Also disables the -l option.
::
::   -d Filename  = Dump the parsed op codes to Filename.
::                  Use con to print to screen.
::
::   -m Filename  = write the interpreter Macro to Filename.
::                  Use con to print to screen.
::
::   -t           = Trace mode: Show op address/value, memory address/value
::                  each cycle.
::
::   -# Count     = Enables # op: Show current state. Count specifies the number
::                  of values to display before and after the current address.
::                  BUG ALERT: Some array values may not display properly if
::                  Count > 0 and array members are swapped out to disk. This is
::                  never a problem if -a is undefined.
::
::   -p           = Parse only.
::
::   -c Count     = Code memory (default = 100). Maximum amount of code allowed
::                  to reside in memory at one time. Unlimited if undefined.
::                  Excess code is swapped out to disk.
::
::   -a Count     = Array memory (default = unlimited). Maximum number of array
::                  members allowed to reside in memory at one time. Unlimited
::                  if undefined. Excess array members are swapped out to disk.
::
::   -l Count     = Limit the number of execution cycles to Count.
::                  Ignored if -s enabled. Default is no limit (undefined).
::
:: The memory array consists of unsigned bytes. Increment and decrement
:: operations simply wrap when the limits are exceeded unless -eo is specified.
::
:: The memory array is unbounded both to the left and right. If -em is specified
:: then the array is unbounded to the right only.
::
:: In theory, the code pointer can range from 0 to 2147483647,
:: and the memory pointer can range from -2147483648 to 2147483647.
:: But performance will become unbearably long before those limits are
:: reached.
::
:: BF.BAT can only read input from a file supplied as a command line option, or
:: the keyboard. Input will fail if data is piped into BF.BAT or if stdin is
:: redirected to a file.
::
:: During keyboard input, the <Enter> key is read as <CR>. All other inputs are
:: normal. Any byte value (except 0x00) may be entered by holding down the <Alt>
:: key and entering a decimal code on the numeric keypad. The only way to enter
:: <LF> is via <Alt> and the numeric keypad.
::
:: This code was written by Dave Benham (aka dbenham)
:: with major contributions from Antonio Perez Ayala (aka Aacini),
:: and einstein1969.
::
:: Development efforts can be traced at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=5751
 
@echo off
:: Rentry point when running the bf program
if "%~1" equ ":run" ( rem
  %bfInterpreter%
) %input%
 
:: Clear environment except for critical standard values
setlocal disableDelayedExpansion
for /f "eol== delims==" %%V in ('set^|findstr /rc:"^[^=]*!"') do set "%%V="
set "preserve= TEMP TMP PATH PATHEXT COMSPEC PRESERVE "
setlocal enableDelayedExpansion
for /f "eol== delims==" %%V in ('set') do (
  if defined %%V if "!preserve: %%V =!" equ "!preserve!" set "%%V="
)
set "preserve="
 
:: Validate parameters
if "%~1" equ "" >&2 echo Usage: %~N0 filename.ext&exit /b 1
if not exist "%~1" >&2 echo File not found: %1&exit /b 1
 
:: Define options
set options=-d:"" -s: -z: -e:"" -m:"" -ee: -eo: -em: -ea: -t: -#:"" -p: -i:"" -c:100 -a:"" -l:""
 
:: Set option defaults
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
 
:loop Parse the option arguments
if not "%~2"=="" (
  set "test=!options:*%~2:=! "
  if "!test!"=="!options! " (
    echo Error: Invalid option %~2
    exit /b 1
  ) else if "!test:~0,1!"==" " (
    set "%~2=1"
  ) else (
    set "%~2=%~3"
    shift /2
  )
  shift /2
  goto :loop
)
 
:: Begin macro defnitions --------------
 
:: Define LF to contain a linefeed (0x0A) character
set ^"LF=^

^" The above empty line is critical - DO NOT REMOVE

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

set showTime=(%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t1: =0^!"^) do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t2: =0^!"^) do set /a "t2=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  set /a "tDiff=t2-t1"%\n%
  if ^^!tDiff^^! lss 0 set /a tDiff+=24*60*60*100%\n%
  set /a "s=tDiff/100, f=tDiff+100"%\n%
  echo   Elapsed time = ^^!s^^!.^^!f:~-2^^! s%\n%
^)
 
if defined -ea (
  set -eo=1
  set -em=1
  set -ee=1
)
 
if defined -eo (
  set "inc=over|=(m.^^^!m^^^!+=^!count^!)&-256"
  set "dec=over|=(m.^^^!m^^^!-=^!count^!)&-256"
  set overError=%\n%
      if ^^!over^^! neq 0 ^>^&2 echo ^^!LF^^!ERROR: Overflow at i=%%I m=%%A^&exit 1
) else (
  set "inc=m.^^^!m^^^!=(m.^^^!m^^^!+^!count^!)&255"
  set "dec=m.^^^!m^^^!=(m.^^^!m^^^!-^!count^!)&255"
)
 
if defined -em (
  set leftError=%\n%
      if ^^!m^^! lss 0 ^>^&2 echo ^^!LF^^!ERROR: Invalid memory access at i=%%I m=%%A^&exit 1
)
 
if defined -t set trace=%\n%
    echo  c.%%I=^^!c.%%I^^!   m.%%A=^^!m.%%A^^!
 
if defined -e set ^"setEOF=set /a "m.%%A=!-e!"^"
 
if defined -z set eofMacros=1
if defined -i set eofMacros=1
if defined eofMacros (
  set "ifNotEOF=if not defined EOF"
  set ifSub=else if "^!key:~-1^!" equ "^!SUB^!" (%\n%
        endlocal^&endlocal%\n%
        set EOF=1%\n%
        !setEOF!%\n%
      ^)
  if defined -ee (
    set "elsePastEOF= else >&2 echo ^!LF^!ERROR: Read past EOF at i=%%I m=%%A&exit 1 "
  ) else if defined -e (
    set "elsePastEOF= else !setEOF! "
  )
)
 
if defined -i (
  set ^"getInput=else if "%%B" equ "," ( for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set /p "key="%\n%
      if defined key (set /a "m.%%A=0x^!key^!"^) else ( !setEOF!%\n%
        set EOF=1%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
) else for %%F in (findstr.exe) do (
  set ^"getInput=else if "%%B" equ "," (for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set "CtrlC=1"%\n%
      setlocal disableDelayedExpansion%\n%
      for /f "delims=" %%K in (%\n%
        '^^^^^""%%~dp$path:Fxcopy.exe" /w "%~f0" "%~f0" 2^^^^^>nul^^^^^"'%\n%
      ^) do if not defined key (set "key=%%K"^) else set "CtrlC="%\n%
      setlocal enableDelayedExpansion%\n%
      if defined CtrlC (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=3"%\n%
      ^) else if "^!key^!" equ "^!line1^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=10"%\n%
      ^) else if "^!key:~-1^!" equ "^!CR^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=13"%\n%
      ^) !ifSub! else (%\n%
        ^>key.bf_tmp echo(^^!key:~-1^^!%\n%
        endlocal^&endlocal%\n%
        for /f "delims=." %%K in ('^^^^^""%%~$path:F" /lg:key.bf_tmp *.bf_chr^^^^^"'^) do set "m.%%A=%%K"%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
)
 
if defined -# (
 
  set #=#
 
  set #Parse=else if "%%b" equ "#" (%\n%
        if defined lastCmd (%\n%
          set /a i+=1%\n%
          ^>c.^^!i^^!.bf_tmp echo S "^^!lastCmd:~0,-1^^!"%\n%
          set "lastCmd="%\n%
        ^)%\n%
        set /a "i+=1, #Found=1, clr=0"%\n%
        ^>c.^^!i^^!.bf_tmp echo #%\n%
      ^)
 
  if !-#! equ 0 (
    set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      echo  c.^^!next^^!=^^!msg^^!  m.%%A=^^!m.%%A^^!%\n%
    ^) ^"
  ) else set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1, m1=m-!-#!, m2=m-1, m3=m+1, m4=m+!-#!%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      set "msg=c.^!next^!=^!msg^!  m=%%A: "%\n%
      for /l %%N in (^^!m1^^! 1 ^^!m2^^!^) do set "msg=^!msg^!| ^!m.%%N^! "%\n%
      set "msg=^!msg^!< ^!m.%%A^! >"%\n%
      for /l %%N in (^^!m3^^! 1 ^^!m4^^!^) do set "msg=^!msg^! ^!m.%%N^! |"%\n%
      echo  ^^!msg^^!%\n%
    ^) ^"
 
)
 
if not defined -s (
 
  set "getCycles=, cycles+=1"
 
  set "getCodePageFaults=, codePageFaults+=1"
 
  set "getArrayStats=, aSize+=1"
 
  set "getArrayPageFaults=, arrayPageFautls+=1"
 
  set showStats=%\n%
      ^>stats.bf_tmp (echo   cycles=^^!cycles^^!  codePageFaults=^^!codePageFaults^^!%\n%
      echo   arraySize=^^!aSize^^!  arrayPageFaults=^^!arrayPageFaults^^!^)
 
  if defined -l set getCycles=!getCycles! ^& if ^^!cycles^^! equ !-l! (!showStats!%\n%
      exit 0%\n%
    ^)
)
 
if defined -c set codePaging=%\n%
  if not defined c.%%I if exist c.%%I.bf_tmp (%\n%
    if ^^!cdCnt^^! equ !-c! (%\n%
      for /f "delims==" %%A in ('set c.') do set "%%A="%\n%
      set cdCnt=0%\n%
    )%\n%
    set /a cdCnt+=1!getCodePageFaults!%\n%
    ^<c.%%I.bf_tmp set /p "c.%%I="%\n%
  )
 
if defined -a (
  set arrayPaging=if not defined m.%%A (%\n%
      if ^^!aCnt^^! equ !-a! for /f "tokens=1,2 delims==" %%a in ('set m.'^) do (%\n%
        ^>%%a.bf_tmp echo %%b%\n%
        set %%a=%\n%
        set aCnt=0%\n%
      ^)%\n%
      if exist m.%%A.bf_tmp (%\n%
        ^<m.%%A.bf_tmp set /p m.%%A=%\n%
        set /a arrayPageFaults+=1%\n%
      ^) else set /a m.%%A=0!getArrayStats!%\n%
      set /a aCnt+=1%\n%
    ^)
) else set "arrayPaging=if not defined m.%%A set /a m.%%A=0!getArrayStats!"
 
:: End macro definition ------------------
 
:: Create character files if they don't already exist
if not defined -s (
  echo ----------------------------
  echo Begin Initialization
  echo Testing ASCII character files.
)
set "needChars="
for /l %%N in (0 1 255) do for %%F in (%%N.bf_chr) do if "%%~zF" neq "1" set "needChars=1"
if defined needChars (
  if not defined -s (
    echo Creating ASCII character files...
    set "t1=!time!"
  )
  call :genAllChr
  if not defined -s (
    set t2=!time!
    %showTime%
  )
)
 
:: Parse the input file
if not defined -i goto :skipInput
if not defined -s (
  <nul set /p "=Parsing input."
  set t1=!time!
)
if not exist "!-i!" >&2 echo ERROR: Input file "!-i!" not found & exit 1
if exist "!-i!\" >&2 echo ERROR: Input file "!-i!" not found & exit 1
copy 0.bf_chr compare.bf_tmp >nul
set /a  size=skipStart=1
for %%F in ("!-i!") do set inSize=%%~zF
for /l %%N in (1 1 32) do if !size! lss !inSize! set /a "size*=2" & type compare.bf_tmp >>compare.bf_tmp
fc /b "!-i!" compare.bf_tmp | findstr /rxc:"........: .. .." >diff.bf_tmp
>input.bf_tmp (
  for /f "tokens=1,2 delims=:[] " %%A in (diff.bf_tmp) do (
    set /a skipEnd=0x%%A
    for /l %%N in (!skipStart! 1 !skipEnd!) do echo 00
    echo %%B
    set /a skipStart=skipEnd+2
    if "!skipStart:~-3!" equ "000" <nul set /p "=." >&2
  )
  for /l %%N in (!skipStart! 1 !inSize!) do echo 00
)
set "input=<input.bf_tmp"
if not defined -s (
  set t2=!time!
  echo(
  %showTime%
)
:skipInput
 
:: Parse the code. Consecutive op codes are coalesced into one op with a count.
:: Optionally treats # as an additional show state op code (withoug coalesce).
:: Optimization collapses [-] into a special clear value op.
:: + - < > [ ] [-] are all converted into an S op followed by a SET /A expression.
:: Consecutive + - and [-] are concatenated into one op. If followed by
:: < > [ or ] then that op is also concatenated, but that is the limit for one op.
:: . and , are stored as themselves, followed by the count.
:: # is stored as itself without a count
:: Detects if input is not used so getInput can be removed from interpreter loop.
:: Treats loops at beginning or immedieately following ] as comments (ignores them)
if not defined -s (
  <nul set /p "=Parsing code."
  set "parseProgress=set /a n=(n+1^^)%%500&if ^!n^! equ 0 <nul set /p "=.""
  set "t1=!time!"
)
2>nul del c.*.bf_tmp
for %%C in (+ - "<" ">" . "," #) do set "comment%%~C=0"
set /a i=comment]=-1, sp=count=comment=0, comment[=1
cmd /u /c type %1|find /v ""|findstr "[+\-<>.,[\]!#!]" >src.bf_tmp
for /f %%b in (src.bf_tmp) do ( %parseProgress%
  if !comment! gtr 0 (
    set /a comment+=!comment%%b!
  ) else if "!lastOp!" equ "%%b" (
    set /a count+=1
  ) else (
    if defined lastOp (
      set "test="
      if !clr! equ 1 if !lastOp! equ - if !count! equ 1 set /a clr=test=2
      if not defined test set clr=0
      if "!lastOp!" equ "+" (
        set "lastCmd=!lastCmd!%inc%,"
      ) else if "!lastOp!" equ "-" (
        set "lastCmd=!lastCmd!!delim!%dec%,"
      ) else (
        set /a i+=1
        if "!lastOp!" equ ">" (
          >c.!i!.bf_tmp echo S "!lastCmd!m+=!count!"
        ) else if "!lastOp!" equ "<" (
          >c.!i!.bf_tmp echo S "!lastCmd!m-=!count!"
        ) else (
          if defined lastCmd (
            >c.!i!.bf_tmp echo S "!lastCmd:~0,-1!"
            set /a i+=1
          )
          >c.!i!.bf_tmp echo !lastOp! !count!
        )
        set "lastCmd="
      )
      set "lastOp="
    )
    if "%%b" equ "[" (
      if defined comment (set /a comment+=1) else (
        set /a "i+=1, sp+=1, clr=1"
        set /a "stack.!sp!=i"
        set "stackCmd.!sp!=!lastCmd!"
        set "lastCmd="
      )
    ) else if "%%b" equ "]" for %%s in (!sp!) do (
      if !clr! equ 2 (
        set "lastCmd=!stackCmd.%%s!m.^!m^!=0,"
        set /a "i-=1, sp-=1"
      ) else (
        set /a i+=1, sp-=1, count=i-stack.%%s
        >c.!i!.bf_tmp echo S "!lastCmd!i-=^^^!^^^!m.^!m^!*!count!"
        >c.!stack.%%s!.bf_tmp echo S "!stackCmd.%%s!i+=^^^!m.^!m^!*!count!"
        set "lastCmd="
      )
      set /a "clr=comment=0"
    ) %#Parse% else (
      set "comment="
      set "lastOp=%%b"
      set /a "count=1"
      if "%%b" equ "," set inputRequired=1
    )
  )
)
if defined lastOp (
  set /a i+=1
  (
    if "!lastOp!" equ "+" (
      echo S "!lastCmd!%inc%"
    ) else if "!lastOp!" equ "-" (
      echo S "!lastCmd!%dec%"
    ) else if "!lastOp!" equ ">" (
      echo S "!lastCmd!m+=!count!"
    ) else if "!lastOp!" equ "<" (
      echo S "!lastCmd!m-=!count!"
    ) else (
      echo S "!lastCmd:~0,-1!"
      echo !lastOp! !count!
    )
  )>c.!i!.bf_tmp
) else if defined lastCmd (
  set /a i+=1
  >c.!i!.bf_tmp echo S "!lastCmd:~0,-1!"
)
if not defined -s (
  set "t2=!time!"
  echo(
  %showTime%
)
 
:: Error if unbalanced brackets
if %sp% neq 0 >&2 echo ERROR: Unbalanced brackets&exit /b 1
if defined comment if "%comment%" neq "0" >&2 echo ERROR: Unbalanced brackets&exit /b 1
 
:: Dump the parsed op codes
if defined -d (for /l %%N in (0 1 !i!) do (
  <c.%%N.bf_tmp set /p "msg="
  echo c.%%N=!msg!
)) >"!-d!"
 
:: Disable code paging if code length does not exceed -cp
if not defined -c set "-c=!i!
if !i! leq !-c! (
  set "codePaging="
  for /l %%N in (0 1 !i!) do <c.%%N.bf_tmp set /p "c.%%N="
)
 
:: Undefine getInput and/or #Parse if not needed
if not defined inputRequired set "getInput="
if not defined #Found set "#Exec="
 
:: Define CR to hold <CarriageReturn> for use in input routine
for /f %%A in ('copy /Z "%~dpf0" nul') do set "CR=%%A"
 
:: Define SUB to hold <0x1A>
for /f "delims=" %%A in (26.bf_chr) do set "SUB=%%A"
 
:: Establish line one of input routine for this locale
set "line1="
for /f "delims=" %%L in (
  'echo .^|xcopy /w "%~f0" "%~f0" 2^>nul'
) do if not defined line1 set "line1=%%L"
set "line1=!line1:~0,-1!"
 
 
:: Create final interpreter macro
set bfInterpreter=^
for /f "delims==" %%V in ('set^^^^^|findstr /bv "c. line1= CR= SUB="') do set "%%V="%\n%
set /a "i=m=cdCnt=cycles=aCnt=aSize=over=0"%\n%
for /l %%. in () do for %%I in (^^!i^^!) do (!codePaging!%\n%
  for /f "tokens=1-3" %%A in ("^!m^! ^!c.%%I^!") do (%\n%
    !arrayPaging!!trace!%\n%
    if "%%B" equ "S" (%\n%
      set /a "%%C"!overError!!leftError!%\n%
    ) else if "%%B" equ "." for /l %%N in (1 1 %%C) do (%\n%
      if "^!m.%%A^!" equ "26" (set /p "=^!SUB^!" ^<nul) else type ^^!m.%%A^^!.bf_chr%\n%
    ) !getInput!!#Exec!else (!showStats!%\n%
      exit 0%\n%
    )%\n%
    set /a i+=1!getCycles!%\n%
  )%\n%
)
 
:: Print out the finalized brainF*** interpreter macro
if defined -m >"!-m!" echo bfInterpreter=!lf!!bfInterpreter:%%=%%%%!
 
 
if not defined -s (
  echo Initialization complete
  echo ============================
  set t1=%time%
)
 
(call )
if defined -p goto :quit
 
:: Launch the interpreter
cmd /v:on /c "%~f0" :run&&(call )||(call)
set "err=%errolevel%
 
if not defined -s (
  set t2=%time%
  echo(
  echo ============================
  %showTime%
  if exist stats.bf_tmp type stats.bf_tmp
)
 
:quit
del *.bf_tmp
exit /b %err%
 
:genAllChr
::This code creates 256 1 byte files, one for each possible byte value.
::This is encoded in a macro way to be called asynchronously with start cmd /c
::Teamwork of carlos, penpen, aGerman, dbenham, einstein1969
::Tested under Win7 and XP
setlocal disableDelayedExpansion
set ^"genchr=(^
for /l %%N in (%%A !cnt! 255) do (^
  if %%N equ 26 (^
    copy /y nul + nul /a 26.bf_chr /a ^>nul^
  ) else (if %%N geq 35 if %%N leq 126 if %%N neq 61 (^
    ^<nul set /p "=!ascii:~%%N,1!" ^>%%N.bf_chr^
  ))^&^
  if not exist %%N.bf_chr (^
    makecab /d compress=off /d reserveperdatablocksize=26 /d reserveperfoldersize=%%N %%A.tmp %%N.bf_chr ^>nul^&^
    type %%N.bf_chr ^| ((for /l %%n in (1 1 38) do pause)^>nul^&findstr "^^" ^>%%N.temp)^&^
    ^>nul copy /y %%N.temp /a %%N.bf_chr /b^&^
    del %%N.temp^
  )^
))^&^
del %%A.tmp^"
del /f /q /a *bf.chr >nul 2>&1
set "ascii=                                   #$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
set /a cnt=number_of_processors
if %cnt% lss 1 set cnt=1
if %cnt% gtr 256 set cnt=256
set /a "end=cnt-1"
for /l %%A in (0 1 %end%) do (
  type nul >%%A.tmp
  if %%A equ %end% (
    cmd /q /v:on /c "%genchr%"
  ) else (
    start "" /b cmd /q /v:on /c "%genchr%"
  )
)
:genAllChr.check
for /l %%N in (0 1 %end%) do if exist %%N.tmp goto :genAllChr.check
exit /b


version 5.7

Code: Select all

::BF.BAT - BrainF*** interpreter in Batch, dbenham version 5.7
::
:: Usage:
::
::   bf sourceFile [-option [value]]...
::
:: Options
::
::   -i Filename  = read Input from file Filename.
::
::   -z           = <Ctrl-Z> terminates keyboard input (forces EOF).
::
::   -e Value     = EOF value: Sets value read into memory upon End-Of-File.
::                  Default is no change to memory.
::
::   -ee          = terminate with Error if attempt is made to read past EOF.
::
::   -eo          = terminate with Error if +/- results in Overflow.
::
::   -em          = terminate with Error if < yields a negative Memory address.
::
::   -ea          = terminate with Error on Any fault. Same as -ee -eo -em
::
::   -s           = Silent mode. Don't print initialization messages, elapsed
::                  times, or run statistics. Also disables the -l option.
::
::   -d Filename  = Dump the parsed op codes to Filename.
::                  Use con to print to screen.
::
::   -m Filename  = write the interpreter Macro to Filename.
::                  Use con to print to screen.
::
::   -t           = Trace mode: Show op address/value, memory address/value
::                  each cycle.
::
::   -# Count     = Enables # op: Show current state. Count specifies the number
::                  of values to display before and after the current address.
::                  BUG ALERT: Some array values may not display properly if
::                  Count > 0 and array members are swapped out to disk. This is
::                  never a problem if -a is undefined.
::
::   -p           = Parse only.
::
::   -c Count     = Code memory (default = 100). Maximum amount of code allowed
::                  to reside in memory at one time. Unlimited if undefined.
::                  Excess code is swapped out to disk.
::
::   -a Count     = Array memory (default = unlimited). Maximum number of array
::                  members allowed to reside in memory at one time. Unlimited
::                  if undefined. Excess array members are swapped out to disk.
::
::   -l Count     = Limit the number of execution cycles to Count.
::                  Ignored if -s enabled. Default is no limit (undefined).
::
:: The memory array consists of unsigned bytes. Increment and decrement
:: operations simply wrap when the limits are exceeded unless -eo is specified.
::
:: The memory array is unbounded both to the left and right. If -em is specified
:: then the array is unbounded to the right only.
::
:: In theory, the code pointer can range from 0 to 2147483647,
:: and the memory pointer can range from -2147483648 to 2147483647.
:: But performance will become unbearably long before those limits are
:: reached.
::
:: BF.BAT can only read input from a file supplied as a command line option, or
:: the keyboard. Input will fail if data is piped into BF.BAT or if stdin is
:: redirected to a file.
::
:: During keyboard input, the <Enter> key is read as <CR>. All other inputs are
:: normal. Any byte value (except 0x00) may be entered by holding down the <Alt>
:: key and entering a decimal code on the numeric keypad. The only way to enter
:: <LF> is via <Alt> and the numeric keypad.
::
:: This code was written by Dave Benham (aka dbenham)
:: with major contributions from Antonio Perez Ayala (aka Aacini),
:: and einstein1969.
::
:: Development efforts can be traced at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=5751
 
@echo off
:: Rentry point when running the bf program
if "%~1" equ ":run" ( rem
  %bfInterpreter%
) %input%
 
:: Clear environment except for critical standard values
setlocal disableDelayedExpansion
for /f "eol== delims==" %%V in ('set^|findstr /rc:"^[^=]*!"') do set "%%V="
set "preserve= TEMP TMP PATH PATHEXT COMSPEC PRESERVE "
setlocal enableDelayedExpansion
for /f "eol== delims==" %%V in ('set') do (
  if defined %%V if "!preserve: %%V =!" equ "!preserve!" set "%%V="
)
set "preserve="
 
:: Validate parameters
if "%~1" equ "" >&2 echo Usage: %~N0 filename.ext&exit /b 1
if not exist "%~1" >&2 echo File not found: %1&exit /b 1
 
:: Define options
set options=-d:"" -s: -z: -e:"" -m:"" -ee: -eo: -em: -ea: -t: -#:"" -p: -i:"" -c:100 -a:"" -l:""
 
:: Set option defaults
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
 
:loop Parse the option arguments
if not "%~2"=="" (
  set "test=!options:*%~2:=! "
  if "!test!"=="!options! " (
    echo Error: Invalid option %~2
    exit /b 1
  ) else if "!test:~0,1!"==" " (
    set "%~2=1"
  ) else (
    set "%~2=%~3"
    shift /2
  )
  shift /2
  goto :loop
)
 
:: Begin macro defnitions --------------
 
:: Define LF to contain a linefeed (0x0A) character
set ^"LF=^

^" The above empty line is critical - DO NOT REMOVE

:: define a newline with line continuation
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
 
set showTime=(%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t1: =0^!"^) do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t2: =0^!"^) do set /a "t2=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  set /a "tDiff=t2-t1"%\n%
  if ^^!tDiff^^! lss 0 set /a tDiff+=24*60*60*100%\n%
  set /a "s=tDiff/100, f=tDiff+100"%\n%
  echo   Elapsed time = ^^!s^^!.^^!f:~-2^^! s%\n%
^)
 
if defined -ea (
  set -eo=1
  set -em=1
  set -ee=1
)
 
set "+=i+=^!m."
set "-=i-=^!^!m."
 
if defined -eo (
  set "e=)&-256"
  set "o=over|=(m."
  set "inc=^^^!o^^^!^^^!m^^^!+=^!count^!^^^!e^^^!"
  set "dec=^^^!o^^^!^^^!m^^^!-=^!count^!^^^!e^^^!"
  set overError=%\n%
      if ^^!over^^! neq 0 ^>^&2 echo ^^!LF^^!ERROR: Overflow at i=%%I m=%%A^&exit 1
) else (
  set "e==(m."
  set "o=)&255"
  set "inc=m.^^^!m^^^!^^^!e^^^!^^^!m^^^!+^!count^!^^^!o^^^!"
  set "dec=m.^^^!m^^^!^^^!e^^^!^^^!m^^^!-^!count^!^^^!o^^^!"
)
 
if defined -em (
  set leftError=%\n%
      if ^^!m^^! lss 0 ^>^&2 echo ^^!LF^^!ERROR: Invalid memory access at i=%%I m=%%A^&exit 1
)
 
if defined -t set trace=%\n%
    echo  c.%%I=^^!c.%%I^^!   m.%%A=^^!m.%%A^^!
 
if defined -e set ^"setEOF=set /a "m.%%A=!-e!"^"
 
if defined -z set eofMacros=1
if defined -i set eofMacros=1
if defined eofMacros (
  set "ifNotEOF=if not defined EOF"
  set ifSub=else if "^!key:~-1^!" equ "^!SUB^!" (%\n%
        endlocal^&endlocal%\n%
        set EOF=1%\n%
        !setEOF!%\n%
      ^)
  if defined -ee (
    set "elsePastEOF= else >&2 echo ^!LF^!ERROR: Read past EOF at i=%%I m=%%A&exit 1 "
  ) else if defined -e (
    set "elsePastEOF= else !setEOF! "
  )
)
 
if defined -i (
  set ^"getInput=else if "%%B" equ "," ( for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set /p "key="%\n%
      if defined key (set /a "m.%%A=0x^!key^!"^) else ( !setEOF!%\n%
        set EOF=1%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
) else for %%F in (findstr.exe) do (
  set ^"getInput=else if "%%B" equ "," (for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set "CtrlC=1"%\n%
      setlocal disableDelayedExpansion%\n%
      for /f "delims=" %%K in (%\n%
        '^^^^^""%%~dp$path:Fxcopy.exe" /w "%~f0" "%~f0" 2^^^^^>nul^^^^^"'%\n%
      ^) do if not defined key (set "key=%%K"^) else set "CtrlC="%\n%
      setlocal enableDelayedExpansion%\n%
      if defined CtrlC (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=3"%\n%
      ^) else if "^!key^!" equ "^!line1^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=10"%\n%
      ^) else if "^!key:~-1^!" equ "^!CR^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=13"%\n%
      ^) !ifSub! else (%\n%
        ^>key.bf_tmp echo(^^!key:~-1^^!%\n%
        endlocal^&endlocal%\n%
        for /f "delims=." %%K in ('^^^^^""%%~$path:F" /lg:key.bf_tmp *.bf_chr^^^^^"'^) do set "m.%%A=%%K"%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
)
 
if defined -# (
 
  set #=#
 
  set #Parse=else if "%%b" equ "#" (%\n%
        if defined lastCmd (%\n%
          set /a i+=1%\n%
          ^>c.^^!i^^!.bf_tmp echo S "^^!lastCmd:~0,-1^^!"%\n%
          set "lastCmd="%\n%
        ^)%\n%
        set /a "i+=1, #Found=1, clr=0"%\n%
        ^>c.^^!i^^!.bf_tmp echo #%\n%
      ^)
 
  if !-#! equ 0 (
    set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      echo  c.^^!next^^!=^^!msg^^!  m.%%A=^^!m.%%A^^!%\n%
    ^) ^"
  ) else set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1, m1=m-!-#!, m2=m-1, m3=m+1, m4=m+!-#!%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      set "msg=c.^!next^!=^!msg^!  m=%%A: "%\n%
      for /l %%N in (^^!m1^^! 1 ^^!m2^^!^) do set "msg=^!msg^!| ^!m.%%N^! "%\n%
      set "msg=^!msg^!< ^!m.%%A^! >"%\n%
      for /l %%N in (^^!m3^^! 1 ^^!m4^^!^) do set "msg=^!msg^! ^!m.%%N^! |"%\n%
      echo  ^^!msg^^!%\n%
    ^) ^"
 
)
 
if not defined -s (
 
  set "getCycles=, cycles+=1"
 
  set "getCodePageFaults=, codePageFaults+=1"
 
  set "getArrayStats=, aSize+=1"
 
  set "getArrayPageFaults=, arrayPageFautls+=1"
 
  set showStats=%\n%
      ^>stats.bf_tmp (echo   cycles=^^!cycles^^!  codePageFaults=^^!codePageFaults^^!%\n%
      echo   arraySize=^^!aSize^^!  arrayPageFaults=^^!arrayPageFaults^^!^)
 
  if defined -l set getCycles=!getCycles! ^& if ^^!cycles^^! equ !-l! (!showStats!%\n%
      exit 0%\n%
    ^)
)
 
if defined -c set codePaging=%\n%
  if not defined c.%%I if exist c.%%I.bf_tmp (%\n%
    if ^^!cdCnt^^! equ !-c! (%\n%
      for /f "delims==" %%A in ('set c.') do set "%%A="%\n%
      set cdCnt=0%\n%
    )%\n%
    set /a cdCnt+=1!getCodePageFaults!%\n%
    ^<c.%%I.bf_tmp set /p "c.%%I="%\n%
  )
 
if defined -a (
  set arrayPaging=if not defined m.%%A (%\n%
      if ^^!aCnt^^! equ !-a! for /f "tokens=1,2 delims==" %%a in ('set m.'^) do (%\n%
        ^>%%a.bf_tmp echo %%b%\n%
        set %%a=%\n%
        set aCnt=0%\n%
      ^)%\n%
      if exist m.%%A.bf_tmp (%\n%
        ^<m.%%A.bf_tmp set /p m.%%A=%\n%
        set /a arrayPageFaults+=1%\n%
      ^) else set /a m.%%A=0!getArrayStats!%\n%
      set /a aCnt+=1%\n%
    ^)
) else set "arrayPaging=if not defined m.%%A set /a m.%%A=0!getArrayStats!"
 
:: End macro definition ------------------
 
:: Create character files if they don't already exist
if not defined -s (
  echo ----------------------------
  echo Begin Initialization
  echo Testing ASCII character files.
)
set "needChars="
for /l %%N in (0 1 255) do for %%F in (%%N.bf_chr) do if "%%~zF" neq "1" set "needChars=1"
if defined needChars (
  if not defined -s (
    echo Creating ASCII character files...
    set "t1=!time!"
  )
  call :genAllChr
  if not defined -s (
    set t2=!time!
    %showTime%
  )
)
 
:: Parse the input file
if not defined -i goto :skipInput
if not defined -s (
  <nul set /p "=Parsing input."
  set t1=!time!
)
if not exist "!-i!" >&2 echo ERROR: Input file "!-i!" not found & exit 1
if exist "!-i!\" >&2 echo ERROR: Input file "!-i!" not found & exit 1
copy 0.bf_chr compare.bf_tmp >nul
set /a  size=skipStart=1
for %%F in ("!-i!") do set inSize=%%~zF
for /l %%N in (1 1 32) do if !size! lss !inSize! set /a "size*=2" & type compare.bf_tmp >>compare.bf_tmp
fc /b "!-i!" compare.bf_tmp | findstr /rxc:"........: .. .." >diff.bf_tmp
>input.bf_tmp (
  for /f "tokens=1,2 delims=:[] " %%A in (diff.bf_tmp) do (
    set /a skipEnd=0x%%A
    for /l %%N in (!skipStart! 1 !skipEnd!) do echo 00
    echo %%B
    set /a skipStart=skipEnd+2
    if "!skipStart:~-3!" equ "000" <nul set /p "=." >&2
  )
  for /l %%N in (!skipStart! 1 !inSize!) do echo 00
)
set "input=<input.bf_tmp"
if not defined -s (
  set t2=!time!
  echo(
  %showTime%
)
:skipInput
 
:: Parse the code. Consecutive op codes are coalesced into one op with a count.
:: Optionally treats # as an additional show state op code (withoug coalesce).
:: Optimization collapses [-] into a special clear value op.
:: + - < > [ ] [-] are all converted into an S op followed by a SET /A expression.
:: Consecutive + - and [-] are concatenated into one op. If followed by
:: < > [ or ] then that op is also concatenated, but that is the limit for one op.
:: . and , are stored as themselves, followed by the count.
:: # is stored as itself without a count
:: Detects if input is not used so getInput can be removed from interpreter loop.
:: Treats loops at beginning or immedieately following ] as comments (ignores them)
if not defined -s (
  <nul set /p "=Parsing code."
  set "parseProgress=set /a n=(n+1^^)%%500&if ^!n^! equ 0 <nul set /p "=.""
  set "t1=!time!"
)
2>nul del c.*.bf_tmp
for %%C in (+ - "<" ">" . "," #) do set "comment%%~C=0"
set /a i=comment]=-1, sp=count=comment=0, comment[=1
cmd /u /c type %1|find /v ""|findstr "[+\-<>.,[\]!#!]" >src.bf_tmp
for /f %%b in (src.bf_tmp) do ( %parseProgress%
  if !comment! gtr 0 (
    set /a comment+=!comment%%b!
  ) else if "!lastOp!" equ "%%b" (
    set /a count+=1
  ) else (
    if defined lastOp (
      set "test="
      if !clr! equ 1 if !lastOp! equ - if !count! equ 1 set /a clr=test=2
      if not defined test set clr=0
      if "!lastOp!" equ "+" (
        set "lastCmd=!lastCmd!%inc%,"
      ) else if "!lastOp!" equ "-" (
        set "lastCmd=!lastCmd!!delim!%dec%,"
      ) else (
        set /a i+=1
        if "!lastOp!" equ ">" (
          >c.!i!.bf_tmp echo S "!lastCmd!m+=!count!"
        ) else if "!lastOp!" equ "<" (
          >c.!i!.bf_tmp echo S "!lastCmd!m-=!count!"
        ) else (
          if defined lastCmd (
            >c.!i!.bf_tmp echo S "!lastCmd:~0,-1!"
            set /a i+=1
          )
          >c.!i!.bf_tmp echo !lastOp! !count!
        )
        set "lastCmd="
      )
      set "lastOp="
    )
    if "%%b" equ "[" (
      if defined comment (set /a comment+=1) else (
        set /a "i+=1, sp+=1, clr=1"
        set /a "stack.!sp!=i"
        set "stackCmd.!sp!=!lastCmd!"
        set "lastCmd="
      )
    ) else if "%%b" equ "]" for %%s in (!sp!) do (
      if !clr! equ 2 (
        set "lastCmd=!stackCmd.%%s!m.^!m^!=0,"
        set /a "i-=1, sp-=1"
      ) else (
        set /a i+=1, sp-=1, count=i-stack.%%s
        >c.!i!.bf_tmp echo S "!lastCmd!^!-^!^!m^!*!count!"
        >c.!stack.%%s!.bf_tmp echo S "!stackCmd.%%s!^!+^!^!m^!*!count!"
        set "lastCmd="
      )
      set /a "clr=comment=0"
    ) %#Parse% else (
      set "comment="
      set "lastOp=%%b"
      set /a "count=1"
      if "%%b" equ "," set inputRequired=1
    )
  )
)
if defined lastOp (
  set /a i+=1
  (
    if "!lastOp!" equ "+" (
      echo S "!lastCmd!%inc%"
    ) else if "!lastOp!" equ "-" (
      echo S "!lastCmd!%dec%"
    ) else if "!lastOp!" equ ">" (
      echo S "!lastCmd!m+=!count!"
    ) else if "!lastOp!" equ "<" (
      echo S "!lastCmd!m-=!count!"
    ) else (
      echo S "!lastCmd:~0,-1!"
      echo !lastOp! !count!
    )
  )>c.!i!.bf_tmp
) else if defined lastCmd (
  set /a i+=1
  >c.!i!.bf_tmp echo S "!lastCmd:~0,-1!"
)
if not defined -s (
  set "t2=!time!"
  echo(
  %showTime%
)
 
:: Error if unbalanced brackets
if %sp% neq 0 >&2 echo ERROR: Unbalanced brackets&exit /b 1
if defined comment if "%comment%" neq "0" >&2 echo ERROR: Unbalanced brackets&exit /b 1
 
:: Dump the parsed op codes
if defined -d (for /l %%N in (0 1 !i!) do (
  <c.%%N.bf_tmp set /p "msg="
  echo c.%%N=!msg!
)) >"!-d!"
 
:: Disable code paging if code length does not exceed -cp
if not defined -c set "-c=!i!
if !i! leq !-c! (
  set "codePaging="
  for /l %%N in (0 1 !i!) do <c.%%N.bf_tmp set /p "c.%%N="
)
 
:: Undefine getInput and/or #Parse if not needed
if not defined inputRequired set "getInput="
if not defined #Found set "#Exec="
 
:: Define CR to hold <CarriageReturn> for use in input routine
for /f %%A in ('copy /Z "%~dpf0" nul') do set "CR=%%A"
 
:: Define SUB to hold <0x1A>
for /f "delims=" %%A in (26.bf_chr) do set "SUB=%%A"
 
:: Establish line one of input routine for this locale
set "line1="
for /f "delims=" %%L in (
  'echo .^|xcopy /w "%~f0" "%~f0" 2^>nul'
) do if not defined line1 set "line1=%%L"
set "line1=!line1:~0,-1!"
 
 
:: Create final interpreter macro
set bfInterpreter=^
for /f "delims==" %%V in ('set^^^^^|findstr /bv "c\. line1= CR= SUB= o= e= += -="') do set "%%V="%\n%
set /a "i=m=cdCnt=cycles=aCnt=aSize=over=0"%\n%
for /l %%. in () do for %%I in (^^!i^^!) do (!codePaging!%\n%
  for /f "tokens=1-3" %%A in ("^!m^! ^!c.%%I^!") do (%\n%
    !arrayPaging!!trace!%\n%
    if "%%B" equ "S" (%\n%
      set /a "%%C"!overError!!leftError!%\n%
    ) else if "%%B" equ "." for /l %%N in (1 1 %%C) do (%\n%
      if "^!m.%%A^!" equ "26" (set /p "=^!SUB^!" ^<nul) else type ^^!m.%%A^^!.bf_chr%\n%
    ) !getInput!!#Exec!else (!showStats!%\n%
      exit 0%\n%
    )%\n%
    set /a i+=1!getCycles!%\n%
  )%\n%
)
 
:: Print out the finalized brainF*** interpreter macro
if defined -m >"!-m!" echo bfInterpreter=!lf!!bfInterpreter:%%=%%%%!
 
 
if not defined -s (
  echo Initialization complete
  echo ============================
  set t1=%time%
)
 
(call )
if defined -p goto :quit
 
:: Launch the interpreter
cmd /v:on /c "%~f0" :run&&(call )||(call)
set "err=%errolevel%
 
if not defined -s (
  set t2=%time%
  echo(
  echo ============================
  %showTime%
  if exist stats.bf_tmp type stats.bf_tmp
)
 
:quit
del *.bf_tmp
exit /b %err%
 
:genAllChr
::This code creates 256 1 byte files, one for each possible byte value.
::This is encoded in a macro way to be called asynchronously with start cmd /c
::Teamwork of carlos, penpen, aGerman, dbenham, einstein1969
::Tested under Win7 and XP
setlocal disableDelayedExpansion
set ^"genchr=(^
for /l %%N in (%%A !cnt! 255) do (^
  if %%N equ 26 (^
    copy /y nul + nul /a 26.bf_chr /a ^>nul^
  ) else (if %%N geq 35 if %%N leq 126 if %%N neq 61 (^
    ^<nul set /p "=!ascii:~%%N,1!" ^>%%N.bf_chr^
  ))^&^
  if not exist %%N.bf_chr (^
    makecab /d compress=off /d reserveperdatablocksize=26 /d reserveperfoldersize=%%N %%A.tmp %%N.bf_chr ^>nul^&^
    type %%N.bf_chr ^| ((for /l %%n in (1 1 38) do pause)^>nul^&findstr "^^" ^>%%N.temp)^&^
    ^>nul copy /y %%N.temp /a %%N.bf_chr /b^&^
    del %%N.temp^
  )^
))^&^
del %%A.tmp^"
del /f /q /a *bf.chr >nul 2>&1
set "ascii=                                   #$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
set /a cnt=number_of_processors
if %cnt% lss 1 set cnt=1
if %cnt% gtr 256 set cnt=256
set /a "end=cnt-1"
for /l %%A in (0 1 %end%) do (
  type nul >%%A.tmp
  if %%A equ %end% (
    cmd /q /v:on /c "%genchr%"
  ) else (
    start "" /b cmd /q /v:on /c "%genchr%"
  )
)
:genAllChr.check
for /l %%N in (0 1 %end%) do if exist %%N.tmp goto :genAllChr.check
exit /b


Message size limit reached - version 5.8 is in next post.
Last edited by dbenham on 25 Jul 2014 17:18, edited 3 times in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: BrainF*** Interpreter in Batch

#47 Post by dbenham » 23 Jul 2014 20:55

... continued from prior post.

EDIT: Fixed the definition of LF - removed the errant space
version 5.8

Code: Select all

::BF.BAT - BrainF*** interpreter in Batch, dbenham version 5.8
::
:: Usage:
::
::   bf sourceFile [-option [value]]...
::
:: Options
::
::   -i Filename  = read Input from file Filename.
::
::   -z           = <Ctrl-Z> terminates keyboard input (forces EOF).
::
::   -e Value     = EOF value: Sets value read into memory upon End-Of-File.
::                  Default is no change to memory.
::
::   -ee          = terminate with Error if attempt is made to read past EOF.
::
::   -eo          = terminate with Error if +/- results in Overflow.
::
::   -em          = terminate with Error if < yields a negative Memory address.
::
::   -ea          = terminate with Error on Any fault. Same as -ee -eo -em
::
::   -s           = Silent mode. Don't print initialization messages, elapsed
::                  times, or run statistics. Also disables the -l option.
::
::   -d Filename  = Dump the parsed op codes to Filename.
::                  Use con to print to screen.
::
::   -m Filename  = write the interpreter Macro to Filename.
::                  Use con to print to screen.
::
::   -t           = Trace mode: Show op address/value, memory address/value
::                  each cycle.
::
::   -# Count     = Enables # op: Show current state. Count specifies the number
::                  of values to display before and after the current address.
::                  BUG ALERT: Some array values may not display properly if
::                  Count > 0 and array members are swapped out to disk. This is
::                  never a problem if -a is undefined.
::
::   -p           = Parse only.
::
::   -c Count     = Code memory (default = 100). Maximum amount of code allowed
::                  to reside in memory at one time. Unlimited if undefined.
::                  Excess code is swapped out to disk.
::
::   -a Count     = Array memory (default = unlimited). Maximum number of array
::                  members allowed to reside in memory at one time. Unlimited
::                  if undefined. Excess array members are swapped out to disk.
::
::   -l Count     = Limit the number of execution cycles to Count.
::                  Ignored if -s enabled. Default is no limit (undefined).
::
:: The memory array consists of unsigned bytes. Increment and decrement
:: operations simply wrap when the limits are exceeded unless -eo is specified.
::
:: The memory array is unbounded both to the left and right. If -em is specified
:: then the array is unbounded to the right only.
::
:: In theory, the code pointer can range from 0 to 2147483647,
:: and the memory pointer can range from -2147483648 to 2147483647.
:: But performance will become unbearably long before those limits are
:: reached.
::
:: BF.BAT can only read input from a file supplied as a command line option, or
:: the keyboard. Input will fail if data is piped into BF.BAT or if stdin is
:: redirected to a file.
::
:: During keyboard input, the <Enter> key is read as <CR>. All other inputs are
:: normal. Any byte value (except 0x00) may be entered by holding down the <Alt>
:: key and entering a decimal code on the numeric keypad. The only way to enter
:: <LF> is via <Alt> and the numeric keypad.
::
:: This code was written by Dave Benham (aka dbenham)
:: with major contributions from Antonio Perez Ayala (aka Aacini),
:: and einstein1969.
::
:: Development efforts can be traced at
:: http://www.dostips.com/forum/viewtopic.php?f=3&t=5751
 
@echo off
:: Rentry point when running the bf program
if "%~1" equ ":run" ( rem
  %bfInterpreter%
) %input%
 
:: Clear environment except for critical standard values
setlocal disableDelayedExpansion
for /f "eol== delims==" %%V in ('set^|findstr /rc:"^[^=]*!"') do set "%%V="
set "preserve= TEMP TMP PATH PATHEXT COMSPEC PRESERVE "
setlocal enableDelayedExpansion
for /f "eol== delims==" %%V in ('set') do (
  if defined %%V if "!preserve: %%V =!" equ "!preserve!" set "%%V="
)
set "preserve="
 
:: Validate parameters
if "%~1" equ "" >&2 echo Usage: %~N0 filename.ext&exit /b 1
if not exist "%~1" >&2 echo File not found: %1&exit /b 1
 
:: Define options
set options=-d:"" -s: -z: -e:"" -m:"" -ee: -eo: -em: -ea: -t: -#:"" -p: -i:"" -c:100 -a:"" -l:""
 
:: Set option defaults
for %%O in (%options%) do for /f "tokens=1,* delims=:" %%A in ("%%O") do set "%%A=%%~B"
 
:loop Parse the option arguments
if not "%~2"=="" (
  set "test=!options:*%~2:=! "
  if "!test!"=="!options! " (
    echo Error: Invalid option %~2
    exit /b 1
  ) else if "!test:~0,1!"==" " (
    set "%~2=1"
  ) else (
    set "%~2=%~3"
    shift /2
  )
  shift /2
  goto :loop
)
 
:: Begin macro defnitions --------------
 
:: Define LF to contain a linefeed (0x0A) character
set ^"LF=^

^" The above empty line is critical - DO NOT REMOVE

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

set showTime=(%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t1: =0^!"^) do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  for /f "tokens=1-4 delims=:.," %%a in ("^!t2: =0^!"^) do set /a "t2=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"%\n%
  set /a "tDiff=t2-t1"%\n%
  if ^^!tDiff^^! lss 0 set /a tDiff+=24*60*60*100%\n%
  set /a "s=tDiff/100, f=tDiff+100"%\n%
  echo   Elapsed time = ^^!s^^!.^^!f:~-2^^! s%\n%
^)
 
if defined -ea (
  set -eo=1
  set -em=1
  set -ee=1
)
 
set "+=i+=^!m."
set "-=i-=^!^!m."
 
if defined -eo (
  set "e=)&-256"
  set "o=over|=(m."
  set "inc=^^^!o^^^!^^^!m^^^!+=^!count^!^^^!e^^^!"
  set "dec=^^^!o^^^!^^^!m^^^!-=^!count^!^^^!e^^^!"
  set overError=%\n%
      if ^^!over^^! neq 0 ^>^&2 echo ^^!LF^^!ERROR: Overflow at i=%%I m=%%A^&exit 1
) else (
  set "e==(m."
  set "o=)&255"
  set "inc=m.^^^!m^^^!^^^!e^^^!^^^!m^^^!+^!count^!^^^!o^^^!"
  set "dec=m.^^^!m^^^!^^^!e^^^!^^^!m^^^!-^!count^!^^^!o^^^!"
)
 
if defined -em (
  set leftError=%\n%
      if ^^!m^^! lss 0 ^>^&2 echo ^^!LF^^!ERROR: Invalid memory access at i=%%I m=%%A^&exit 1
)
 
if defined -t set trace=%\n%
    echo  c.%%I=^^!c.%%I^^!   m.%%A=^^!m.%%A^^!
 
if defined -e set ^"setEOF=set /a "m.%%A=!-e!"^"
 
if defined -z set eofMacros=1
if defined -i set eofMacros=1
if defined eofMacros (
  set "ifNotEOF=if not defined EOF"
  set ifSub=else if "^!key:~-1^!" equ "^!SUB^!" (%\n%
        endlocal^&endlocal%\n%
        set EOF=1%\n%
        !setEOF!%\n%
      ^)
  if defined -ee (
    set "elsePastEOF= else >&2 echo ^!LF^!ERROR: Read past EOF at i=%%I m=%%A&exit 1 "
  ) else if defined -e (
    set "elsePastEOF= else !setEOF! "
  )
)
 
if defined -i (
  set ^"getInput=else if "%%B" equ "," ( for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set /p "key="%\n%
      if defined key (set /a "m.%%A=0x^!key^!"^) else ( !setEOF!%\n%
        set EOF=1%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
) else for %%F in (findstr.exe) do (
  set ^"getInput=else if "%%B" equ "," (for /l %%N in (1 1 %%C^) do !ifNotEOF! (%\n%
      set "key="%\n%
      set "CtrlC=1"%\n%
      setlocal disableDelayedExpansion%\n%
      for /f "delims=" %%K in (%\n%
        '^^^^^""%%~dp$path:Fxcopy.exe" /w "%~f0" "%~f0" 2^^^^^>nul^^^^^"'%\n%
      ^) do if not defined key (set "key=%%K"^) else set "CtrlC="%\n%
      setlocal enableDelayedExpansion%\n%
      if defined CtrlC (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=3"%\n%
      ^) else if "^!key^!" equ "^!line1^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=10"%\n%
      ^) else if "^!key:~-1^!" equ "^!CR^!" (%\n%
        endlocal^&endlocal%\n%
        set "m.%%A=13"%\n%
      ^) !ifSub! else (%\n%
        ^>key.bf_tmp echo(^^!key:~-1^^!%\n%
        endlocal^&endlocal%\n%
        for /f "delims=." %%K in ('^^^^^""%%~$path:F" /lg:key.bf_tmp *.bf_chr^^^^^"'^) do set "m.%%A=%%K"%\n%
      ^)%\n%
    ^)!elsePastEOF!^) ^"
)
 
if defined -# (
 
  set #=#
 
  set #Parse=else if "%%b" equ "#" (%\n%
        if defined lastCmd (%\n%
          set /a i+=1%\n%
          ^>c.^^!i^^!.bf_tmp echo S ^^^^!lastCmd:~0,-1^^^^!%\n%
          set "lastCmd="%\n%
        ^)%\n%
        set /a "i+=1, #Found=1, clr=0"%\n%
        ^>c.^^!i^^!.bf_tmp echo #%\n%
      ^)
 
  if !-#! equ 0 (
    set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      echo  c.^^!next^^!=^^!msg^^!  m.%%A=^^!m.%%A^^!%\n%
    ^) ^"
  ) else set ^"#Exec=else if "%%B" equ "#" (%\n%
      set /a next=i+1, m1=m-!-#!, m2=m-1, m3=m+1, m4=m+!-#!%\n%
      set "msg="%\n%
      ^<c.^^!next^^!.bf_tmp set /p "msg="%\n%
      set "msg=c.^!next^!=^!msg^!  m=%%A: "%\n%
      for /l %%N in (^^!m1^^! 1 ^^!m2^^!^) do set "msg=^!msg^!| ^!m.%%N^! "%\n%
      set "msg=^!msg^!< ^!m.%%A^! >"%\n%
      for /l %%N in (^^!m3^^! 1 ^^!m4^^!^) do set "msg=^!msg^! ^!m.%%N^! |"%\n%
      echo  ^^!msg^^!%\n%
    ^) ^"
 
)
 
if not defined -s (
 
  set "getCycles=, cycles+=1"
 
  set "getCodePageFaults=, codePageFaults+=1"
 
  set "getArrayStats=, aSize+=1"
 
  set "getArrayPageFaults=, arrayPageFautls+=1"
 
  set showStats=%\n%
      ^>stats.bf_tmp (echo   cycles=^^!cycles^^!  codePageFaults=^^!codePageFaults^^!%\n%
      echo   arraySize=^^!aSize^^!  arrayPageFaults=^^!arrayPageFaults^^!^)
 
  if defined -l set getCycles=!getCycles! ^& if ^^!cycles^^! equ !-l! (!showStats!%\n%
      exit 0%\n%
    ^)
)
 
if defined -c set codePaging=%\n%
  if not defined c.%%I if exist c.%%I.bf_tmp (%\n%
    if ^^!cdCnt^^! equ !-c! (%\n%
      for /f "delims==" %%A in ('set c.') do set "%%A="%\n%
      set cdCnt=0%\n%
    )%\n%
    set /a cdCnt+=1!getCodePageFaults!%\n%
    ^<c.%%I.bf_tmp set /p "c.%%I="%\n%
  )
 
if defined -a (
  set arrayPaging=if not defined m.%%A (%\n%
      if ^^!aCnt^^! equ !-a! for /f "tokens=1,2 delims==" %%a in ('set m.'^) do (%\n%
        ^>%%a.bf_tmp echo %%b%\n%
        set %%a=%\n%
        set aCnt=0%\n%
      ^)%\n%
      if exist m.%%A.bf_tmp (%\n%
        ^<m.%%A.bf_tmp set /p m.%%A=%\n%
        set /a arrayPageFaults+=1%\n%
      ^) else set /a m.%%A=0!getArrayStats!%\n%
      set /a aCnt+=1%\n%
    ^)
) else set "arrayPaging=if not defined m.%%A set /a m.%%A=0!getArrayStats!"
 
:: End macro definition ------------------
 
:: Create character files if they don't already exist
if not defined -s (
  echo ----------------------------
  echo Begin Initialization
  echo Testing ASCII character files.
)
set "needChars="
for /l %%N in (0 1 255) do for %%F in (%%N.bf_chr) do if "%%~zF" neq "1" set "needChars=1"
if defined needChars (
  if not defined -s (
    echo Creating ASCII character files...
    set "t1=!time!"
  )
  call :genAllChr
  if not defined -s (
    set t2=!time!
    %showTime%
  )
)
 
:: Parse the input file
if not defined -i goto :skipInput
if not defined -s (
  <nul set /p "=Parsing input."
  set t1=!time!
)
if not exist "!-i!" >&2 echo ERROR: Input file "!-i!" not found & exit 1
if exist "!-i!\" >&2 echo ERROR: Input file "!-i!" not found & exit 1
copy 0.bf_chr compare.bf_tmp >nul
set /a  size=skipStart=1
for %%F in ("!-i!") do set inSize=%%~zF
for /l %%N in (1 1 32) do if !size! lss !inSize! set /a "size*=2" & type compare.bf_tmp >>compare.bf_tmp
fc /b "!-i!" compare.bf_tmp | findstr /rxc:"........: .. .." >diff.bf_tmp
>input.bf_tmp (
  for /f "tokens=1,2 delims=:[] " %%A in (diff.bf_tmp) do (
    set /a skipEnd=0x%%A
    for /l %%N in (!skipStart! 1 !skipEnd!) do echo 00
    echo %%B
    set /a skipStart=skipEnd+2
    if "!skipStart:~-3!" equ "000" <nul set /p "=." >&2
  )
  for /l %%N in (!skipStart! 1 !inSize!) do echo 00
)
set "input=<input.bf_tmp"
if not defined -s (
  set t2=!time!
  echo(
  %showTime%
)
:skipInput
 
:: Parse the code. Consecutive op codes are coalesced into one op with a count.
:: Optionally treats # as an additional show state op code (withoug coalesce).
:: Optimization collapses [-] into a special clear value op.
:: + - < > [ ] [-] are all converted into an S op followed by a SET /A expression.
:: Consecutive + - and [-] are concatenated into one op. If followed by
:: < > [ or ] then that op is also concatenated, but that is the limit for one op.
:: . and , are stored as themselves, followed by the count.
:: # is stored as itself without a count
:: Detects if input is not used so getInput can be removed from interpreter loop.
:: Treats loops at beginning or immedieately following ] as comments (ignores them)
if not defined -s (
  <nul set /p "=Parsing code."
  set "parseProgress=set /a n=(n+1^^)%%500&if ^!n^! equ 0 <nul set /p "=.""
  set "t1=!time!"
)
2>nul del c.*.bf_tmp
for %%C in (+ - "<" ">" . "," #) do set "comment%%~C=0"
set /a i=comment]=-1, sp=count=comment=0, comment[=1
cmd /u /c type %1|find /v ""|findstr "[+\-<>.,[\]!#!]" >src.bf_tmp
for /f %%b in (src.bf_tmp) do ( %parseProgress%
  if !comment! gtr 0 (
    set /a comment+=!comment%%b!
  ) else if "!lastOp!" equ "%%b" (
    set /a count+=1
  ) else (
    if defined lastOp (
      set "test="
      if !clr! equ 1 if !lastOp! equ - if !count! equ 1 set /a clr=test=2
      if not defined test set clr=0
      if "!lastOp!" equ "+" (
        set "lastCmd=!lastCmd!%inc%,"
      ) else if "!lastOp!" equ "-" (
        set "lastCmd=!lastCmd!!delim!%dec%,"
      ) else (
        set /a i+=1
        if "!lastOp!" equ ">" (
          >c.!i!.bf_tmp echo S !lastCmd!m+=!count!
        ) else if "!lastOp!" equ "<" (
          >c.!i!.bf_tmp echo S !lastCmd!m-=!count!
        ) else (
          if defined lastCmd (
            >c.!i!.bf_tmp echo S !lastCmd:~0,-1!
            set /a i+=1
          )
          >c.!i!.bf_tmp echo !lastOp! !count!
        )
        set "lastCmd="
      )
      set "lastOp="
    )
    if "%%b" equ "[" (
      if defined comment (set /a comment+=1) else (
        set /a "i+=1, sp+=1, clr=1"
        set /a "stack.!sp!=i"
        set "stackCmd.!sp!=!lastCmd!"
        set "lastCmd="
      )
    ) else if "%%b" equ "]" for %%s in (!sp!) do (
      if !clr! equ 2 (
        set "lastCmd=!stackCmd.%%s!m.^!m^!=0,"
        set /a "i-=1, sp-=1"
      ) else (
        set /a i+=1, sp-=1, count=i-stack.%%s
        >c.!i!.bf_tmp echo S !lastCmd!^^!-^^!^^!m^^!*!count!
        >c.!stack.%%s!.bf_tmp echo S !stackCmd.%%s!^^!+^^!^^!m^^!*!count!
        set "lastCmd="
      )
      set /a "clr=comment=0"
    ) %#Parse% else (
      set "comment="
      set "lastOp=%%b"
      set /a "count=1"
      if "%%b" equ "," set inputRequired=1
    )
  )
)
if defined lastOp (
  set /a i+=1
  (
    if "!lastOp!" equ "+" (
      echo S !lastCmd!%inc%
    ) else if "!lastOp!" equ "-" (
      echo S !lastCmd!%dec%
    ) else if "!lastOp!" equ ">" (
      echo S !lastCmd!m+=!count!
    ) else if "!lastOp!" equ "<" (
      echo S !lastCmd!m-=!count!
    ) else (
      echo S !lastCmd:~0,-1!
      echo !lastOp! !count!
    )
  )>c.!i!.bf_tmp
) else if defined lastCmd (
  set /a i+=1
  >c.!i!.bf_tmp echo S !lastCmd:~0,-1!
)
if not defined -s (
  set "t2=!time!"
  echo(
  %showTime%
)
 
:: Error if unbalanced brackets
if %sp% neq 0 >&2 echo ERROR: Unbalanced brackets&exit /b 1
if defined comment if "%comment%" neq "0" >&2 echo ERROR: Unbalanced brackets&exit /b 1
 
:: Dump the parsed op codes
if defined -d (for /l %%N in (0 1 !i!) do (
  <c.%%N.bf_tmp set /p "msg="
  echo c.%%N=!msg!
)) >"!-d!"
 
:: Disable code paging if code length does not exceed -cp
if not defined -c set "-c=!i!
if !i! leq !-c! (
  set "codePaging="
  for /l %%N in (0 1 !i!) do <c.%%N.bf_tmp set /p "c.%%N="
)
 
:: Undefine getInput and/or #Parse if not needed
if not defined inputRequired set "getInput="
if not defined #Found set "#Exec="
 
:: Define CR to hold <CarriageReturn> for use in input routine
for /f %%A in ('copy /Z "%~dpf0" nul') do set "CR=%%A"
 
:: Define SUB to hold <0x1A>
for /f "delims=" %%A in (26.bf_chr) do set "SUB=%%A"
 
:: Establish line one of input routine for this locale
set "line1="
for /f "delims=" %%L in (
  'echo .^|xcopy /w "%~f0" "%~f0" 2^>nul'
) do if not defined line1 set "line1=%%L"
set "line1=!line1:~0,-1!"
 
 
:: Create final interpreter macro
set bfInterpreter=^
for /f "delims==" %%V in ('set^^^^^|findstr /bv "c\. line1= CR= SUB= o= e= += -="') do set "%%V="%\n%
set /a "i=m=cdCnt=cycles=aCnt=aSize=over=0"%\n%
for /l %%. in () do for %%I in (^^!i^^!) do (!codePaging!%\n%
  for /f "tokens=1-3" %%A in ("^!m^! ^!c.%%I^!") do (%\n%
    !arrayPaging!!trace!%\n%
    if "%%B" equ "S" (%\n%
      set /a "%%C"!overError!!leftError!%\n%
    ) else if "%%B" equ "." for /l %%N in (1 1 %%C) do (%\n%
      if "^!m.%%A^!" equ "26" (set /p "=^!SUB^!" ^<nul) else type ^^!m.%%A^^!.bf_chr%\n%
    ) !getInput!!#Exec!else (!showStats!%\n%
      exit 0%\n%
    )%\n%
    set /a i+=1!getCycles!%\n%
  )%\n%
)
 
:: Print out the finalized brainF*** interpreter macro
if defined -m >"!-m!" echo bfInterpreter=!lf!!bfInterpreter:%%=%%%%!
 
 
if not defined -s (
  echo Initialization complete
  echo ============================
  set t1=%time%
)
 
(call )
if defined -p goto :quit
 
:: Launch the interpreter
cmd /v:on /c "%~f0" :run&&(call )||(call)
set "err=%errolevel%
 
if not defined -s (
  set t2=%time%
  echo(
  echo ============================
  %showTime%
  if exist stats.bf_tmp type stats.bf_tmp
)
 
:quit
del *.bf_tmp
exit /b %err%
 
:genAllChr
::This code creates 256 1 byte files, one for each possible byte value.
::This is encoded in a macro way to be called asynchronously with start cmd /c
::Teamwork of carlos, penpen, aGerman, dbenham, einstein1969
::Tested under Win7 and XP
setlocal disableDelayedExpansion
set ^"genchr=(^
for /l %%N in (%%A !cnt! 255) do (^
  if %%N equ 26 (^
    copy /y nul + nul /a 26.bf_chr /a ^>nul^
  ) else (if %%N geq 35 if %%N leq 126 if %%N neq 61 (^
    ^<nul set /p "=!ascii:~%%N,1!" ^>%%N.bf_chr^
  ))^&^
  if not exist %%N.bf_chr (^
    makecab /d compress=off /d reserveperdatablocksize=26 /d reserveperfoldersize=%%N %%A.tmp %%N.bf_chr ^>nul^&^
    type %%N.bf_chr ^| ((for /l %%n in (1 1 38) do pause)^>nul^&findstr "^^" ^>%%N.temp)^&^
    ^>nul copy /y %%N.temp /a %%N.bf_chr /b^&^
    del %%N.temp^
  )^
))^&^
del %%A.tmp^"
del /f /q /a *bf.chr >nul 2>&1
set "ascii=                                   #$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
set /a cnt=number_of_processors
if %cnt% lss 1 set cnt=1
if %cnt% gtr 256 set cnt=256
set /a "end=cnt-1"
for /l %%A in (0 1 %end%) do (
  type nul >%%A.tmp
  if %%A equ %end% (
    cmd /q /v:on /c "%genchr%"
  ) else (
    start "" /b cmd /q /v:on /c "%genchr%"
  )
)
:genAllChr.check
for /l %%N in (0 1 %end%) do if exist %%N.tmp goto :genAllChr.check
exit /b


Dave Benham
Last edited by dbenham on 25 Jul 2014 17:19, edited 1 time in total.

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: BrainF*** Interpreter in Batch

#48 Post by Aacini » 23 Jul 2014 22:05

dbenham wrote:
einstein1969 wrote:Next step? a little more speed. This is very difficult... but we will do!

The execution of Hanoi_MOD.bf is now fast but more speed is necessary for a reasonable speed (for demo porpouse)

I think we need at least a 400% performance increase to get hanoi to be almost tolerable. I don't see that happening.


I am working right now in a new version that I think could give performance increases even larger than 400% (details below). I am late on this matter because I was very busy lately developing other projects, like the ReadFormattedLine.bat subroutine (that seems to go unnoticed) or the AnsiSys.exe driver emulator. Also, I have almost ready the first version of my Edlin.bat program, an emulator of the old MS-DOS Edlin.com line-oriented text editor. I hope to have ready my next BF version soon.

dbenham wrote:
einstein1969 wrote:- It is possible merge 3 instructions into one?

Well, my code already allows indefinite concatenation of + - and [.] But they are rare, and normally pointless. Otherwise, the answer is NO. It is not possible to get the correct result if any ops are concatenated after < > [ ].

It is possible to execute large groups of BF operations in two SET /A commands as long as the group have certain structure, but the required structure is quite common in standard BF programs. Full details in the next post!

dbenham wrote:There is one special case with room for optimization. Some programs use [.] to clear a value, followed by a series of + to set a fixed positive value. My current code will perform 2 computations. But they could be merged into a single computation that sets the end value directly. Hanoi uses this construct, but it is not particularly common. I don't think the optimization will be noticeable.

If the current cell have a value greater than zero, then "[.]" is an endless loop that show the same character in the screen! I reviewed Hanoi.b file and can not found a single "[.]" construct.

Antonio

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: BrainF*** Interpreter in Batch

#49 Post by dbenham » 23 Jul 2014 22:22

Sorry Antonio. That was a typo. It should read [-] followed by a series of +. That construct definitely appears in hanoi. I edited my prior post to fix the mistake.

Regarding significant performance increases that you allude to... I'd certainly like to see it. I'm assuming the code looks dramatically different, because I don't see much room for improvement with the current structure.

Dave

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#50 Post by einstein1969 » 24 Jul 2014 00:48

Aacini wrote:
dbenham wrote:
einstein1969 wrote:Next step? a little more speed. This is very difficult... but we will do!

The execution of Hanoi_MOD.bf is now fast but more speed is necessary for a reasonable speed (for demo porpouse)

I think we need at least a 400% performance increase to get hanoi to be almost tolerable. I don't see that happening.


I am working right now in a new version that I think could give performance increases even larger than 400% (details below). I am late on this matter because I was very busy lately developing other projects, like the ReadFormattedLine.bat subroutine (that seems to go unnoticed) or the AnsiSys.exe driver emulator. Also, I have almost ready the first version of my Edlin.bat program, an emulator of the old MS-DOS Edlin.com line-oriented text editor. I hope to have ready my next BF version soon.

dbenham wrote:
einstein1969 wrote:- It is possible merge 3 instructions into one?

Well, my code already allows indefinite concatenation of + - and [.] But they are rare, and normally pointless. Otherwise, the answer is NO. It is not possible to get the correct result if any ops are concatenated after < > [ ].

It is possible to execute large groups of BF operations in two SET /A commands as long as the group have certain structure, but the required structure is quite common in standard BF programs. Full details in the next post!

dbenham wrote:There is one special case with room for optimization. Some programs use [.] to clear a value, followed by a series of + to set a fixed positive value. My current code will perform 2 computations. But they could be merged into a single computation that sets the end value directly. Hanoi uses this construct, but it is not particularly common. I don't think the optimization will be noticeable.

If the current cell have a value greater than zero, then "[.]" is an endless loop that show the same character in the screen! I reviewed Hanoi.b file and can not found a single "[.]" construct.

Antonio


Wow! :D

I'm really happy for this project! Go in my direction!

When a project reached the limit, the algorithm must be rewritten.

And then he goes rebuilt the stage performance optimization

These are other idea :idea:

- The FOR is another place for optimizing more.
It is possible queue some code for Running without necessarily using the environment

- using "endlocal & setlocal" for fast reset enviroment

einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#51 Post by einstein1969 » 24 Jul 2014 03:03

@dbenham

In version 5.6..5.8 there are SPACE characters in the definition of LF that go on error.

einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#52 Post by einstein1969 » 24 Jul 2014 10:01

Hi , I have done a resocont for the RUN/EXECUTION of last versions.

There are two type of graph: CYCLES/S, ELAPSED TIME

This is the first:
einstein1969 wrote:

Code: Select all





2400 _│                   2410
      │
      │                     █
2100 _│                     █
      │                     █
      │                     █
1800 _│                     █
      │             1690    █
      │                     █
1500 _│               █     █
      │       1440    █     █
      │               █     █
1200 _│         █     █     █
      │         █     █     █             A:Aacini  3.1
      │         █     █     █             B:dbenham 4.0
 900 _│         █     █     █             C:dbenham 4.0 -s -c 65
      │  801    █     █     █             D:dbenham 5.x* -s -c 65
      │         █     █     █             E:Aacini  4.0*
 600 _│   █     █     █     █    585
      │   █     █     █     █
      │   █     █     █     █     █
 300 _│   █     █     █     █     █
      │   █     █     █     █     █
      │   █     █     █     █     █
   0 _│___█ ____█_____█ ____█ ____█_ ______
     /    █/    █/    █/    █/    █/
    /     /     /     /     /     /
   /     A     B     C     D     E


                Run: Cycles/s  Hanoi_Mod.bf



*:The performance is the same from 5.0 and 5.8. I thinks that my explain is not good. The number of cycles is changed from version 4.0 of dbenham and version 3.1 Aacini. Then the result is that every cycle will give minus time of execution (see graph of ELAPSED TIME).


This is the second:
einstein1969 wrote:

Code: Select all




        539
 540 _│
      │  █
      │  █
 ... _............................
      │  █
      │  █
 360 _│  █
      │  █
      │  █    297
 300 _│  █
      │  █     █
      │  █     █    253
 240 _│  █     █
      │  █     █     █             A:Aacini  3.1
      │  █     █     █             B:dbenham 4.0
 180 _│  █     █     █             C:dbenham 4.0  -s -c 65
      │  █     █     █             D:dbenham 5.x* -s -c 65
      │  █     █     █             E:Aacini  4.0*
 120 _│  █     █     █
      │  █     █     █    108
      │  █     █     █     ▄
  60 _│  █     █     █     █    62
      │  █     █     █     █
      │  █     █     █     █     █
   0 _│__█ ____█ ____█_____█ ____█ _______
     /   █/    █/    █/    █/    █/
    /    /     /     /     /     /
   /    A     B     C     D     E


                Run: Elapsed time sec  Hanoi_Mod.bf




The ELAPSED TIME is the time for the interpreter to put the first DISK over the plane.

EDIT: I have applied some of my tips and i get 9% faster 5.0. (2600 Cycles/s and 99 s elapsed Time) and 40% faster Aacini 4.0 (980 Cycles/s and 37 s Elapsed Time)

einstein1969
Last edited by einstein1969 on 26 Jul 2014 12:09, edited 5 times in total.

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: BrainF*** Interpreter in Batch

#53 Post by Aacini » 25 Jul 2014 23:28

Here are my new versions. Although my last version is 4.0, I also posted here the previous version 3.1 in order to preserve the history of these developments. Version 3.1 have implemented this point: just before start the execution cycle the interpreter deletes all variables, then load the parsed code of the main program and initialize control variables. By "all variables" I mean "ALL VARIABLES", even PATH, TEMP, TMP, etc. Only when the BF program use input commands, PATH is preserved, but just the part of the path that points to xcopy.exe command, not the whole standard Path.

Version 3.1:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
if "%~2" equ ":runCode" goto %2

rem bf.bat: Brainf*ck interpreter in Batch
rem Antonio Perez Ayala

rem Version 3:   - Define subroutines from common code segments and modify the code to call they,
rem                parse each subroutine into its own file
rem                and load subroutine code on demand / delete all subs code and mem=0 on return from 1st-level subroutines

rem Version 3.1: - A couple small modifications to improve parsing speed borrowed from Dave Benham's Version 3
rem              - Discard loops placed immediately after a previous one, or at beginning of the program
rem              - Combine "[-]" ("= 0") followed by "+++..." ("+ N") in a single "= N"
rem              - Extract code for input command into a subroutine, and leave a CALL in execution cycle

if "%~1" equ "" echo Usage: %~N0 filename.ext & goto :EOF
if not exist "%~1" echo File not found: %1 & goto :EOF
if exist "%~PN1\sub0.bp" (
   echo A parsed version of this file exists
   choice /M "Do you want to use it "
   if !errorlevel! equ 1 goto Ascii
)


rem Parser phase I: Identify common subroutines, define they and shrink the code accordingly

set "input="
set /A sp=0, sub=0
rem Initialize the main program
set "block[0]="
set discardNextBlock[0]=True
rem Initialize "[-]" subroutine (set m[%mp%]=0)
set "subTable[1][0]=-"
set /P "=Extracting subroutines." < NUL
cmd /U /C type "%~1" | find /V "" | findstr "[+\-<>.,[\]]" > bf.tmp
for /F %%b in (bf.tmp) do (
   if "%%b" equ "[" (
      rem Start a new code block, don't include starting "["
      set /A sp+=1
      set "block[!sp!]="
      set "len[!sp!]=0"
   ) else if "%%b" equ "]" (
      rem Close this block
      for %%s in (!sp!) do (
         set "block=!block[%%s]!"
         set "block[%%s]="
         set "len=!len[%%s]!"
         set "len[%%s]="
      )
      set /A sp-=1
      if not defined discardNextBlock[!sp!] (
         rem Search this same block in the subroutine table
         set "subNum="
         for /F "tokens=3* delims=[]=" %%i in ('set subTable[!len!] 2^>NUL') do (
            if "!block!" equ "%%j" set subNum=%%i
         )
         if not defined subNum (
            rem Define a new subroutine with this block
            set /A sub+=1, subNum=sub
            set "subTable[!len!][!sub!]=!block!"
            set /P "=." < NUL
         )
         rem Get the length of this number (4 digits max)
         for /L %%i in (0,1,3) do if "!subNum:~%%i,1!" neq "" set len=%%i
         rem Replace this block by its subroutine number ("call") in the nesting code
         for %%s in (!sp!) do set "block[%%s]=!block[%%s]!!subNum!" & set /A len[%%s]+=len+1
      )
      set discardNextBlock[!sp!]=True
   ) else (
      rem Normal operator: insert it in current block
      for %%s in (!sp!) do (
         set "block[%%s]=!block[%%s]!%%b"
         set /A len[%%s]+=1
         set "discardNextBlock[%%s]="
      )
      if "%%b" equ "," set input=True
   )
)
del bf.tmp
echo/
if %sp% neq 0 echo ERROR: Unbalanced brackets & goto :EOF
rem Delete subroutine "[-]" (special case)
set "subTable[1][0]="
rem Transfer main program code to "subroutine 0"
set "subTable[0][0]=!block[0]!"
set "block[0]="


rem Parser phase II: Parse subroutines into their own files

set /P "=Parsing subroutines." < NUL
if not exist "%~PN1" (md "%~PN1") else del /Q "%~PN1\*.*" 2> NUL
REM set subTable[ > "%~PN1\%~N1.txt"
set "digits=0123456789"
set "AddOrSub=+-"
for /F "tokens=3* delims=[]=" %%i in ('set subTable[') do (
   rem Initialize values for *each* subroutine
   set "sub="
   set "lastOp="
   set /A ip=0, count=0
   for /F "delims=" %%b in ('cmd /U /C set /P "=%%j" ^<NUL ^| find /V ""') do (
      if "!lastOp!" equ "%%b" (
         set /A count+=1
      ) else (
         if defined lastOp (
            set /A prevIp=ip, ip+=1
            for /F "tokens=1,2" %%x in ("!lastOp! !prevIp!") do (
               rem Combine "= 0" followed by "+ N" in "= N"
               if "!AddOrSub:%%x=!" neq "%AddOrSub%" (
                  if "!s%%i[%%y]!" equ "= 0" set "ip=!prevIp!" & set "lastOp=="
               )
            )
            set "s%%i[!ip!]=!lastOp! !count!"
            set "lastOp="
            set count=0
         )
         if "!digits:%%b=!" neq "%digits%" (
            rem Digit: assemble subroutine name
            set "sub=!sub!%%b"
         ) else (
            if defined sub (
               rem Insert subroutine call
               set /A ip+=1
               if !sub! equ 0 (set "s%%i[!ip!]== 0") else set "s%%i[!ip!]=C !sub!"
               set "sub="
            )
            set "lastOp=%%b"
            set count=1
         )
      )
   )
   if defined sub (
      set /A ip+=1
      if !sub! equ 0 (set "s%%i[!ip!]== 0") else set "s%%i[!ip!]=C !sub!"
   )
   if defined lastOp (
      set /A ip+=1
      set "s%%i[!ip!]=!lastOp! !count!"
   )
   rem Insert "]" at end of subroutines
   if %%i neq 0 (
      set /A ip+=1
      set "s%%i[!ip!]=]"
   )
   ( rem Save parsed code in its file
      for /L %%n in (1,1,!ip!) do (
         echo "s%%i[%%n]=!s%%i[%%n]!"
         set "s%%i[%%n]="
      )
      rem Be sure that subroutine code ends here (insert "return")
      set /A ip+=1
      echo "s%%i[!ip!]="
   ) > "%~PN1\sub%%i.bp"
   set /P "=." < NUL
)

:Ascii
echo/
set /P "=Creating Ascii tables." < NUL
type nul > t.tmp
set "options=/d compress=off /d reserveperdatablocksize=26"
for /L %%i in (0,1,255) do (
   if not exist %%i.chr call :genchr %%i
   set /A mod=%%i %% 16
   if !mod! equ 0 set /P "=." < NUL
)
set "options="
del t.tmp temp.tmp
echo/

echo Running.
echo Start time: %time% >  "%~PN1\Log.txt"
cmd /Q /C ""%~F0" "%~1" :runCode"
echo End   time: %time% >> "%~PN1\Log.txt"
exit /B %errorlevel%

:runCode
rem Delete *all* environment variables, excepting "Path" if input routine will be used
if defined input (for %%a in (xcopy.exe) do set "Path=%%~DP$Path:a") else set "Path="
for /F "delims==" %%v in ('set') do if "%%v" neq "Path" set "%%v="
if defined Path (
   rem Define variables for input routine
   for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
   for /F "delims=" %%a in (26.chr) do set "C_Z=%%a"
   for /F "delims=" %%a in ('echo .^|xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined line1 set "line1=%%a"
   set "line1=!line1:~0,-1!"
)
rem Read main program code and start run cycle
for /F "usebackq delims=/" %%a in ("%~PN1\sub0.bp") do set %%a
rem Avoid "s" in first letter of control variables
set /A nSub=0, ip=1, ps=0, mp=0
set "tk[0]=0 0"

for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do for /F "tokens=1-3" %%a in ("!mp! !s%%i[%%j]!") do (
   if        "%%b" equ "+" (
      set /A "m[%%a]=(m[%%a]+%%c) & 255"
   ) else if "%%b" equ "-" (
      set /A "m[%%a]=(m[%%a]-%%c) & 255"
   ) else if "%%b" equ "=" (
      set "m[%%a]=%%c"
   ) else if "%%b" equ ">" (
      set /A "mp=(mp+%%c) & 4095"
   ) else if "%%b" equ "<" (
      set /A "mp=(mp-%%c) & 4095"
   ) else if "%%b" equ "]" (
      if defined m[%%a] if "!m[%%a]!" neq "0" set ip=0
   ) else if "%%b" equ "." (
      for /L %%i in (1,1,%%c) do (
         if defined m[%%a] (
            if        "!m[%%a]!" equ "10" (
                echo/
            ) else if "!m[%%a]!" equ "26" (
               set /P "=!C_Z!" < NUL
            ) else (
               type !m[%%a]!.chr
            )
         ) else (
            type 0.chr
         )
      )
   ) else if "%%b" equ "," (
      call :input
   ) else if "%%b" equ "C" ( rem Call subroutine %%c, but only if m[%%a] is not zero
      if defined m[%%a] if "!m[%%a]!" neq "0" (
         rem If the subroutine code is not defined, load it
         if not defined s%%c[1] for /F "usebackq delims=/" %%v in ("%~PN1\sub%%c.bp") do set %%v
         rem Push return address in the "stack"
         set /A ps+=1
         set "tk[!ps!]=!nSub! !ip!"
         rem Jump to the subroutine
         set /A nSub=%%c, ip=0
      )
   ) else ( rem Subroutine ends
      rem Pop return address from the "stack"
      for %%s in (!ps!) do for /F "tokens=1,2" %%x in ("!tk[%%s]!") do set /A nSub=%%x, ip=%%y
      set "tk[!ps!]="
      set /A ps-=1
      rem If return to main program, delete code of all loaded subroutines, excepting main program, and mem=0
      if !ps! equ 0 (
         for /F "delims==" %%v in ('set s') do (
            set "v=%%v"
            if "!v:~0,3!" neq "s0[" set "%%v="
         )
         for /F "tokens=1,2 delims==" %%x in ('set m[') do if "%%y" equ "0" set "%%x="
      )
      rem If main program ends, end run
      if !ps! equ -1 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
   set /A ip+=1
)


:input
      set "key="
      set "CtrlC=1"
      setlocal DisableDelayedExpansion
      for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do (
         if not defined key (set "key=%%K") else set "CtrlC="
      )
      setlocal EnableDelayedExpansion
      if defined CtrlC (
         endlocal & endlocal
         set "m[%%a]=3"
      ) else if "!key!" equ "!line1!" (
         endlocal & endlocal
         set "m[%%a]=10"
      ) else if "!key:~-1!" equ "!CR!" (
         endlocal & endlocal
         set "m[%%a]=13"
      ) else (
         > key.txt.tmp echo(!key:~-1!
         endlocal & endlocal
         for /F "delims=." %%K in ('findstr /LG:key.txt.tmp *.chr') do set "m[%%a]=%%K"
      )
exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B

Now about my version 4: the feature first suggested by einstein1969 and then developed by dbenham of combine several operators in a single SET /A command is the key to reach new speed levels, specially after dbenham added the method to execute "[" and "]" via a SET /A command. I taken this method and extended it to combine larger blocks of code in two SET /A commands, and further optimize several details. Although the resulting program runs more than twice as fast as dbenham's version 5, I expected an even larger speed increase. Here are some timings: Triangle completed in 44 seconds. Hanoi show the title immediately after start run, and display the first line less than 30 seconds after. It took ~8 minutes to show the starting position and completed the first 3 moves in 15 minutes.

Versions 3.1 and 4 also delete all memory cells with zero value at the same time that deletes program code (feature present since version 3). I don't know how this point alone affect the speed. I still have to tune-up the demand paging scheme.

EDIT 2014-07-27: There was a small bug in the original Version 4 code that prevents that "[-]" (= 0) followed by "++++" (+ N) be combined in a single (= N). The code below have the bug fixed now, but you don't need to download the whole file again; just change these lines:

Code: Select all

            rem Change "=0" followed by "+N"/"-N" to "=N"/"=-N"
            for %%p in (!sp!) do if "!s%%i[%%p]:~0,1!!s%%i[%%p]:~-2!" equ "S=0" (

... by these ones:

Code: Select all

            rem Change "=0" followed by "+N"/"-N" to "=N"/"=-N"
            for %%p in (!ip!) do if "!s%%i[%%p]:~0,1!!s%%i[%%p]:~-2!!i0!" equ "S=00" (

Version 4:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
if "%~2" equ ":runCode" goto %2

rem bf.bat: Brainf*ck interpreter in Batch
rem Antonio Perez Ayala

rem Version 3:   - Define subroutines from common code segments and modify the code to call they,
rem                parse each subroutine into its own file
rem                and load subroutine code on demand / delete all subs code and mem=0 on return from 1st-level subroutines

rem Version 3.1: - A couple small modifications to improve parsing speed borrowed from Dave Benham's version 3
rem              - Discard loops placed immediately after a previous one, or at beginning of the program
rem              - Change "[-]" (= 0) followed by "+++..." (+ N) or "---..." (- N) for a single (= N) or (= -N)
rem              - Extract code for input operator into a subroutine, and leave a CALL in execution cycle

rem Version 4:   - Use a SET /A command to execute + - > < [ ] = operators; method taken from Dave Benham's version 5
rem              - Combine groups of any number of + - > < operators in two SET /A commands
rem              - Further optimize "pure" subroutines: they have only + - > < operators and preserve mp position

if "%~1" equ "" echo Usage: %~N0 filename.ext & goto :EOF
if not exist "%~1" echo File not found: %1 & goto :EOF
if exist "%~1\" echo File not found: %1 & goto :EOF
if exist "%~PN1\sub0.bp" (
   echo A parsed version of this file exists
   choice /M "Do you want to use it "
   if !errorlevel! equ 1 goto Ascii
)


rem Parser phase I: Identify common subroutines, define they and shrink the code accordingly

set "input="
set /A sp=0, sub=0
rem Initialize the main program
set "block[0]="
set discardNextBlock[0]=True
rem Initialize "[-]" subroutine (set m[%mp%]=0)
set "subTable[2][0]=-]"
set /P "=Extracting subroutines." < NUL
cmd /U /C type "%~1" | find /V "" | findstr "[+\-<>.,[\]]" > bf.tmp
for /F %%b in (bf.tmp) do (
   if "%%b" equ "[" (
      rem Start a new code block, don't include starting "["
      set /A sp+=1
      set "block[!sp!]="
      set "len[!sp!]=0"
   ) else if "%%b" equ "]" (
      rem Close this block
      for %%s in (!sp!) do (
         set "block=!block[%%s]!]"
         set "block[%%s]="
         set /A "len=len[%%s]+1"
         set "len[%%s]="
      )
      set /A sp-=1
      if not defined discardNextBlock[!sp!] (
         rem Search this same block in the subroutine table
         set "subNum="
         for /F "tokens=3* delims=[]=" %%i in ('set subTable[!len!] 2^>NUL') do (
            if "!block!" equ "%%j" set subNum=%%i
         )
         if not defined subNum (
            rem Define a new subroutine with this block
            set /A sub+=1, subNum=sub
            set "subTable[!len!][!sub!]=!block!"
            set /P "=." < NUL
         )
         rem Get the length of this sub number (4 digits max)
         for /L %%i in (0,1,3) do if "!subNum:~%%i,1!" neq "" set len=%%i
         rem Replace this block by its subroutine number ("call") in the nesting code
         for %%s in (!sp!) do (
            set "block[%%s]=!block[%%s]!!subNum!"
            set /A len[%%s]+=len+1
         )
      )
      set discardNextBlock[!sp!]=True
   ) else (
      rem Normal operator: insert it in current block
      for %%s in (!sp!) do (
         set "block[%%s]=!block[%%s]!%%b"
         set /A len[%%s]+=1
         set "discardNextBlock[%%s]="
         if "%%b" equ "," set "input=True"
      )
   )
)
del bf.tmp
echo/
if %sp% neq 0 echo ERROR: Unbalanced brackets & goto :EOF
rem Delete subroutine "[-]" (special case)
set "subTable[2][0]="
rem Transfer main program code to "subroutine 0"
set "subTable[0][0]=!block[0]!]"
set "block[0]="


rem Parser phase II: Parse subroutines into their own files

set /P "=Parsing subroutines." < NUL
if not exist "%~PN1" (md "%~PN1") else del /Q "%~PN1\*.*" 2> NUL
set subTable[ > "%~PN1\SubTable.txt"
set "digits=0123456789"
for /F "tokens=3* delims=[]=" %%i in ('set subTable[') do (
   rem Initialize values for *each* subroutine
   set "sub="
   set "subIsPure=True"
   set "lastOp="
   set "iExpr="
   set "mExpr="
   set /A ip=0, count=0, i0=0, i1=1, iN=0, iNN=0
   for /F "delims=" %%b in ('cmd /U /C set /P "=%%j" ^<NUL ^| find /V ""') do (
      if "!lastOp!" equ "%%b" (
         set /A count+=1
      ) else (
         if defined lastOp (
            rem Change "=0" followed by "+N"/"-N" to "=N"/"=-N"
            for %%p in (!ip!) do if "!s%%i[%%p]:~0,1!!s%%i[%%p]:~-2!!i0!" equ "S=00" (
               if "!lastOp!" equ "+" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!!count!"
                  set "lastOp="
               ) else if "!lastOp!" equ "-" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!-!count!"
                  set "lastOp="
               )
            )
         )
         if defined lastOp (
            if        "!lastOp!" equ ">" (
               set "iExpr=!iExpr!i!i1!=i!i0!+!count!,"
               set /A i0+=1, i1+=1, iN+=count, iNN+=count
            ) else if "!lastOp!" equ "<" (
               set "iExpr=!iExpr!i!i1!=i!i0!-!count!,"
               set /A i1+=1, i0+=1, iN-=count, iNN-=count
            ) else if "!lastOp!" equ "+" (
               set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]+!count!)^^^&255,"
            ) else if "!lastOp!" equ "-" (
               set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]-!count!)^^^&255,"
            ) else (
               set "subIsPure="
               call :IncludeExpressions %%i
               set /A ip+=1, i0=0, i1=1, iN=0
               set "s%%i[!ip!]=!lastOp! !count!"
            )
            set "lastOp="
            set count=0
         )
         if "!digits:%%b=!" neq "%digits%" (
            rem Digit: assemble subroutine name
            set "sub=!sub!%%b"
         ) else (
            if defined sub ( rem Subroutine call
               call :IncludeExpressions %%i
               for %%p in (!ip!) do if !sub! equ 0 ( rem Change sub 0 by "m[!mp!]=0"
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,m[^^^!i0^^^!]=0"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S m[^^^!i0^^^!]=0"
                  )
               ) else ( rem Insert the subroutine call
                  set "subIsPure="
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,ip+=^^^^^^^!m[^^^!i0^^^!]"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S ip+=^^^^^^^!m[^^^!i0^^^!]"
                  )
                  set /A ip+=1
                  set "s%%i[!ip!]=C !sub!"
               )
               set /A i0=0, i1=1, iN=0
               set "sub="
            )
            set "lastOp=%%b"
            set count=1
         )
      )
   )

   rem Insert last expressions, if any
   call :IncludeExpressions %%i

   rem Adjust values for "pure" subroutines or insert "]" at end of non "pure" subroutines
   if %%i neq 0 for %%p in (!ip!) do (
      if "!subIsPure!!iNN!" equ "True0" (
         set "s%%i[1]=S m=m[^^^!i0^^^!],!s%%i[1]:~2!"
         for /L %%x in (2,1,%%p) do (
            set "s%%i[%%x]=!s%%i[%%x]:]+=]+m*!"
            set "s%%i[%%x]=!s%%i[%%x]:]-=]-m*!"
         )
      ) else (
REM      Further optimization in progress...
REM      if "!s%%i[%%p]:~0,1!!iNN!" equ "S0" (
REM         set "s%%i[%%p]=!s%%i[%%p]!,ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
REM      ) else (
            set /A ip+=1
            set "s%%i[!ip!]=S ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
REM      )
      )
   )

   ( rem Save parsed code in its file
      for /L %%n in (1,1,!ip!) do (
         echo "s%%i[%%n]=!s%%i[%%n]!"
         set "s%%i[%%n]="
      )
      rem Be sure that subroutine code ends here ("insert return")
      set /A ip+=1
      echo "s%%i[!ip!]="
   ) > "%~PN1\sub%%i.bp"
   set /P "=." < NUL
)

:Ascii
echo/
set /P "=Creating Ascii tables." < NUL
type nul > t.tmp
set "options=/d compress=off /d reserveperdatablocksize=26"
for /L %%i in (0,1,255) do (
   if not exist %%i.chr call :genchr %%i
   set /A mod=%%i %% 16
   if !mod! equ 0 set /P "=." < NUL
)
del t.tmp temp.tmp
echo/

echo Running.
echo Start time: %time% >  "%~PN1\Log.txt"
cmd /Q /C ""%~F0" "%~1" :runCode"
echo End   time: %time% >> "%~PN1\Log.txt"
exit /B %errorlevel%

:runCode
rem Delete *all* environment variables, excepting "Path" if input routine will be used
if defined input (for %%a in (xcopy.exe) do set "Path=%%~DP$Path:a") else set "Path="
for /F "delims==" %%v in ('set') do if "%%v" neq "Path" set "%%v="
if defined Path (
   rem Define variables for input routine
   for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
   for /F "delims=" %%a in (26.chr) do set "C_Z=%%a"
   for /F "delims=" %%a in ('echo .^|xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined line1 set "line1=%%a"
   set "line1=!line1:~0,-1!"
)
rem Read main program code and start run cycle
for /F "usebackq delims=/" %%a in ("%~PN1\sub0.bp") do set %%a
rem Do NOT use "s" in first letter of any control variable
set /A nSub=0, ip=1, ps=0, i0=0
set "tk[0]=0 0"

for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do for /F "tokens=1-3" %%a in ("!i0! !s%%i[%%j]!") do (
   if "%%b" equ "S" (
      set /A "%%c"
   ) else if "%%b" equ "." (
      for /L %%i in (1,1,%%c) do (
         if defined m[%%a] (
            if "!m[%%a]!" equ "10" (
                echo/
            ) else if "!m[%%a]!" equ "26" (
               set /P "=!C_Z!" < NUL
            ) else (
               type !m[%%a]!.chr
            )
         ) else (
            type 0.chr
         )
      )
   ) else if "%%b" equ "," (
      call :input %%a
   ) else if "%%b" equ "C" ( rem Call subroutine %%c
      if not defined s%%c[1] for /F "usebackq delims=/" %%v in ("%~PN1\sub%%c.bp") do set %%v
      set /A ps+=1
      set "tk[!ps!]=!nSub! !ip!"
      set /A nSub=%%c, ip=0
   ) else ( rem Subroutine ends
      for %%s in (!ps!) do for /F "tokens=1,2" %%x in ("!tk[%%s]!") do set /A nSub=%%x, ip=%%y
      set "tk[!ps!]="
      set /A ps-=1
      if !ps! equ 0 ( rem Return to main program: delete code and memory=0
         for /F "delims==" %%v in ('set s') do (
            set "v=%%v"
            if "!v:~0,3!" neq "s0[" set "%%v="
         )
         for /F "tokens=1,2 delims==" %%x in ('set m[') do if "%%y" equ "0" set "%%x="
      )
      if !ps! equ -1 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
   set /A ip+=1
)


:input mp
set "key="
set "CtrlC=1"
setlocal DisableDelayedExpansion
for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do (
         if not defined key (set "key=%%K") else set "CtrlC="
)
setlocal EnableDelayedExpansion
if defined CtrlC (
   endlocal & endlocal
   set "m[%1]=3"
) else if "!key!" equ "!line1!" (
   endlocal & endlocal
   set "m[%1]=10"
) else if "!key:~-1!" equ "!CR!" (
   endlocal & endlocal
   set "m[%1]=13"
) else (
   > key.txt.tmp echo(!key:~-1!
   endlocal & endlocal
   for /F "delims=." %%K in ('findstr /LG:key.txt.tmp *.chr') do set "m[%1]=%%K"
)
exit /B


:IncludeExpressions subNum
if defined iExpr (
   set /A ip+=1
   set "s%1[!ip!]=S !iExpr:~0,-1!"
)
if defined mExpr (
   set /A ip+=1
   set "s%1[!ip!]=S !mExpr:~0,-1!"
   set "mExpr="
)
if defined iExpr for %%p in (!ip!) do (
   if !i0! neq 1 (
      set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
   ) else if "!s%1[%%p]:~2,5!" neq "i1=i0" (
      set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
   ) else if "!s%1[%%p]:~7,1!" equ "+" (
      set "s%1[%%p]=!s%1[%%p]:i0+=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0+!"
   ) else (
      set "s%1[%%p]=!s%1[%%p]:i0-=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0-!"
   )
   set "iExpr="
)
exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B

Antonio
Last edited by Aacini on 28 Jul 2014 21:32, edited 1 time in total.

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#54 Post by einstein1969 » 26 Jul 2014 09:11

@Antonio

:shock:

You are doubled the speed! (10X from version aacini 3.1, 2X from version dbenham 5.0 .... "hanoi" tested )

I have seen your code and is very complicated to me understand the alghoritm.

I have seen that you have moved the memory M[xxxx] in the upper zone of Environment. This is GOOD! 8)

Do you have the FOR /TMP problem in your system?

There are some trick that you not have considerated that can make your code 40% or more faster.

Now the hanoi_mod.bf is fast enought. (We have reachead the 300%-400% from last version) :D

I will wait for final performance tuning the next version of dbenham.

@dbenham.
There is different speed from

Code: Select all

set /a i=!k!


and

Code: Select all

set /a i=k


if possible use the faster.


PS:I will update the graph in the previus post
EDIT:Updated!

einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#55 Post by einstein1969 » 29 Jul 2014 09:04

I have merger the best code (for achieve more speed) of dbenham 5.x BF with the Aacini 4.0 BF.

There are very little modification. The code:

bf_aacini_4.0_MOD:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
if "%~2" equ ":runCode" goto %2

rem bf.bat: Brainf*ck interpreter in Batch
rem Antonio Perez Ayala

rem Version 3:   - Define subroutines from common code segments and modify the code to call they,
rem                parse each subroutine into its own file
rem                and load subroutine code on demand / delete all subs code and mem=0 on return from 1st-level subroutines

rem Version 3.1: - A couple small modifications to improve parsing speed borrowed from Dave Benham's version 3
rem              - Discard loops placed immediately after a previous one, or at beginning of the program
rem              - Change "[-]" (= 0) followed by "+++..." (+ N) or "---..." (- N) for a single (= N) or (= -N)
rem              - Extract code for input operator into a subroutine, and leave a CALL in execution cycle

rem Version 4:   - Use a SET /A command to execute + - > < [ ] = operators; method taken from Dave Benham's version 5
rem              - Combine groups of any number of + - > < operators in two SET /A commands
rem              - Further optimize "pure" subroutines: they have only + - > < operators and preserve mp position

if "%~1" equ "" echo Usage: %~N0 filename.ext & goto :EOF
if not exist "%~1" echo File not found: %1 & goto :EOF
if exist "%~1\" echo File not found: %1 & goto :EOF
if exist "%~PN1\sub0.bp" (
   echo A parsed version of this file exists
   choice /M "Do you want to use it "
   if !errorlevel! equ 1 goto Ascii
)


rem Parser phase I: Identify common subroutines, define they and shrink the code accordingly

set "input="
set /A sp=0, sub=0
rem Initialize the main program
set "block[0]="
set discardNextBlock[0]=True
rem Initialize "[-]" subroutine (set m[%mp%]=0)
set "subTable[2][0]=-]"
set /P "=Extracting subroutines." < NUL
cmd /U /C type "%~1" | find /V "" | findstr "[+\-<>.,[\]]" > bf.tmp
for /F %%b in (bf.tmp) do (
   if "%%b" equ "[" (
      rem Start a new code block, don't include starting "["
      set /A sp+=1
      set "block[!sp!]="
      set "len[!sp!]=0"
   ) else if "%%b" equ "]" (
      rem Close this block
      for %%s in (!sp!) do (
         set "block=!block[%%s]!]"
         set "block[%%s]="
         set /A "len=len[%%s]+1"
         set "len[%%s]="
      )
      set /A sp-=1
      if not defined discardNextBlock[!sp!] (
         rem Search this same block in the subroutine table
         set "subNum="
         for /F "tokens=3* delims=[]=" %%i in ('set subTable[!len!] 2^>NUL') do (
            if "!block!" equ "%%j" set subNum=%%i
         )
         if not defined subNum (
            rem Define a new subroutine with this block
            set /A sub+=1, subNum=sub
            set "subTable[!len!][!sub!]=!block!"
            set /P "=." < NUL
         )
         rem Get the length of this sub number (4 digits max)
         for /L %%i in (0,1,3) do if "!subNum:~%%i,1!" neq "" set len=%%i
         rem Replace this block by its subroutine number ("call") in the nesting code
         for %%s in (!sp!) do (
            set "block[%%s]=!block[%%s]!!subNum!"
            set /A len[%%s]+=len+1
         )
      )
      set discardNextBlock[!sp!]=True
   ) else (
      rem Normal operator: insert it in current block
      for %%s in (!sp!) do (
         set "block[%%s]=!block[%%s]!%%b"
         set /A len[%%s]+=1
         set "discardNextBlock[%%s]="
         if "%%b" equ "," set "input=True"
      )
   )
)
del bf.tmp
echo/
if %sp% neq 0 echo ERROR: Unbalanced brackets & goto :EOF
rem Delete subroutine "[-]" (special case)
set "subTable[2][0]="
rem Transfer main program code to "subroutine 0"
set "subTable[0][0]=!block[0]!]"
set "block[0]="


rem Parser phase II: Parse subroutines into their own files

set /P "=Parsing subroutines." < NUL
if not exist "%~PN1" (md "%~PN1") else del /Q "%~PN1\*.*" 2> NUL
set subTable[ > "%~PN1\SubTable.txt"
set "digits=0123456789"
for /F "tokens=3* delims=[]=" %%i in ('set subTable[') do (
   rem Initialize values for *each* subroutine
   set "sub="
   set "subIsPure=True"
   set "lastOp="
   set "iExpr="
   set "mExpr="
   set /A ip=0, count=0, i0=0, i1=1, iN=0, iNN=0
   for /F "delims=" %%b in ('cmd /U /C set /P "=%%j" ^<NUL ^| find /V ""') do (
      if "!lastOp!" equ "%%b" (
         set /A count+=1
      ) else (
         if defined lastOp (
            rem Change "=0" followed by "+N"/"-N" to "=N"/"=-N"
            for %%p in (!ip!) do if "!s%%i[%%p]:~0,1!!s%%i[%%p]:~-2!!i0!" equ "S=00" (
               if "!lastOp!" equ "+" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!!count!"
                  set "lastOp="
               ) else if "!lastOp!" equ "-" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!-!count!"
                  set "lastOp="
               )
            )
         )
         if defined lastOp (
            if        "!lastOp!" equ ">" (
               set "iExpr=!iExpr!i!i1!=i!i0!+!count!,"
               set /A i0+=1, i1+=1, iN+=count, iNN+=count
            ) else if "!lastOp!" equ "<" (
               set "iExpr=!iExpr!i!i1!=i!i0!-!count!,"
               set /A i1+=1, i0+=1, iN-=count, iNN-=count
            ) else if "!lastOp!" equ "+" (
               set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]+!count!)^^^&255,"
            ) else if "!lastOp!" equ "-" (
               set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]-!count!)^^^&255,"
            ) else (
               set "subIsPure="
               call :IncludeExpressions %%i
               set /A ip+=1, i0=0, i1=1, iN=0
               set "s%%i[!ip!]=!lastOp! !count!"
            )
            set "lastOp="
            set count=0
         )
         if "!digits:%%b=!" neq "%digits%" (
            rem Digit: assemble subroutine name
            set "sub=!sub!%%b"
         ) else (
            if defined sub ( rem Subroutine call
               call :IncludeExpressions %%i
               for %%p in (!ip!) do if !sub! equ 0 ( rem Change sub 0 by "m[!mp!]=0"
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,m[^^^!i0^^^!]=0"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S m[^^^!i0^^^!]=0"
                  )
               ) else ( rem Insert the subroutine call
                  set "subIsPure="
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,ip+=^^^^^^^!m[^^^!i0^^^!]"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S ip+=^^^^^^^!m[^^^!i0^^^!]"
                  )
                  set /A ip+=1
                  set "s%%i[!ip!]=C !sub!"
               )
               set /A i0=0, i1=1, iN=0
               set "sub="
            )
            set "lastOp=%%b"
            set count=1
         )
      )
   )

   rem Insert last expressions, if any
   call :IncludeExpressions %%i

   rem Adjust values for "pure" subroutines or insert "]" at end of non "pure" subroutines
   if %%i neq 0 for %%p in (!ip!) do (
      if "!subIsPure!!iNN!" equ "True0" (
         set "s%%i[1]=S m=m[^^^!i0^^^!],!s%%i[1]:~2!"
         for /L %%x in (2,1,%%p) do (
            set "s%%i[%%x]=!s%%i[%%x]:]+=]+m*!"
            set "s%%i[%%x]=!s%%i[%%x]:]-=]-m*!"
         )
      ) else (
REM      Further optimization in progress...
REM      if "!s%%i[%%p]:~0,1!!iNN!" equ "S0" (
REM         set "s%%i[%%p]=!s%%i[%%p]!,ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
REM      ) else (
            set /A ip+=1
            set "s%%i[!ip!]=S ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
REM      )
      )
   )

   ( rem Save parsed code in its file
      for /L %%n in (1,1,!ip!) do (
         echo "s%%i[%%n]=!s%%i[%%n]!"
         set "s%%i[%%n]="
      )
      rem Be sure that subroutine code ends here ("insert return")
      set /A ip+=1
      echo "s%%i[!ip!]="
   ) > "%~PN1\sub%%i.bp"
   set /P "=." < NUL
)

:Ascii
echo/
set /P "=Creating Ascii tables." < NUL
type nul > t.tmp
set "options=/d compress=off /d reserveperdatablocksize=26"
for /L %%i in (0,1,255) do (
   if not exist %%i.chr call :genchr %%i
   set /A mod=%%i %% 16
   if !mod! equ 0 set /P "=." < NUL
)
del t.tmp temp.tmp
echo/

rem MOD
2>nul del %~PN1\*.bf_tmp

echo Running.
echo Start time: %time% >  "%~PN1\Log.txt"
cmd /Q /C ""%~F0" "%~1" :runCode"
echo End   time: %time% >> "%~PN1\Log.txt"

exit /B %errorlevel%

:runCode
rem Delete *all* environment variables, excepting "Path" if input routine will be used
if defined input (for %%a in (xcopy.exe) do set "Path=%%~DP$Path:a") else set "Path="
for /F "delims==" %%v in ('set') do if "%%v" neq "Path" set "%%v="
if defined Path (
   rem Define variables for input routine
   for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
   for /F "delims=" %%a in (26.chr) do set "C_Z=%%a"
   for /F "delims=" %%a in ('echo .^|xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined line1 set "line1=%%a"
   set "line1=!line1:~0,-1!"
)
rem Read main program code and start run cycle
for /F "usebackq delims=/" %%a in ("%~PN1\sub0.bp") do set %%a
rem Do NOT use "s" in first letter of any control variable
set /A nSub=0, ip=1, ps=0, i0=0, $=0
set "tk[0]=0 0"

for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do (

 rem MOD
 if not defined s%%i[%%j] (
   if !$! equ 150 call :swapout %1
   set /a $+=1
   if exist %~PN1\s%%i[%%j].bf_tmp <%~PN1\s%%i[%%j].bf_tmp set /p "s%%i[%%j]="
 )

 for /F "tokens=1-3" %%a in ("!i0! !s%%i[%%j]!") do (
   if "%%b" equ "S" (
      set /A "%%c"
   ) else if "%%b" equ "." (
      for /L %%i in (1,1,%%c) do (
         if defined m[%%a] (
            if "!m[%%a]!" equ "10" (
                echo/
            ) else if "!m[%%a]!" equ "26" (
               set /P "=!C_Z!" < NUL
            ) else (
               type !m[%%a]!.chr
            )
         ) else (
            type 0.chr
         )
      )
   ) else if "%%b" equ "," (
      call :input %%a
   ) else if "%%b" equ "C" ( rem Call subroutine %%c
      if not defined s%%c[1] for /F "usebackq delims=/" %%v in ("%~PN1\sub%%c.bp") do set %%v
      set /A ps+=1
      set "tk[!ps!]=!nSub! !ip!"
      set /A nSub=%%c, ip=0
   ) else ( rem Subroutine ends
      for %%s in (!ps!) do for /F "tokens=1,2" %%x in ("!tk[%%s]!") do set /A nSub=%%x, ip=%%y
      set "tk[!ps!]="
      set /A ps-=1
      if !ps! equ 0 ( rem Return to main program: delete code and memory=0
         for /F "delims==" %%v in ('set s') do (
            set "v=%%v"
            if "!v:~0,3!" neq "s0[" set "%%v="
         )
         for /F "tokens=1,2 delims==" %%x in ('set m[') do if "%%y" equ "0" set "%%x="
      )
      if !ps! equ -1 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
   set /A ip+=1
 )
)

rem MOD
:swapout
(     rem swap out
     for /f "delims==" %%A in ('set s') do (
       if not exist %~PN1\%%A.bf_tmp >%~PN1\%%A.bf_tmp echo !%%A!
       rem set "v=%%A"
       rem if "!v:~0,3!" neq "s0[" set "%%A="
       set "%%A="
     )
     set $=0
exit /b )

:input mp
set "key="
set "CtrlC=1"
setlocal DisableDelayedExpansion
for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do (
         if not defined key (set "key=%%K") else set "CtrlC="
)
setlocal EnableDelayedExpansion
if defined CtrlC (
   endlocal & endlocal
   set "m[%1]=3"
) else if "!key!" equ "!line1!" (
   endlocal & endlocal
   set "m[%1]=10"
) else if "!key:~-1!" equ "!CR!" (
   endlocal & endlocal
   set "m[%1]=13"
) else (
   > key.txt.tmp echo(!key:~-1!
   endlocal & endlocal
   for /F "delims=." %%K in ('findstr /LG:key.txt.tmp *.chr') do set "m[%1]=%%K"
)
exit /B

:IncludeExpressions subNum
if defined iExpr (
   set /A ip+=1
   set "s%1[!ip!]=S !iExpr:~0,-1!"
)
if defined mExpr (
   set /A ip+=1
   set "s%1[!ip!]=S !mExpr:~0,-1!"
   set "mExpr="
)
if defined iExpr for %%p in (!ip!) do (
   if !i0! neq 1 (
      set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
   ) else if "!s%1[%%p]:~2,5!" neq "i1=i0" (
      set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
   ) else if "!s%1[%%p]:~7,1!" equ "+" (
      set "s%1[%%p]=!s%1[%%p]:i0+=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0+!"
   ) else (
      set "s%1[%%p]=!s%1[%%p]:i0-=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0-!"
   )
   set "iExpr="
)
exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B


the new graph of speed :
einstein1969 wrote:

Code: Select all




      │
 150 _│
      │
      │
 120 _│
      │ 108                        A:dbenham 5.x* -s -c 65
      │                            B:Aacini  4.0*
  90 _│  █                         C:Aacini_MOD 4.0*
      │  █
      │  █
  60 _│  █    62
      │  █
      │  █     █
  30 _│  █     █    34
      │  █     █
      │  █     █     █
   0 _│__█ ____█ ____█____
     /   █/    █/    █/
    /    /     /     /
   /    A     B     C


                Run: Elapsed time sec  Hanoi_Mod.bf




The ELAPSED TIME is the time for the interpreter to put the first DISK over the plane.

*:The performance is the same from 5.0 and 5.8. I thinks that my explain is not good. The number of cycles is changed from version 4.0 of dbenham and version 3.1 Aacini. Then the result is that every cycle will give minus time of execution (see graph of ELAPSED TIME).

EDIT: I have added in the code a lookup for the changed code:
search:

Code: Select all

rem MOD

This sign the changed/added code

einstein1969
Last edited by einstein1969 on 31 Jul 2014 00:50, edited 1 time in total.

pieh-ejdsch
Posts: 240
Joined: 04 Mar 2014 11:14
Location: germany

Re: BrainF*** Interpreter in Batch

#56 Post by pieh-ejdsch » 30 Jul 2014 09:29

I have not completely understaind how bf is to manage.

Where are Testfiles Theres are Saller than this?

system Surface (Win RT 8.1)
script dbenham v5.8
file PWQQUesT (Hanoi)

From 35.BF_CHR till 126.BF_CHR it is no once in it. The ErrorMessage only is in these files:

Code: Select all

Das System hat keinen Meldungstext fr die Meldungsnummer 0x2371 in der Meldungsdatei Application gefunden.

In the cmdLine i see this ErrorMessage more than infinity.

I think it's an error from Pipe into : anyprocess |cmd /k (anyprocess with if [or and for-Loops with anyprocess | find] )
Nothing works?

Phil
where can i get for test it the Section :genAllChr?

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: BrainF*** Interpreter in Batch

#57 Post by dbenham » 30 Jul 2014 19:16

Great stuff Antonio and einstein1969. :D

Using bf_aacini_4.0_MOD I am able to run the original hanoi at work and it completes in 2 hr 27 min :!: :shock: The modified version without the delay finishes in 2 hr 20 min.

There is still room for improvement without making major changes. I think removing the REM statements from within the loop may make a difference, but I haven't tested. I think variable "comments" like %= comment =% would be better.

Making the interpreter code dynamic to eliminate code that isn't needed for a given run definitely helps.

Finally, I don't think there is any need to clear memory when a subroutine ends if there is also code to clear memory when a threshold is reached.


I tried the code against the triangle at work, and it took 11 seconds, whereas my latest version took 8 seconds. I hard-coded an optimized interpreter for that situation (no memory management, no input, a few computations rearranged a bit, and got the timing down to 6 seconds :!:

Here is the interpreter that I used. It should be possible to dynamically build an interpreter macro that achieves this level of performance with the triangle, yet doesn't degrade (or possibly improves) the hanoi time.

Code: Select all

rem Read program code and start run cycle
for /F "delims=" %%a in ('type %~PN1\*.bp 2^>nul') do set %%a
rem Do NOT use "s" in first letter of any control variable
set /A nSub=0, ip=1, ps=0, i0=0, $=0
set "tk[0]=0 0"
 
for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do (
for /F "tokens=1-3" %%a in ("!i0! !s%%i[%%j]!") do (
   if "%%b" equ "S" (
      set /A "%%c"
   ) else if "%%b" equ "." (
      for /L %%i in (1,1,%%c) do (
         if defined m[%%a] (
            if "!m[%%a]!" equ "26" (set /P "=!C_Z!" < NUL) else type !m[%%a]!.chr
         ) else type 0.chr
      )
   ) else if "%%b" equ "C" (
      set /A ps+=1
      set "tk[!ps!]=!nSub! !ip!"
      set /A nSub=%%c, ip=0
   ) else (
      for %%s in (!ps!) do for /F "tokens=1,2" %%x in ("!tk[%%s]!") do (
        set /A nSub=%%x, ip=%%y, ps-=1
        set "tk[%%s]="
      )
      if !ps! equ -1 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
   set /A ip+=1
)
)

I don't fully understand Antonio's algorithm. I do get that the code precomputes multiple addresses so that a single SET /A can work on multiple addresses. Good idea. I don't understand the rationale when you decide to group them.

I haven't had time to work on my code. But I have some ideas. Using the technique of precomputing addresses, it should be possible to have no more than two SET /A per "block" (excepting when input and/or output is needed). One SET /A to precompute the addresses, and the 2nd do do the work. And multiple commands can be concatenated into a single line of code, and parsed/iterated using a simple FOR. There really is only a need for a new line of code whenever a [ or ] is reached.

Such a scheme would have a major impact on my trace and # features. I haven't figured out how (or if) I would continue to support them.


Dave Benham

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#58 Post by einstein1969 » 31 Jul 2014 09:53

@dbenham & Aacini
You can store all the files created in the subdirectory including *.chr and *.bf_tmp temporary?

I tried this - LostKingdom but it seems not to work.
The version of Aacini MOD not optimizes the load, I'm not able to implement, so I have not tested if it works. It use too much time.

@dbenham
You can save a version of the program already parsed as Aacini do?

einstein1969

einstein1969
Expert
Posts: 961
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: BrainF*** Interpreter in Batch

#59 Post by einstein1969 » 02 Aug 2014 18:16

dbenham wrote:There is still room for improvement without making major changes. I think removing the REM statements from within the loop may make a difference, but I haven't tested. I think variable "comments" like %= comment =% would be better.

I have tried with comment like %= comm. =% but i get syntax error :?:

Finally, I don't think there is any need to clear memory when a subroutine ends if there is also code to clear memory when a threshold is reached.

Go from 34s to 33s.

I have tried using CALL for moving out the code from the main cycle and
merging the set /A IP+=1 and
remove the REM and I get from 33s to 23s :!: (about 30% off)

this is the piece of code changed:

Code: Select all

for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do (
 if not defined s%%i[%%j] (
   if !$! equ 150 call :swapout %1
   set /a $+=1
   if exist %~PN1\s%%i[%%j].bf_tmp <%~PN1\s%%i[%%j].bf_tmp set /p "s%%i[%%j]="
 )
 for /F "tokens=1-3" %%a in ("!i0! !s%%i[%%j]!") do (
   if "%%b" equ "S" (
      set /A "%%c, ip+=1"
   ) else if "%%b" equ "." (
      call :print %%a %%c
   ) else if "%%b" equ "," (
      call :input %%a
   ) else if "%%b" equ "C" (
      if not defined s%%c[1] for /F "usebackq delims=/" %%v in ("%~PN1\sub%%c.bp") do set %%v
      set /A ps+=1
      set "tk[!ps!]=!nSub! !ip!"
      set /A nSub=%%c, ip=1
   ) else (
      for %%s in (!ps!) do for /F "tokens=1,2" %%x in ("!tk[%%s]!") do set /A nSub=%%x, ip=%%y
      set "tk[!ps!]="
      set /A ps-=1, ip+=1
      if !ps! equ -1 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
 )
)

:swapout
(    rem swap out
     for /f "delims==" %%A in ('set s') do (
       if not exist %~PN1\%%A.bf_tmp >%~PN1\%%A.bf_tmp echo !%%A!
       set "%%A="
     )
     set $=0
exit /b )

:print
(     for /L %%i in (1,1,%2) do (
         if defined m[%1] (
            if "!m[%1]!" equ "10" (
                echo/
            ) else if "!m[%1]!" equ "26" (
               set /P "=!C_Z!" < NUL
            ) else (
               type !m[%1]!.chr
            )
         ) else (
            type 0.chr
         )
      )
      set /A ip+=1
exit/b )


plus added set /A ip+=1 at subroutine :input

einstein1969

Aacini
Expert
Posts: 1914
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: BrainF*** Interpreter in Batch

#60 Post by Aacini » 03 Aug 2014 17:18

Here it is the new version 4.1 of my BF interpreter, but before present it I want to state a brief description about the optimization method used in version 4 and the scheme used to define subroutines in version 3.

Suppose this BF code segment:

Code: Select all

>>> ++ >>>> ----- <<<<<<< - 

You may execute previous segment with a SET /A command equivalent to the next one:

Code: Select all

set /A m[mp+3]=(m[mp+3]+2)&255, m[mp+3+4]=(m[mp+3+4]-5)&255, m[mp+3+4-7]=(m[mp+3+4-7]-1)&255

However, it is not possible to directly perform additions in the indices of the variables, so previous command must be divided in two parts: one SET /A command for the different indices (executed first) and another one for the affected elements:

Code: Select all

set /A i1=i0+3, i2=i1+4, i3=i2-7
set /A m[!i1!]=(m[!i1!]+2)&255, m[!i2!]=(m[!i2!]-5)&255, m[!i3!]=(m[!i3!]-1)&255, i0=i3

The last i0=i3 preserve the final position of the pointer. Previous code may be further optimized a little eliminating i3 index:

Code: Select all

set /A i1=i0+3, i2=i1+4, i0=i2-7
set /A m[!i1!]=(m[!i1!]+2)&255, m[!i2!]=(m[!i2!]-5)&255, m[!i0!]=(m[!i0!]-1)&255

... and further optimized another small step if the final index is equal to the starting one, as happens in this example:

Code: Select all

set /A i1=i0+3, i2=i1+4
set /A m[!i1!]=(m[!i1!]+2)&255, m[!i2!]=(m[!i2!]-5)&255, m[!i0!]=(m[!i0!]-1)&255


Next steps are based on this concept:

A "subroutine" is a BF code segment enclosed in square brackets.

That is to say, "subroutine = BF loop". This division makes possible several optimization points:

  • All loops with the same code are replaced by a "subroutine call" that takes just one instruction, and the subroutine code is defined just one time. This point makes the code smaller.
  • The starting "[" is executed before the subroutine is called. Because the call takes one instruction, the offset for the ip pointer just needs to be 0 if the subroutine is called, or 1 if it is not. This is an example of the way a subroutine call is encoded:

    Code: Select all

    set /A ip+=1
    set "s%%i[!ip!]=S ip+=^^^^^^^!m[^^^!i0^^^!]"
    set /A ip+=1
    set "s%%i[!ip!]=C !subNum!"

  • The "]" at end of the subroutine also set ip equal 1 to return to the beginning of the loop, or equal 0 (to a not existent instruction) when the loop ends ("subroutine return"). This is the end of any subroutine:

    Code: Select all

    set /A ip+=1
    set "s%%i[!ip!]=S ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"

A significant additional optimization step may be achieved if the subroutine just contain > < + - commands and the position of the mp pointer is preserved in the loop; I called "pure" this type of subroutines. For example, the following one:

Code: Select all

[ >>> ++ >>>> ----- <<<<<<< - ]

:arrow: If you analyze previous loop you will realize that its net effect is to add the contents of current memory cell times 2 to the cell placed three positions ahead, and subtract current cell times 5 from the cell placed seven positions ahead; at end, current cell is zeroed. This mean that this same final effect may be directly achieved with a couple SET /A commands that don't require any loop!

Code: Select all

set /A m=m[!i0!], i1=i0+3, i2=i1+4
set /A m[!i1!]=(m[!i1!]+m*2)&255, m[!i2!]=(m[!i2!]-m*5)&255, m[!i0!]=(m[!i0!]-m)&255

Note that previous subroutine (the equivalent of a BF "loop", really) have not the "]" at end, so it is always executed as two SET /A commands no matter the number in current cell when it is called.

An additional optimization step may be achieved removing the final assignment of zero to current cell: "m[!i0!]=(m[!i0!]-m)&255" and perform it when the subroutine returns:

Code: Select all

set /A m=m[!i0!], i1=i0+3, i2=i1+4
set /A m[!i1!]=(m[!i1!]+m*2)&255, m[!i2!]=(m[!i2!]-m*5)&255

When the subroutine returns the current cell is deleted instead of zeroed: set "m[!i0!]=". Note that all subroutines ends when the current cell is zero even if they are not "pure" ones, so this point also aids to keep the environment space required for memory cells as low as possible.

The "(...)&255" part in the SET /A commands for the memory cells is required in order to keep the value of the cell in byte range, with an automatic wrap-around if the result overflow or underflow. However, the BF programs that make good use of this characteristic are rare, so we could have the option of eliminate such parts and produce simpler SET /A commands that execute faster:

Code: Select all

set /A m=m[!i0!], i1=i0+3, i2=i1+4
set /A m[!i1!]+=m*2, m[!i2!]-=m*5

I implemented all previously described optimization points in my new version 4.1, including the possibility of eliminate the "(...)&255" parts via the new /O switch. I think that the SET /A commands produced by this version when the /O switch is included represent the most efficient way to execute BF code in a Batch file.

I used version 4.1 with all the optimizations points mentioned so far to run Hanoi.b modified and the program took 13 mins 3 secs to complete the first three moves, so it run 13% faster than version 4. Then, I added the modifications suggested by einstein1969 in his last post that basically consists in extract the code of output command from the run cycle, execute ip+=1 in each part and eliminate the single SET /A ip+=1 command at end. With these changes the program took 12 exact minutes, that is, 20% faster than version 4 :!: Finally, I completed a couple additional modifications in order to manage the subroutine stack in the same SET /A commands used in the call/return parts and this time the program took 10 mins 58 sec, that is, 27% faster :shock: Please, note that I did NOT used the "if !$! equ 150 call :swapout %1" paging method in my program! :wink:

As I said when I presented the version 3, the subroutine division allows to implement a more efficient paging scheme because the "page faults" can not occur at random times, so it is not necessary to check for that situation in each executed instruction, but just at specific points; also, in this scheme there are a series of parameters that allows to tune-up the paging mechanism in more precise ways. The experience accumulated so far makes me think that the paging method based on subroutine division will result in significant execution speed gains. We will see the result in the next version!

Version 4.1:

Code: Select all

@echo off
setlocal EnableDelayedExpansion
if "%~2" equ ":runCode" goto %2

rem bf.bat: Brainf*ck interpreter in Batch
rem Antonio Perez Ayala

rem Version 3:   - Define subroutines from common code segments and modify the code to call they,
rem                parse each subroutine into its own file
rem                and load subroutine code on demand / delete all subs code and mem=0 on return from 1st-level subroutines

rem Version 3.1: - A couple small modifications to improve parsing speed borrowed from Dave Benham's version 3
rem              - Discard loops placed immediately after a previous one, or at beginning of the program
rem              - Change "[-]" (= 0) followed by "+++..." (+ N) or "---..." (- N) for a single (= N) or (= -N)
rem              - Extract code for input operator into a subroutine, and leave a CALL in execution cycle

rem Version 4:   - Use a SET /A command to execute + - > < [ ] = operators; method taken from Dave Benham's version 5
rem              - Combine groups of any number of + - > < operators in two SET /A commands
rem              - Further optimize "pure" subroutines: they have only + - > < operators and preserve mp position,
rem                so they are executed in two direct SET /A commands *with no cycles*

rem Version 4.1: - Further optimize the two SET /A expressions to make they as short as possible
rem              - Delete current cell when subroutines ends
rem              - Use /O switch to don't limit current cell to 0..255 range (faster execution)
rem              - A couple run-time optimization details taken from last einstein1969's suggestions
rem              - Execute subroutine call/return via SET /A commands

if "%~1" equ "" (
   echo Usage: %~N0 filename.ext [/O]
   echo/
   echo - Cells are byte elements with values 0-255 that wrap-around at both ends.
   echo   If /O switch is given, cells becomes 32-bits signed elements. The size of
   echo   cell elements is stored in the parsed version of the file, so /O switch
   echo   don't cares if you execute a previously parsed version of a BF file.
   echo/
   echo - The cell pointer is a 32-bits signed number.
   echo/
   echo - In output, an Ascii character 10 is written as a CR+LF pair.
   echo/
   echo - In keyboard input, Enter key is read as an Ascii character 10
   echo   and Ctrl-C indicate the End Of File: the current cell is not changed.
   goto :EOF
)
if not exist "%~1" echo File not found: %1 & goto :EOF
if exist "%~1\" echo File not found: %1 & goto :EOF
if /I "%~2" neq "/O" (
   set "switch/O="
   set /A start=31, end=29
) else (
   set "switch/O=True"
   set /A start=15, end=13
)
if not exist "%~PN1\sub0.bp" goto Parse
echo A parsed version of this file exists
choice /M "Do you want to use it "
if %errorlevel% equ 1 (
   set /P "line1=" < "%~PN1\sub0.bp"
   set !line1!
   goto Ascii
)


rem Parser phase I: Identify common subroutines, define they and shrink the code accordingly.
rem                 A "subroutine" is a code block enclosed in square brackets (a BF loop).

:Parse
set "input="
set /A sp=0, sub=0
rem Initialize the main program
set "block[0]="
set discardNextBlock[0]=True
rem Initialize "[-]" subroutine (set m[%mp%]=0)
set "subTable[2][0]=-]"
set /P "=Extracting subroutines." < NUL
cmd /U /C type "%~1" | find /V "" | findstr "[+\-<>.,[\]]" > bf.tmp
for /F %%b in (bf.tmp) do (
   if "%%b" equ "[" (
      rem Start a new code block, don't include starting "["
      set /A sp+=1
      set "block[!sp!]="
      set "len[!sp!]=0"
   ) else if "%%b" equ "]" (
      rem Close this block
      for %%s in (!sp!) do (
         set "block=!block[%%s]!]"
         set "block[%%s]="
         set /A "len=len[%%s]+1"
         set "len[%%s]="
      )
      set /A sp-=1
      if not defined discardNextBlock[!sp!] (
         rem Search this same block in the subroutine table
         set "subNum="
         for /F "tokens=3* delims=[]=" %%i in ('set subTable[!len!] 2^>NUL') do (
            if "!block!" equ "%%j" set subNum=%%i
         )
         if not defined subNum (
            rem Define a new subroutine with this block
            set /A sub+=1, subNum=sub
            set "subTable[!len!][!sub!]=!block!"
            set /P "=." < NUL
         )
         rem Get the length of this subroutine number (4 digits max)
         for /L %%i in (0,1,3) do if "!subNum:~%%i,1!" neq "" set len=%%i
         rem Replace this block by its subroutine number ("call") in the nesting code
         for %%s in (!sp!) do (
            set "block[%%s]=!block[%%s]!!subNum!"
            set /A len[%%s]+=len+1
         )
      )
      set discardNextBlock[!sp!]=True
   ) else (
      rem Normal operator: insert it in current block
      for %%s in (!sp!) do (
         set "block[%%s]=!block[%%s]!%%b"
         set /A len[%%s]+=1
         set "discardNextBlock[%%s]="
         if "%%b" equ "," set "input=True"
      )
   )
)
del bf.tmp
echo/
if %sp% neq 0 echo ERROR: Unbalanced brackets & goto :EOF
rem Delete subroutine "[-]" (special case)
set "subTable[2][0]="
rem Transfer main program code to "subroutine 0"
set "subTable[0][0]=!block[0]!]"
set "block[0]="


rem Parser phase II: Parse subroutines into their own files

set /P "=Parsing subroutines." < NUL
if not exist "%~PN1" (md "%~PN1") else del /Q "%~PN1\*.*" 2> NUL
set subTable[ > "%~PN1\SubTable.txt"
set "digits=0123456789"
for /F "tokens=3* delims=[]=" %%i in ('set subTable[') do (
   rem Initialize values for *each* subroutine
   set "sub="
   set "subIsPure=True"
   set "lastOp="
   set "iExpr="
   set "mExpr="
   set /A ip=0, count=0, i0=0, i1=1, iN=0
   for /F "delims=" %%b in ('cmd /U /C set /P "=%%j" ^<NUL ^| find /V ""') do (
      if "!lastOp!" equ "%%b" (
         set /A count+=1
      ) else (
         if defined lastOp (
            rem Change "=0" followed by "+N"/"-N" to "=N"/"=-N"
            for %%p in (!ip!) do if "!s%%i[%%p]:~0,1!!s%%i[%%p]:~-2!!i0!" equ "S=00" (
               if "!lastOp!" equ "+" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!!count!"
                  set "lastOp="
               ) else if "!lastOp!" equ "-" (
                  set "s%%i[%%p]=!s%%i[%%p]:~0,-1!-!count!"
                  set "lastOp="
               )
            )
         )
         if defined lastOp (
            if        "!lastOp!" equ ">" (
               set "iExpr=!iExpr!i!i1!=i!i0!+!count!,"
               set /A i0+=1, i1+=1, iN+=count
            ) else if "!lastOp!" equ "<" (
               set "iExpr=!iExpr!i!i1!=i!i0!-!count!,"
               set /A i1+=1, i0+=1, iN-=count
            ) else if "!lastOp!" equ "+" (
               if not defined switch/O (
                  set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]+!count!)^^^&255,"
               ) else (
                  set "mExpr=!mExpr!m[^^^!i!i0!^^^!]+=!count!,"
               )
            ) else if "!lastOp!" equ "-" (
               if not defined switch/O (
                  set "mExpr=!mExpr!m[^^^!i!i0!^^^!]=(m[^^^!i!i0!^^^!]-!count!)^^^&255,"
               ) else (
                  set "mExpr=!mExpr!m[^^^!i!i0!^^^!]-=!count!,"
               )
            ) else (
               set "subIsPure="
               call :IncludeExpressions %%i
               set /A ip+=1, i0=0, i1=1, iN=0
               set "s%%i[!ip!]=!lastOp! !count!"
            )
            set "lastOp="
            set count=0
         )
         if "!digits:%%b=!" neq "%digits%" (
            rem Digit: assemble subroutine name
            set "sub=!sub!%%b"
         ) else (
            if defined sub ( rem Subroutine call
               set "subIsPure="
               call :IncludeExpressions %%i
               for %%p in (!ip!) do if !sub! equ 0 ( rem Change sub 0 by "m[!mp!]=0"
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,m[^^^!i0^^^!]=0"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S m[^^^!i0^^^!]=0"
                  )
               ) else ( rem Insert the subroutine call
                  if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
                     set "s%%i[%%p]=!s%%i[%%p]!,ip+=^^^^^^^!m[^^^!i0^^^!]"
                  ) else (
                     set /A ip+=1
                     set "s%%i[!ip!]=S ip+=^^^^^^^!m[^^^!i0^^^!]"
                  )
                  set /A ip+=1
                  set "s%%i[!ip!]=C !sub!"
               )
               set /A i0=0, i1=1, iN=0
               set "sub="
            )
            set "lastOp=%%b"
            set count=1
         )
      )
   )

   rem Insert last expressions, if any
   call :IncludeExpressions %%i

   if %%i neq 0 for %%p in (!ip!) do (
      rem If the subroutine is "pure": adjust values for "one-cycle execution"
      if "!subIsPure!!iN!" equ "True0" (
         set "s%%i[1]=S m=m[^^^!i0^^^!],!s%%i[1]:~2!"
         if not defined switch/O (
            set "s%%i[2]=!s%%i[2]:]+=]+m*!"
            set "s%%i[2]=!s%%i[2]:]-=]-m*!"
            set "s%%i[2]=!s%%i[2]:m*1)=m)!"
         ) else (
            set "s%%i[2]=!s%%i[2]:~2!"
            set "s="
            for /L %%? in (1,1,!i0!) do for /F "tokens=1,2* delims==," %%x in ("!s%%i[2]!") do (
               setlocal DisableDelayedExpansion
               if "%%y" equ "1" (
                  set "term=%%x=m"
               ) else (
                  set "term=%%x=m*%%y"
               )
               set "rest=%%z"
               setlocal EnableDelayedExpansion
               set "term=!term:^=^^^!"
               set "rest=!rest:^=^^^!"
               for /F "tokens=1,2" %%m in ("!term! !rest!") do (
                  endlocal & endlocal
                  set "s=!s!%%m,"
                  set "s%%i[2]=%%n"
               )
            )
            set "s%%i[2]=S !s:~0,-1!"
         )
         if "!s%%i[%%p]:~0,11!" equ "S m[^^^!i0^^^!]" (
             set "s%%i[%%p]=S !s%%i[%%p]:~%start%!"
         ) else (
             set "s%%i[%%p]=!s%%i[%%p]:~0,-%end%!"
         )
      ) else ( rem Subroutine is not "pure": insert "]" at end
         if "!s%%i[%%p]:~0,1!!iN!" equ "S0" (
            set "s%%i[%%p]=!s%%i[%%p]!,ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
         ) else (
            set /A ip+=1
            set "s%%i[!ip!]=S ip=^^^^^^^!^^^^^^^!m[^^^!i0^^^!]-1"
         )
      )
   )

   ( rem Save parsed code in its file
      if %%i equ 0 echo "input=%input%"
      for /L %%n in (1,1,!ip!) do (
         echo "s%%i[%%n]=!s%%i[%%n]!"
         set "s%%i[%%n]="
      )
      rem Be sure that main program code ends here ("insert exit")
      if %%i equ 0 (
         set /A ip+=1
         echo "s%%i[!ip!]="
      )
   ) > "%~PN1\sub%%i.bp"
   set /P "=." < NUL
)

:Ascii
echo/
set /P "=Creating Ascii tables." < NUL
type nul > t.tmp
set "options=/d compress=off /d reserveperdatablocksize=26"
for /L %%i in (0,1,255) do (
   if not exist %%i.chr call :genchr %%i
   set /A mod=%%i %% 16
   if !mod! equ 0 set /P "=." < NUL
)
del t.tmp temp.tmp
echo/

echo Running.
echo Start time: %time% >  "%~PN1\Log.txt"
cmd /Q /C ""%~F0" "%~1" :runCode"
echo End   time: %time% >> "%~PN1\Log.txt"
exit /B %errorlevel%

:runCode
rem Delete *all* environment variables, excepting "Path" if input routine will be used
if defined input (for %%a in (xcopy.exe) do set "Path=%%~DP$Path:a") else set "Path="
for /F "delims==" %%v in ('set') do if "%%v" neq "Path" set "%%v="
if defined Path (
   rem Define variables for input routine
   for /F %%a in ('copy /Z "%~F0" NUL') do set "CR=%%a"
   for /F "delims=" %%a in (26.chr) do set "C_Z=%%a"
   for /F "delims=" %%a in ('echo .^|xcopy /W "%~F0" "%~F0" 2^>NUL') do if not defined line1 set "line1=%%a"
   set "line1=!line1:~0,-1!"
)
rem Read main program code and start run cycle
for /F "usebackq delims=/" %%a in ("%~PN1\sub0.bp") do set %%a
rem Do NOT use "s" in first letter of any control variable
set /A nSub=0, ip=1, ps=1, i0=0, tk[0]=0

for /L %%? in () do for /F "tokens=1,2" %%i in ("!nSub! !ip!") do for /F "tokens=1-3" %%a in ("!i0! !s%%i[%%j]!") do (
   if "%%b" equ "S" (
      set /A "%%c, ip+=1"
   ) else if "%%b" equ "." (
      call :output %%a %%c
   ) else if "%%b" equ "," (
      call :input %%a
   ) else if "%%b" equ "C" (
      if not defined s%%c[1] for /F "usebackq delims=/" %%v in ("%~PN1\sub%%c.bp") do set %%v
      set /A "tk[!ps!]=(nSub<<10)+ip, ps+=1, nSub=%%c, ip=1"
   ) else (
      set "m[%%a]="
      set /A ps-=1
      set /A "nSub=tk[!ps!]>>10, ip=(tk[!ps!]&0x3FF)+1"
      if !ps! equ 1 (
         for /F "delims==" %%v in ('set s') do (
            set "v=%%v"
            if "!v:~0,3!" neq "s0[" set "%%v="
         )
         for /F "tokens=1,2 delims==" %%x in ('set m[') do if "%%y" equ "0" set "%%x="
      )
      if !ps! equ 0 del key.txt.tmp 2>NUL & exit !m[%%a]!
   )
)


:output mp times
(for /L %%i in (1,1,%2) do (
   if defined m[%1] (
      if "!m[%1]!" equ "10" (
         echo/
      ) else if "!m[%1]!" equ "26" (
         set /P "=!C_Z!" < NUL
      ) else (
         type !m[%1]!.chr
      )
   ) else (
      type 0.chr
   )
)
set /A ip+=1
exit /B
)


:input mp
set "key="
set "CtrlC=1"
setlocal DisableDelayedExpansion
for /F "delims=" %%K in ('xcopy /W "%~F0" "%~F0" 2^>NUL') do (
   if not defined key (set "key=%%K") else set "CtrlC="
)
setlocal EnableDelayedExpansion
if defined CtrlC (
   endlocal & endlocal
) else if "!key!" equ "!line1!" (
   endlocal & endlocal
   set "m[%1]=10"
) else if "!key:~-1!" equ "!CR!" (
   endlocal & endlocal
   set "m[%1]=10"
) else (
   > key.txt.tmp echo(!key:~-1!
   endlocal & endlocal
   for /F "delims=." %%K in ('findstr /LG:key.txt.tmp *.chr') do set "m[%1]=%%K"
)
set /A ip+=1
exit /B


:IncludeExpressions subNum
if defined iExpr ( rem Insert indices expression
   set /A ip+=1
   set "s%1[!ip!]=S !iExpr:~0,-1!"
)
if defined mExpr ( rem Insert memory expression
   set /A ip+=1
   set "s%1[!ip!]=S !mExpr:~0,-1!"
   set "mExpr="
)
if defined iExpr for %%p in (!ip!) do ( rem Adjust last index part
   if !i0! neq 1 (
      if !iN! neq 0 (
         set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
      ) else (
         set /A iq=ip-1
         set "s="
         set "lastPart="
         for %%q in (!iq!) do (
            for /L %%i in (1,1,!i1!) do if defined s%1[%%q] for /F "tokens=1* delims=," %%x in ("!s%1[%%q]!") do (
               set "s=!s!!lastPart!,"
               set "lastPart=%%x"
               set "s%1[%%q]=%%y"
            )
            set "s%1[%%q]=!s:~1,-1!"
         )
         for /F "delims==" %%x in ("!lastPart!") do set "s%1[%%p]=!s%1[%%p]:%%x=i0!"
      )
   ) else if "!s%1[%%p]:~2,5!" neq "i1=i0" (
      set "s%1[%%p]=!s%1[%%p]!,i0=i!i0!"
   ) else if "!s%1[%%p]:~7,1!" equ "+" (
      set "s%1[%%p]=!s%1[%%p]:i0+=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0+!"
   ) else (
      set "s%1[%%p]=!s%1[%%p]:i0-=!"
      set "s%1[%%p]=!s%1[%%p]:i1=i0-!"
   )
   set "iExpr="
)
exit /B


:genchr
REM This code creates one single byte. Parameter: int
REM Teamwork of carlos, penpen, aGerman, dbenham
REM Tested under Win2000, XP, Win7, Win8
if %~1 neq 26 (
   makecab %options% /d reserveperfoldersize=%~1 t.tmp %~1.chr > nul
   type %~1.chr | ( (for /l %%N in (1,1,38) do pause)>nul & findstr "^" > temp.tmp )
   >nul copy /y temp.tmp /a %~1.chr /b
) else (
   copy /y nul + nul /a 26.chr /a >nul
)
exit /B


Antonio

Post Reply