Author Topic: MasmBasic  (Read 144296 times)

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Re: MasmBasic
« Reply #210 on: December 16, 2014, 11:09:41 AM »
Minor update of 16 December 2014 (download)

One new feature: Extract$() has a new flag called xsScan, which serves to extract substrings sequentially using the same delimiter to the left and right. Example:

include \masm32\MasmBasic\MasmBasic.inc      ; download
  Init
  Let esi="My car,is,red,but, if it  ,was,  green,  I,  would,still,drive,it."
  .While 1
      .Break .if !Extract$(esi, ",", ",", xsScan or xsTrim)
      PrintLine "[", eax, "]"
  .Endw

  Inkey "ok?"
  Exit
end start


Output:
Code: [Select]
[My car]
[is]
[red]
[but]
[if it]
[was]
[green]
[I]
[would]
[still]
[drive]
[it.]
ok?

Gunther

  • Member
  • *****
  • Posts: 3515
  • Forgive your enemies, but never forget their names
Re: MasmBasic
« Reply #211 on: December 16, 2014, 08:37:36 PM »
Interesting feature, Jochen.  :t Well done.

Gunther
Get your facts first, and then you can distort them.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Files$() array from commandline
« Reply #212 on: December 24, 2014, 11:14:07 AM »
Update 24.12.2014 (download):

In addition to the standard file pattern, GetFiles can now take special arguments:

1. GetFiles <pattern>

  GetFiles *.asm           ; simple: puts all filenames from the current folder into the Files$() array
  GetFiles C:\Windows\System32\msvc*.dll|nt*.dll|nt*.exe
  SortFiles                ; sort the Files$() array by date, most recent files first
  For_ ebx=0 To eax-1      ; print the detailed results
      PrintLine Str$(GfSize(ebx)), Tb$, GfDate$(ebx), Spc2$, GfTime$(ebx), Tb$, Files$(ebx)
  Next



2. GetFiles CL (or GetFiles wCL for Unicode file names):

include \masm32\MasmBasic\MasmBasic.inc      ; download
  Init
  GetFiles CL              ; put filenames in the commandline into the Files$() array
  For_ ebx=0 To eax-1      ; print the results
      PrintLine Str$(GfSize(ebx)), Tb$, GfDate$(ebx), Spc2$, GfTime$(ebx), Tb$, "[", Left$(Files$(ebx), 90), "]"
  Next
  Inkey Str$("%i files found in commandline", ebx)
  Exit
end start



3. GetFiles WM_DROPFILES (in WndProc, main window must have WS_EX_ACCEPTFILES style):

  CASE WM_DROPFILES
      GetFiles WM_DROPFILES
      For_ ebx=0 To eax-1      ; show the files in the edit control
            AddWin$ hEdit=Str$(GfSize(ebx))+Tb$+GfDate$(ebx)+Spc2$+GfTime$(ebx)+Tb$+Files$(ebx)+CrLf$
      Next


#2 is handy when the user drags a group of files from Explorer over the name of the executable (i.e. the files are passed via the commandline).

#3 does the same but with files dropped over the window itself. During installation, you will see the MasmBasic guide; on top of the page, click on "File" to the left of the usual disclaimers apply, and choose "New Masm source". In the green window, click on Simple window in the upper left corner, hit F6 to build the application, then select files in Explorer and drag them over the application.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Limitation of line length to 255 chars in MASM (but not JWasm)
« Reply #213 on: December 26, 2014, 12:42:46 PM »
The update fixed an issue with a limitation of the line length in ML (all versions), which caused cryptic error messages when the user tried to initialise many global variables. In addition, more types are now possible, also for Swap.

SetGlobals & Swap: C/C++ allows direct assigning of values to variables during their initialisation. Same applies to GfaBasic, code such as
LOCAL my$="Hello", mydouble=123.456 is quite common.

A similar command called SetGlobals has been available for a while, but it choked for multiple assignments. This bug is fixed.

