Jump to content


Photo

printf function which closely emulates perl and c++


  • Please log in to reply
9 replies to this topic

#1 panofish

panofish
  • Members
  • 176 posts

Posted 13 April 2012 - 08:39 PM

Finally, a printf function in AutoHotkey that is almost the same as printf functions in other languages.
I impressed myself with this one, but there is still room for improvement to fully implement all functionality as documented here http://www.cplusplus.../cstdio/printf/.

It would be nice to find an elegant way to pass the padchar value (currently defaulted to space) and a way to indicate "allow trunctation" (currently defaulted to no truncation).

As it is, it is very functional...
Feedback encouraged and welcome. :)

Example usage:
outputdebug DBGVIEWCLEAR   ; view output in dbgview utility from microsoft at www.sysinternals.com

outputdebug % " ID  FirstName  LastName   Number  Code" 
outputdebug % "---- ---------- ---------- ------- ----" 
outputdebug % printf("%-4s %10s %10s %7f %4s", 23, "Alan", "Lilly", 3.999, "A")
outputdebug % printf("%-4s %10s %10s %-7.3f %4s this is amazing", 23, "Joe", "Apples", 3.999, "B")
outputdebug % printf("%-4s %10s %10s %07.0f %4s", 23, "Thomas", "Sterling", 3.999, "C")

;Example formatting numbers:

outputdebug % printf("%07.1f", 145.09)   ; round 1 decimal place and zerofill 7 digits

exitapp
Library Function printf:
;============================================================
; ahk printf function:
;
; closely emulates perl and c++ printf 
; currently supports string and float
;
; string format:
;    - to right justify
;
; float format:
;    - for right justify
;    0 to indicate zerofill
;
; currently the pad character is hard coded as a space until I can find a more elegant way to pass it in
; also truncation does not occur (it would be nice to make this an option)
;============================================================ 

printf(string, prms*)    ; uses variadics to handle variable number of inputs
{
    padchar := " "
    
    for each, prm in prms
    {
        RegExMatch(string,"`%(.*?)([s|f])",m)
        
        format := m1
        type := m2
        
        if (type = "f") {    ; format float using setformat command
            
            originalformat := A_FormatFloat
            SetFormat, Float, %format%
            prm += 0.0
            SetFormat, Float, %originalformat%
           
        } else if (type = "s") {   ; format string (pad string if necessary, negative number indicates right justify)
        
            if (format < 0)
                loop % -format-StrLen(prm)
                    prm := padchar prm
            else
                loop % format-StrLen(prm)
                    prm := prm padchar
                    
        } else {
            msgbox, unknown type = %type% specified in call to printf
        }
        
        StringReplace, string, string, % "`" m, % prm     ; "%" symbol must be escaped with backtick
        
    }
        
    return string
}


#2 panofish

panofish
  • Members
  • 176 posts

Posted 09 May 2012 - 10:43 PM

Minor bugfix for properly handling integers as floats so that zero filling works properly with integers.

#3 Colmik

Colmik
  • Members
  • 112 posts

Posted 21 September 2012 - 03:54 PM

I looked for the minor bugfix on <!-- m -->http://www.panofish.net<!-- m -->, but I don't think it works.
The code would presumably be under "Computer", but while other links on the site work OK, "Computer" just brings up some photos.

Never mind, I'm about to copy the code and give it a go, and if I find the problem you mentioned, I may well be able to fix it myself.
If it formats text and numerical strings similar to printf(), it'll be a Godsend.
Sorry - a Panofishsend.

#4 panofish

panofish
  • Members
  • 176 posts

Posted 21 September 2012 - 04:37 PM

sorry... the panofish.net reference is my signature.

I updated the original post with the bug fix and the last post was meant only to be a comment that I had found and fixed a minor bug.

#5 Colmik

Colmik
  • Members
  • 112 posts

Posted 21 September 2012 - 08:25 PM