include \masm32\MasmBasic\MasmBasic.inc      ; download
  SetGlobals v1=111, v2=222, a$="String A", b$="String B"
  SetGlobals s1:REAL4=111.111, s2:REAL4=222.222
  SetGlobals x1:REAL10=111.111, x2:REAL10=222.222
  Init
  Dim da1() As REAL8       ; create two dynamic double arrays; note the
  Dim da2() As REAL8       ; type after Dim ... As it must be uppercase
  Print "Arrays:"
  For_ ecx=0 To 2
      mov esi, Str$("%If", ecx*1.1111111111111111+10)
      MovVal da1(ecx), esi      ; assign values to arrays - clumsy but OK for testing
      mov esi, Str$("%If", ecx*2.2222222222222222+20)
      MovVal da2(ecx), esi
      Print Str$(" da1(%i)=", ecx), Str$(da1(ecx)), Str$(" da2(%i)=", ecx), Str$(da2(ecx))
  Next
  deb 4, "original", v1, v2, $a$, $b$, s1, s2, x1, x2, da1(1), da2(2)
  Swap v1, v2
  Swap a$, b$
  Swap s1, s2
  Swap x1, x2
  Swap da1(), da2()
  Print "Arrays:"
  For_ ecx=0 To 2
      Print Str$(" da1(%i)=", ecx), Str$(da1(ecx)), Str$(" da2(%i)=", ecx), Str$(da2(ecx))
  Next
  deb 4, "swapped", v1, v2, $a$, $b$, s1, s2, x1, x2, da1(1), da2(2)
  Exit
end start


Output:[/tt]
Arrays: da1(0)=10.00000 da2(0)=20.00000 da1(1)=11.11111 da2(1)=22.22222 da1(2)=12.22222 da2(2)=24.44444
original
v1      111
v2      222
$a$             String A
$b$             String B
s1      111.1110
s2      222.2220
x1      111.111000000000000
x2      222.222000000000000
da1(1)  22.22222222222222
da2(2)  24.44444444444444
Arrays: da1(0)=20.00000 da2(0)=10.00000 da1(1)=22.22222 da2(1)=11.11111 da1(2)=24.44444 da2(2)=12.22222
swapped
v1      222
v2      111
$a$             String B
$b$             String A
s1      222.2220
s2      111.1110
x1      222.222000000000000
x2      111.111000000000000
da1(1)  11.11111111111111
da2(2)  12.22222222222222[/tt]

Attached a more detailed example, needs MasmBasic version 26 December.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
BUG WARNING
« Reply #214 on: December 27, 2014, 10:48:15 AM »
Version 26.12. choked for Sqrt(2); it's fixed with version 27.12. (download)

Examples for Sqrt():

include \masm32\MasmBasic\MasmBasic.inc
  SetGlobals sq:double
  Init
  movlps xmm0, FP8(2.0)      ; load an xmm reg using the Masm32 FP8 macro
  Print Str$("sq2=%Jf\n", Sqrt(f:xmm0))      ; it's a float, so put the f: prefix before the xmm reg
  mov eax, 3
  movd xmm0, eax      ; put a value into an xmm reg
  Print Str$("sq3=%Jf\n", Sqrt(xmm0))      ; it's integer
  fld Sqrt(4)
  fstp sq                  ; sq = sqrt(3), the manual way
  Print Str$("sq4=%Jf\n", sq)
  Print Str$("sq5=%Jf\n", Sqrt(5, sq))
  void Sqrt(6, ST(0))
  Print Str$("sq6=%Jf\n", ST(0))
  fstp st                  ; when leaving values on the FPU, do the cleanup!!
  Inkey Str$("sq7=%Jf\n", Sqrt(7, ST(0)))
  fstp st                  ; cleanup
  Exit
end start


Output:
sq2=1.414213562373095049
sq3=1.732050807568877293
sq4=2.000000000000000000
sq5=2.236067977499789805
sq6=2.449489742783178098
sq7=2.645751311064590590

Gunther

  • Member
  • *****
  • Posts: 3515
  • Forgive your enemies, but never forget their names
Re: MasmBasic
« Reply #215 on: December 27, 2014, 01:14:45 PM »
Jochen,

the cleanup of the FPU stack is always necessary. That has to do with the stack roll over.

Gunther
Get your facts first, and then you can distort them.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Re: MasmBasic
« Reply #216 on: December 27, 2014, 06:34:40 PM »
the cleanup of the FPU stack is always necessary.

That is rarely necessary. In fact, the cleanup instruction fstp st appears only 14 times in the MB source.

Quote
That has to do with the stack roll over.
While I know what you mean when writing "stack roll over", it is not a term used when dealing with the FPU. You can check that in Raymond's excellent tutorial.

Btw if you want to make a little tutorial for the noobs, you should do it properly: Open a FPU thread in the Campus.

Gunther

  • Member
  • *****
  • Posts: 3515
  • Forgive your enemies, but never forget their names
Re: MasmBasic
« Reply #217 on: December 27, 2014, 11:02:25 PM »
That is rarely necessary. In fact, the cleanup instruction fstp st appears only 14 times in the MB source.

I think it's a good style to give back the entire processor in the same state how did you get it. That reduces furthermore error sources. But it's only my point of view, no offense.

Gunther
Get your facts first, and then you can distort them.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Printing numbers with 19 digits precision
« Reply #218 on: December 29, 2014, 11:59:08 AM »
Update 29 December: Str$() can now print REAL10 numbers with up to 19 digits precision, if a) the format string contains %Jf and b) the number starts with less than 923... (which is the case for 92.3% of all numbers ;)).

deb uses this format for all REAL10 variables.

include \masm32\MasmBasic\MasmBasic.inc
.data
qwjj      QWORD 7fffffffffffffffh  ; aka +9223372036854775807
qwj2      qword -9223372036854775807
      SetGlobals numericvar1:REAL10=1.8888888888888888888888888888e9
      SetGlobals numericvar2:REAL10=9.2222222222234567895e9
      SetGlobals numericvar3:REAL10=9.2333333333333333335e9
      SetGlobals numericvar4:REAL10=1.2345678901234567890e9
      SetGlobals numericvar5:REAL10=9.2345678901234567890e9
      Init
      fldpi
      deb 4, "Values", numericvar1, numericvar2, numericvar3, PI, ST(0)
      fstp st
      mov f2sOlly, 123
      Print Str$("qword limit\t+%i\n", qwjj)      ; 4294967295 or 2147483647
      Print Str$("qword limit\t%i\n", qwj2)      ; 4294967295 or 2147483647
      fldpi
      Print Str$("PI=\t\t%Jf\n", ST(0))
      fstp st
      Print Str$("PI=\t\t%Jf\n", PI)
      Print Str$("numericvar1=\t%Jf\n", numericvar1)
      Print Str$("numericvar2=\t%Jf\n", numericvar2)
      Print Str$("numericvar3=\t%Jf\n\n", numericvar3)
      Print Str$("numericvar4=\t%Jf (number starts with 123, 19 digits)\n", numericvar4)
      Print Str$("numericvar5=\t%Jf  (number starts with 923, 18 digits)\n", numericvar5)
      Exit
end start


Output:
Values
numericvar1     1888888888.888888889
numericvar2     9222222222.223456788
numericvar3     9233333333.33333333
PI              3.141592653589793238
ST(0)           3.141592653589793238
qword limit     +9223372036854775807
qword limit     -9223372036854775807
PI=             3.141592653589793238
PI=             3.141592653589793238
numericvar1=    1888888888.888888889
numericvar2=    9222222222.223456788
numericvar3=    9233333333.33333333

numericvar4=    1234567890.123456789 (number starts with 123, 19 digits)
numericvar5=    9234567890.12345678  (number starts with 923, 18 digits)

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
MasmBasic updated
« Reply #219 on: January 02, 2015, 10:37:50 AM »
Version 2 January (download): The restriction to not assign eax to an array element is removed. This is now legal code:

include \masm32\MasmBasic\MasmBasic.inc  ; download
  Init
  Dim My$()
  invoke GetCommandLine                  ; CL$() is easier to use, but for this demo we take the API
  Let My$(1)=eax                         ; you can now assign eax to an array element
  Inkey "Commandline=[", My$(1), "]"
  Exit
end start

OPT_Arg1      Hello Forum                ; RichMasm's way to supply arguments for testing


Output: Commandline=[C:\somepath\some.exe Hello Forum]

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
19 digits precision
« Reply #220 on: January 08, 2015, 02:21:12 AM »
see updated post below
« Last Edit: January 26, 2015, 11:00:47 AM by jj2007 »

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
19 digits precision and increased range
« Reply #221 on: January 26, 2015, 11:01:50 AM »
By default, REAL10 numbers are now printed with 19 digits precision. The last digit might be one too high or too low.
The range of printable numbers is now 1.0e+-309, i.e. roughly the range of ordinary doubles.
Speedwise Str$() has improved, it is now about a factor 15 faster than the gcc ssprintf() function provided here by adeyblue (thanks!!). Attached a testbed using Str$(), MovVal and the gcc counterparts ssprintf() and sscanf().