It took me a little while to suck the bones out of this. I downloaded dbgview from Microsoft and ran it, and it opened a dialogue with headers and a blank screen, but when I ran the script, nothing happened. I had never used dbgview before, so I guess there was something I didn't understand.
I looked at the script to try to figure it out, and it took a while to do so, with a generous peppering of MsgBox's, but I eventually made it work in a way that suits what I wanted to do, so for the benefit of anybody who comes after me with similarly limited experience, here's what I did ....

1. Leave the printf script as it is. It's fine.
2. Replace the upper script above - the one that uses and demonstrates the printf() - with this one :

msgstring := % " ID  FirstName  LastName   Number  Code`n---- ---------- ---------- ------- ----`n"
msgbox %msgstring%
msgstring .= printf("%-4s %10s %10s %7f %4s`n", 23, "Alan", "Lilly", 3.999, "A")
msgbox %msgstring%
msgstring .= printf("%-4s %10s %10s %-7.3f %4s this is amazing`n", 23, "Joe", "Apples", 3.999, "B")
msgbox %msgstring%
msgstring .= printf("%-4s %10s %10s %07.0f %4s`n", 23, "Thomas", "Sterling", 3.999, "C")
msgbox %msgstring%


; Example formatting numbers:

msgstring .= printf("%07.1f", 145.09)   ; round 1 decimal place and zerofill 7 digits
msgbox %msgstring%

pause

This way, you get to use the script without the use of MS's dbgview. Note that the "`n" new-line character has been included in the format strings.

BTW - message to Panofish : The reason I came looking for this was because I was considering writing it, but I would have analysed the format string bit by bit in AHK, so I was contemplating quite a long script to do it. I have never learned Regular Expression - I know it's a subject on its own - but I think now that maybe I should. I'm very impressed with the elegance of your solution to the problem. Way better than anything I could have done. Thanks for sharing it.

#6 panofish

panofish
  • Members
  • 176 posts

Posted 21 September 2012 - 08:47 PM

Thanks Colmilk!

You should try dbgview again... it is a fantastic way to debug programs.
I use it with c sharp, c++ and autohotkey all the time.
It probably will work with most any language.

dbgview can be downloaded from here:
<!-- m -->http://technet.micro... ... 96647.aspx<!-- m -->

Posted Image

#7 Colmik

Colmik
  • Members
  • 112 posts

Posted 21 September 2012 - 09:38 PM

Thanks for the tip, Panofish. I'm certainly in favour of anything that makes programming easier.
I just downloaded it from MS (and later from the link you gave), put it in a directory as an Exe file and ran it, and it opened a dialogue exactly like the one you pictured above, except that the screen part of mine is blank. All of the Options and Capture options that you have indicated are ticked by default, except "Capture Global Win32", and when I attempt to tick it, it won't let me. It says "Unable to monitor Win32 debug output: : Access is denied."
I have a 64 bit PC. Do you think that might be the reason?
Oh - I just read "Support for Windows Vista 32-bit and 64-bit". Mine's Win-7 / 64, but for most cases, that's close enough to Vista.
It also says "on Windows 2000/XP you must have administrative privilege to view kernel-mode debug output" It wouldn't surprise me if that's my problem. This is an ACER PC and it drives me up the wall with its insistance on Administrator privileges, nd it won't let me do some things.
I'm not sure how dbgview operates - my guess is that it grabs anything bound for Stdout and displays it?
Do I need to do anything else, or simply run the script and see the output?


---------------------------
OK
---------------------------

#8 panofish

panofish
  • Members
  • 176 posts

Posted 21 September 2012 - 11:12 PM

It will work on all flavors of windows.... I am using 64 bit windows 7 also.
It is most likely that you need to be sure you have full administrative privilege.

I forget what I tweaked, but my window 7 setup no longer bugs me to about privileges when install or do anything on my pc. Search the web... it's a fairly easy thing to disable and then it should work.

#9 Colmik

Colmik
  • Members
  • 112 posts

Posted 22 September 2012 - 02:58 PM

I got dbgview working, and I can see that it is going to solve a number of debug issues for which my techniques (mostly msgbox and tooltip) are cumbersome.