include \masm32\MasmBasic\MasmBasic.inc      ; download version 26 January 2015
  SetGlobals MyR10:REAL10, Add01:REAL10=0.111111111111111111111
  Init
  xor ecx, ecx
  Print "digits", Tb$, "  1234567890123456789"
  .Repeat
      inc ecx
      push ecx
      fld Add01
      fimul stack      ; load ct*0.111
      pop eax
      fstp MyR10      ; store back to memory
      .if ecx<10 || ecx>=84 && ecx<99 || ecx>=994
            Print Str$("\n#%i\t", ecx), Str$(MyR10)
      .elseif ecx==10 || ecx==99
            Print      ; insert a blank line
      .endif
  .Until ecx>=1000
  Exit
end start


Output:
Code: [Select]
digits    1234567890123456789
#1      0.1111111111111111111
#2      0.2222222222222222222
#3      0.3333333333333333333
#4      0.4444444444444444444
#5      0.5555555555555555555
#6      0.6666666666666666666
#7      0.7777777777777777777
#8      0.8888888888888888888
#9      1.000000000000000000

#84     9.333333333333333333
#85     9.444444444444444444
#86     9.555555555555555555
#87     9.666666666666666666
#88     9.777777777777777777
#89     9.888888888888888888
#90     10.00000000000000000
#91     10.11111111111111111
#92     10.22222222222222222
#93     10.33333333333333333
#94     10.44444444444444444
#95     10.55555555555555555
#96     10.66666666666666667
#97     10.77777777777777778
#98     10.88888888888888889

#994    110.4444444444444444
#995    110.5555555555555555
#996    110.6666666666666667
#997    110.7777777777777778
#998    110.8888888888888889
#999    111.0000000000000000
#1000   111.1111111111111111

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
New Split$(), Join$() and Filter$() macros, bugfix for Extract$()
« Reply #222 on: February 01, 2015, 10:37:41 PM »
Update 2 February 2015 - download here:

1. Join$() converts a string array to one string:

  include \masm32\MasmBasic\MasmBasic.inc
  Init     
  Dim My$()
  Let My$(0)="Masm32"
  Let My$(1)="is"
  Let My$(2)="great"
  PrintLine "[", Join$(My$()), "]"   ; with no second arg, output is [Masm32\nis\ngreat], i.e. items separated by 13, 10
  Print "[", Join$(My$(), " "), "]"  ; shows [Masm32 is great] with " " as second arg
  Exit
  EndOfCode


For Split$() and Filter$(), see the guide during installation.

2. Bugfix for Extract$():
   When using the flag xsLineR, one byte from the right match string was duplicated. It's fixed. Usage example (this extracts structures from a C header file; you need Visual Studio installed):

include \masm32\MasmBasic\MasmBasic.inc      ; download
  Init
  My$ equ esi         ; two equates saying we'll use a register for the string to be searched
  Struct$ equ edi     ; and for the strings returned, in this case C/C++ structures
  SetReg64            ; the registry wants to cheat 32-bit programs, we cheat back ;-)
  Let My$=GetRegVal("HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\2E22551E7BF434E44AAD692811011B7C", "66FC8F6438BA7A83992B5AEB05E74E27", "NoSuchFile.bla")

  .if Exist(My$)      ; should be something like C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\srv.h
      Let My$=FileRead$(My$)
      Open "O", #1, "Structures.txt"      ; open a file for output
      xor ebx, ebx      ; reset counter
      .While 1
            ; flags: full word, include beginning of first line and end of last line, use in a loop; 100 lines max (enough for a big structure)
            mov Struct$, Extract$(My$, "struct", '}', xsFullW or xsLineL or xsLineR or xsIncR or xsLoop, 100)
            .Break .if !Struct$
            Print Struct$, CrLf$, CrLf$      ; print to console
            Print #1, Struct$, CrLf$, CrLf$      ; and to file
            inc ebx
      .Endw
      Close #1
      Print Str$("%i structures found\n", ebx)
  .endif
  Exit
end start


Output:
Code: [Select]
typedef struct srv_datetime
{       // Format for SRVDATETIME
    long dtdays;            // number of days since 1/1/1900
    unsigned long dttime;   // number 300th second since mid
} DBDATETIME;

typedef struct srv_money
{               // Format for SRVMONEY
    long mnyhigh;
    unsigned long mnylow;
} DBMONEY;

typedef struct dbdatetime4
{       // Format for SRVDATETIM4
    unsigned short numdays; // number of days since 1/1/1900
    unsigned short nummins; // number of minutes sicne midnight
} DBDATETIM4;