Instruction for others who read this entry ...
To run it, Right-click dbgview.exe and select "Run as Administrator", then you are able to tick "Capture / Capture global Win32",
then any output directed to it with OutputDebug will be displayed by it.

#10 geob

geob
  • Members
  • 1 posts

Posted 30 October 2012 - 03:05 PM

Interesting function, Panofish, thank you, here is my version


;;; - Formatting examples:
; outputdebug DBGVIEWCLEAR
; outputdebug % printf("%-4s %-10s %-10s %-7s %4-s", "ID", "FirstName", "LastName", "Number", "Code")
; outputdebug % printf("%'-'4s %'-'10s %'-'10s %'-'7s %'-'4s", "", "", "", "", "" )
; outputdebug % printf("%'.'-4d %'.'-10s %'.'-10s %'.'7d %'.'-4s", 1, "Alan", "Lilly", 3.999, "A")
; outputdebug % printf("%'.'-4d %'.'-10s %'.'-10s %'.'7.3f %'.'-4s", 2, "Joe", "Apples", 3.999, "B")
; outputdebug % printf("%'.'-4d %'.'-10s %'.'-10s %07.0f %'.'-4s", 3, "Thomas", "Sterling", 3.999, "C")
;;======================================================================
;; ahk printf function
;;
;; Tested with AutoHotkey_L:
;; 1.1.08.01
;;
;; Date:
;; 2012.10.28
;;
;; original "printf()" by panofish, this version is a modification
;;
;; closely emulates perl and c++ printf
;; currently supports float, hex, integer, string, char
;; it supports column size, justification and decimal precision
;;
;; Revision:
;; 2012.11.01
;; Added: Passing in a user defined pad-character for numbers
;; Some minor changes
;;----------------------------------------------------------------------
;;
;; case sensitive delimiter characters for:
;; char ......: "%c" [integers are converted to char, eg. 65 -> "A"]
;; integer ...: "%d","%i" [floats get rounded to the nearest integer,
;; hexadecimals are converted to integer]
;; float .....: "%f" [integers are converted to floats, eg. 1 -> 1.0]
;; float : "%fix" [float integer extraction, eg. 3.6 -> 3]
;; string ....: "%s" [treats numbers as strings]
;; hexadecimal: "%x" [0xFF -> 0xff, integers are converted to hex]
;; hexadecimal: "%X" [0xff -> 0xFF, integers are converted to HEX]
;;
;; known limitations:
;; delimiter chars "%" and "c" to "X" can not be used as pad character
;;
;; char format:
;; printf("%['<PadChar>'][-/0][TotalWidth]c", 65)
;; [-] for left justify
;; [0] to indicate zerofill
;;
;; float format:
;; printf("%['<PadChar>'][-/0][TotalWidth.DecimalPlaces]f", 3.1416)
;; [-] for left justify
;; [0] to indicate zerofill
;;
;; hexadecimal format:
;; printf("%['<PadChar>'][-][TotalWidth]x", 0xff)
;; [-] for left justify
;; literal integers will be written as hexadecimal
;;
;; integer format:
;; printf("%['<PadChar>'][-/0][TotalWidth]d", 255)
;; [-] for left justify
;; [0] to indicate zerofill
;;
;; string format:
;; printf("%['<PadChar>'][-][TotalWidth]s", "ABC")
;; [-] if TotalWidth > StringWidth for left justify
;; [-] if TotalWidth < StringWidth for truncation from string end
;; [] if TotalWidth < StringWidth for truncation from string start
;;
;;----------------------------------------------------------------------
;;
;; PADDING AND TRUNCATION FOR STRING TYPE:
;; pattern:
;; "%[-][TotalWidth][.][-][StringWidth]s"
;;
;; example usage: printf("%20s", "lowlevel")
;;
;; format examples:
;;
;; "%s" "lowlevel"
;; "%3s" "low"
;; "%-5s" "level"
;; "%20s" " lowlevel"
;; "%-20s" "lowlevel "
;; "%20.3s" " low"
;; "%-20.3s" "low "
;; "%20.-5s" " level"
;; "%-20.-5s" "level "
;;
;;
;; PASSING IN A USER DEFINED PAD-CHARACTER FOR STRING TYPE:
;; The default pad character is hard coded as a space,
;; the pad flags are hard coded as a "'", if you want to
;; change it, look for "padCharFlag1" and "padCharFlag2"
;;
;; pattern:
;; "%'<PadChar>'[-][TotalWidth][.][-][StringWidth]s"
;;
;; example usage: printf("%'.'20s", "hello, world")
;;
;; format examples:
;;
;; "%'.'s" "hello, world"
;; "%'.'5s" "hello"
;; "%'.'-5s" "world"
;; "%'.'20s" "........hello, world"
;; "%'.'-20s" "hello, world........"
;; "%'.'20.5s" "...............hello"
;; "%'.'-20.5s" "hello..............."
;; "%'.'20.-5s" "...............world"
;; "%'.'-20.-5s" "world..............."
;;
;; PASSING IN A USER DEFINED PAD-CHARACTER FOR NUMBERS:
;; example usage: printf("%'_'10.4f", 3.141592653589793)
;;
;;======================================================================