typedef struct dbnumeric
{       // Format for SRVNUMERIC,SRVNUMERICN,SRVDECIMAL,SRVDECIMALN
        BYTE precision;
        BYTE scale;
        BYTE sign;
        BYTE val[MAXNUMERICLEN];
} DBNUMERIC;

4 structures found
« Last Edit: February 02, 2015, 11:45:44 AM by jj2007 »

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
Bugfix for 64-bit Windows
« Reply #223 on: February 06, 2015, 11:12:50 AM »
As noted on other occasions, the 64-bit versions of Windows have the bad habit to destroy registers xmm0 ... xmm5 in API calls, nota bene: in 32-bit code on a 64-bit OS. Since 32-bit code on 32-bit Windows does not trash them, and many people may have relied on this benign but undocumented behaviour, there is a nice potential for bugs that come out of the blue when running the same application the first time on a 64-bit OS.

For performance reasons, MasmBasic does not preserve the xmm regs for each and every API call used, but its own algos do preserve the xmm regs.

This worked fine in general, but I ran into a strange case that needed fixing:

include \masm32\MasmBasic\MasmBasic.inc      ; download the fixed version of 6 February 2015
  Init
  Dim MyDouble() As REAL8      ; same for QWORD

  MovVal f:xmm0, "1234.5678"      ; fill low quad of xmm0 with float
  movlps MyDouble(0), xmm0
  deb 4, "Element 0", f:xmm0, MyDouble(0)

  MovVal f:xmm0, "1234.5678"
  movlps MyDouble(1), xmm0      ; <<<<<<< this instruction misbehaved
  deb 4, "Element 1", f:xmm0, MyDouble(1)

  MovVal f:xmm0, "1234.5678"
  movlps MyDouble(2), xmm0
  deb 4, "Element 2", f:xmm0, MyDouble(2)
  Exit
end start

Old buggy version:
Element 0
f:xmm0          1234.567800000000
MyDouble(0)     1234.567800000000

Element 1
f:xmm0          0
MyDouble(1)     0.0

Element 2
f:xmm0          1234.567800000000
MyDouble(2)     1234.567800000000


The reason for this unexpected behaviour was that the simple instruction movlps MyDouble(1), xmm0 has a nice feature under the hood: MyDouble() is a dynamic array. When an index hits the boundary, new memory is being requested via HeapReAlloc. And that request trashed the xmm regs - all six of them. I'd like to trash Windows, but Linux doesn't seem any better, so the solution was to preserve the six regs around the HeapReAlloc call. It works correctly from version 6 Feb 2015 onwards.

jj2007

  • Moderator
  • Member
  • *****
  • Posts: 7559
  • Assembler is fun ;-)
    • MasmBasic
64-bit Windows trashes XMM regs
« Reply #224 on: February 10, 2015, 11:20:07 AM »
Update 10 Feb (download): Since I ran often into problems with Windows' bad habit to trash xmm0 ... xmm5 in its 64-bit versions, I decided to address this Microsoft 'feature' more systematically.

Inter alia, ordinary message boxes and setting the timestamp for a FileWrite did not work in 32-bit code on Win64. This is now fixed, and the simple unzipper below will work as expected:

include \masm32\MasmBasic\MasmBasic.inc
  Init
  Let esi=CL$()
  .if !Instr_(esi, ".zip", 1)
      MsgBox 0, esi, "Not a zip archive?", MB_OK
      Exit
  .endif
  UnzipInit esi            ; expects a filename, returns a comment (if present); edx returns #records
  .if Sign?
      Print eax      ; print an error message
  .else
      For_ ecx=0 To edx-1      ; #files returned in edx
            Let esi=Files$(ecx)
            .if Rinstr(esi, "/")      ; zipfiles use forward slashes
                  xchg eax, esi      ; we don't display the full path, and we
                  inc esi      ; don't use folder names for extraction!!
            .endif
            PrintLine Str$(GfSize(ecx)), Tb$, GfDate$(ecx), Spc2$, GfTime$(ecx), Tb$, esi
            Inkey "Extract? (yes/no/quit) ", Cr$
            .Break .if eax=="q" || eax==VK_ESCAPE
            .if eax=="y"
                  FileWrite esi, UnzipFile(ecx), GfLastWrite(ecx)      ; setting the timestamp works now on Win7-64, too
            .endif
      Next
      UnzipExit
  .endif
  Exit
end start