;;; uses variadics to handle variable number of inputs
printf(string, prms*)
{
; save original integer and float format
originalIntegerFormat := A_FormatInteger
originalFloatFormat := A_FormatFloat

padCharFlag1 := "'" ; pad character delimiter 1
padCharFlag2 := "'" ; pad character delimiter 2
padCharRegEx := padCharFlag1 "(.*?)" padCharFlag2

for each, prm in prms
{
padChar := " " ; default pad character

RegExMatch(string,"`%(.*?)((fix)|[s|f|c|d|i|x|X])",m)

format := m1
type := m2

; get user defined pad character
userPadCharPos := RegExMatch(format, padCharRegEx, userPadChar)
if (userPadCharPos > 0)
{
if (userPadChar1 <> "")
padChar := substr(userPadChar1, 1, 1)
StringReplace, format, format, %userPadChar%,,
}

;-------------------------------
;;; FLOAT
;-------------------------------
if (type == "f")
{
if format <>
SetFormat, Float, %format%
prm += 0.0

if (userPadCharPos > 0)
StringReplace, prm, prm, %A_Space%, %padChar%, All
}
;-------------------------------
;;; HEXADECIMAL
;-------------------------------
else if (type == "x") or (type == "X")
{
type := (type == "x") ? "h" : "H"

if prm is float
prm := round(prm)

SetFormat, Integer, %type%
prm += 0

if (format < 0)
loop % -format-StrLen(prm)
prm := prm padChar
else if (format > 0)
loop % format-StrLen(prm)
prm := padChar prm
}
;-------------------------------
;;; INTEGER
;-------------------------------
else if (type == "d") or (type == "i") or (type == "fix")
{
if (type == "d") or (type == "i") ; round number
prm := round(prm)
else if (type == "fix") ; float integer extraction
{
StringSplit, part, prm,`.
prm := part1
}

SetFormat, Integer, D

prm += 0  ; bugfix december 10. 2012

if (substr(format,1,1) = "0") ; check zero fill
padChar := "0"

if (format < 0)
loop % -format-StrLen(prm)
prm := prm padChar
else if (format > 0)
loop % format-StrLen(prm)
prm := padChar prm
}
;-------------------------------
;;; STRING
;-------------------------------
else if (type == "s") or (type == "c")
{
if (type == "c") ; integer to char
if prm is integer
prm := Chr(prm)

; split format into total width and string length
IfInString, format,`.
{
StringSplit, part, format,`.
totalWidth := part1
stringWidth := part2
}
else
{
totalWidth := 0
stringWidth := !format ? 0 : format
}

;-------------------------------
; PADDING OR TRUNCATION
;-------------------------------
if !(totalWidth)
{
prmLen := StrLen(prm)

if (abs(stringWidth) > prmLen)
{
if (stringWidth > 0) ; eg. "%20s"
loop % stringWidth-prmLen ; pad on right side
prm := padChar prm
else ; if (stringWidth < 0) ; eg. "%-20s"
loop % -stringWidth-prmLen ; pad on left side
prm := prm padChar
}
else if (abs(stringWidth) < prmLen)
{
if (stringWidth > 0) ; eg. "%5s"
prm := substr(prm, 1, stringWidth) ; cut string from start
else if (stringWidth < 0) ; eg. "%-5s"
prm := substr(prm, stringWidth+1) ; cut string from end
}
}
;-------------------------------
; PADDING AND TRUNCATION
;-------------------------------
else ; if (totalWidth)
{
if (totalWidth > 0)
{
if (stringWidth > 0) ; eg. "%20.5s"
{
prm := substr(prm, 1, stringWidth) ; cut string from start
loop % totalWidth - StrLen(prm) ; pad on left side
prm := padChar prm
}
else if (stringWidth < 0) ; eg. "%20.-5s"
{
prm := substr(prm, stringWidth+1) ; cut string from end
loop % totalWidth - StrLen(prm) ; pad on left side
prm := padChar prm
}
}
else if (totalWidth < 0)
{
if (stringWidth > 0) ; eg. "%-20.5s"
{
prm := substr(prm, 1, stringWidth) ; cut string from start
loop % -totalWidth - StrLen(prm) ; pad on right side
prm := prm padChar
}
else if (stringWidth < 0) ; eg. "%-20.-5s"
{
prm := substr(prm, stringWidth+1) ; cut string from end
loop % -totalWidth - StrLen(prm) ; pad on right side
prm := prm padChar
}
}
}
}
;-------------------------------
;;; UNKNOWN TYPE
;-------------------------------
else
{
msg =
(ltrim join`n comment
Error in RegExMatch(string,...)`n
string%A_Tab% = %string%
format%A_Tab% = %format%
type%A_Tab% = %type%`n
Unknown type = %type% specified in call to printf
)
msgbox, 4096,, %msg%
}

; replace regexmatch 'm' with formatted input value 'prm'
; "%" symbol must be escaped with backtick
StringReplace, string, string, % "`" m, %prm%
}
; restore original integer and float format
SetFormat, Integer, %originalIntegerFormat%
SetFormat, Float, %originalFloatFormat%

return string
}
;; end_of_code



Here is a starting point for a
printf-like function that works in AHK Basic:



; printf_basic - printf-like function for AHK Basic
;
; Formatting example:
;
; _ := A_Tab ; delimiter
; d := 1 ; decimal
; f := 3.1416 ; float
; s := "string" ; string
;
; outputdebug DBGVIEWCLEAR
;
; outputdebug % printf_basic("|%09d|%9.2f|%'.'-9s|" _ d _ f _ s)

;;; uses a string with 'A_Tab' as delimiter to handle
;;; format string and variable number of inputs
printf_basic(string_prms)
{
; save original integer and float format
originalIntegerFormat := A_FormatInteger
originalFloatFormat := A_FormatFloat

padCharFlag1 := "'" ; pad character delimiter 1
padCharFlag2 := "'" ; pad character delimiter 2
padCharRegEx := padCharFlag1 "(.*?)" padCharFlag2

StringGetPos, pos, string_prms, %A_Tab%
string := substr(string_prms, 1, pos)
prms := substr(string_prms, pos+2)

Loop, parse, prms, %A_Tab%
{
prm := A_LoopField

padChar := " " ; default pad character

RegExMatch(string,"`%(.*?)((fix)|[s|f|c|d|i|x|X])",m)
format := m1
type := m2

; get user defined pad character
userPadCharPos := RegExMatch(format, padCharRegEx, userPadChar)
if (userPadCharPos > 0)
{
if (userPadChar1 <> "")
padChar := substr(userPadChar1, 1, 1)
StringReplace, format, format, %userPadChar%,,
}

;-------------------------------
;;; FLOAT
;-------------------------------

; add here the rest code of 'printf' function from above
; ...
; ...

 

bugfix 10. December 2012