Page 2 of 2

Posted: Fri May 15, 2009 9:07 am
by DoubleDutch
I think that PB doesn't include unused procedures in the exe. Is this just to shorten the source code?

Posted: Sat May 16, 2009 1:29 am
by maker
DoubleDutch wrote:I think that PB doesn't include unused procedures in the exe. Is this just to shorten the source code?
No, PB compiles and includes everything you put in your source. If you keep a lot of utility code in include files (as I do), it can bulk up your executable quite a bit unless you use something like Trimmer.

I used to think the same as you, but I noticed my "Hello World" test program executable getting larger and larger as I added to my personal code library, and so Trimmer was born.

Posted: Sat May 16, 2009 1:37 am
by Seymour Clufley
Maker, I'm finding that URL isn't working again!

The best thing would be to post the source code here.

Posted: Sat May 16, 2009 1:56 am
by maker
Seymour Clufley wrote:Maker, I'm finding that URL isn't working again!

The best thing would be to post the source code here.
Here 'tis.

You'll need Srod's merge code from here: http://www.purebasic.fr/english/viewtop ... ource+file

And the merge code, in turn, needs Srod's stack code: http://www.purecoder.net/stack.zip

Code: Select all

; Trimmer 1.2 - 
; trimmer.pb
; - fixed bug that caused some false hits on variables with same name as procs
; - added ability to filter out recursive references, so no more false hits there
;   either
DataSection
    usageText:
    Data.s "==============                                                           "
    Data.s "= Trimmer 1.2                                                            "
    Data.s "==============                                                           "
    Data.s "Takes your source code set and creates two files from it:                "
    Data.s "      1) a merged file containing all code in your main file +           "
    Data.s "         all included file text                                          "
    Data.s "      2) a merged-and-trimmed file, same as #1 but with unused           "
    Data.s "         procedures replaced by comments.                                "
    Data.s "By compiling the 2nd file, you should get much smaller executables       "
    Data.s "(if you had a lot of library code in the include files).                 "
    Data.s "                                                                         "
    Data.s "    Usage:                                                               "
    Data.s "    trimmer1.2.exe mainFileName.pb                                       "
    Data.s "                                                                         "
    Data.s "    Credits:                                                             "
    Data.s "      Trimmer uses stack and file merge code by Stephen Rodriguez (srod)."
    Data.s "      The rest can be blamed on Craig Gilbert (maker).                   "
EndDataSection
Macro USAGETEXTLINES: 17 : EndMacro

Macro extensionExtension : "pp"  : EndMacro
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; support routines
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Procedure showUsageAndExit()
    Define outtext.s
    Restore usageText
    For i = 1 To USAGETEXTLINES
        Read.s outtext
        PrintN(outtext);
    Next i
    End 1
EndProcedure

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; main program
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
If Not OpenConsole()
    End 1
EndIf
;
; get target file from command line
;
If Not CountProgramParameters()
    showUsageAndExit()
EndIf
Define mainFile.s
;
; This is pretty crude but it gets around a problem with 3rd party editors and
; batch files causing nested double quoted file names. Note that it also
; means this prog can only have one command line arg.
For i = 1 To CountProgramParameters()
    mainFile = mainFile + ProgramParameter()
Next i
Define temp.s
For i = 1 To Len(mainFile)
    If Mid(mainFile, i, 1) <> Chr(34)
        temp = temp + Mid(mainFile,i,1)
    EndIf
Next i
mainFile = temp

;
; get cur dir
;
Define homeDir.s = GetCurrentDirectory()

;
; build one large file from main file + included files
;
XIncludeFile "srod_merge.pbi"
Define mergedFilename.s = mainFile+"MERGED"
Define retval = MergePBSources(mainFile, mergedFilename)
If retval <> #MPBS_OKAY
    Select retval
        Case #MPBS_INVALIDSOURCEFILE
            PrintN("file merger says: MPBS_INVALIDSOURCEFILE:")
            PrintN("                  " + mainFile)
        Case #MPBS_INSUFFICIENTMEMORY
            PrintN("file merger says: MPBS_INSUFFICIENTMEMORY")
        Case #MPBS_FILEOPENERROR
            PrintN("file merger says: MPBS_FILEOPENERROR, one of these:")
            PrintN("                  " + mainFile)
            PrintN("                  " + mergedFilename)
        Case #MPBS_INVALIDPATH
            PrintN("file merger says: MPBS_INVALIDPATH")
        Case #MPBS_INVALIDINCLUDEFILE
            PrintN("file merger says: MPBS_INVALIDINCLUDEFILE")
        Case #MPBS_INCLUDEFILENOTFOUND ;In this case, gMPBS_Include$ will contain the path\filename
                                       ;of the file which cannot be located.
            PrintN("file merger says: MPBS_INCLUDEFILENOTFOUND:")
            PrintN("                  " + gMPBS_Include$)
        Case #MPBS_NESTEDINCLUDESOVERFLOW  ;Probably a recursive use of a certain include file.
            PrintN("file merger says: MPBS_NESTEDINCLUDESOVERFLOW")
    EndSelect
    End 1
EndIf
;
; get merged file
;
Define f = ReadFile(#PB_Any, mergedFilename)
If Not f
    PrintN("Unable to open merged file: " + mergedFilename)
    End 1
EndIf

Define textLine.s
NewList lineList.s()
While Not Eof(f)
    textLine = ReadString(f)
    AddElement(lineList())
    lineList() = textLine
Wend

CloseFile(f)

If ListSize(lineList()) < 1
    PrintN("No merged file available!")
    End 1
EndIf


Procedure isIdentifierChar(ch.c)
    If (ch >= '0' And ch <= '9') Or (ch >= 'A' And ch <= 'Z') Or (ch >= 'a' And ch <= 'z') Or ch = '_' Or ch = '$'
        ProcedureReturn 1
    Else
        ProcedureReturn 0
    EndIf
EndProcedure

Procedure is_procedure_line(l.s)
    found = FindString(LCase(l), "procedure", 1)
    If Not found    :   ProcedureReturn 0 : EndIf
    
    Define ch.s = ""
    For x = 1 To found-1   ; make sure nothing before 'Procedure' except spaces or tabs
        ch = Mid(l,x,1)
        If ch <> " " And ch <> Chr(9)
            ProcedureReturn 0
        EndIf
    Next x

    ProcedureReturn found
EndProcedure


Procedure is_end_procedure_line(l.s)
    found = FindString(LCase(l), "endprocedure", 1)
    If Not found    :   ProcedureReturn #FALSE : EndIf
    
    Define ch.s = ""
    For x = 1 To found-1   ; make sure nothing before it except spaces or tabs
        ch = Mid(l,x,1)
        If ch <> " " And ch <> Chr(9)
            ProcedureReturn #FALSE 
        EndIf
    Next x
    
    For x = found + 12 To Len(l)
        ch = Mid(l,x,1)
        If ch = " " Or ch = Chr(9)
            ProcedureReturn #TRUE ; If at least one space or tab after it, assume the
                         ; rest is comments or macros that eval to comments
        Else
            ProcedureReturn #FALSE 
        EndIf 
    Next x
    
    ProcedureReturn #TRUE 
EndProcedure

;
; purge unused procedures
;
Structure procstruct
    name.s
    used.l
    *start    ; buildProcList() must set this
    *endproc    
EndStructure
Procedure buildProcList(List lines.s(), List procList.procstruct())
; expects procList to be empty.
    Define l.s
    ResetList(lines())
    For i = 1 To ListSize(lines())
        If Not NextElement(lines())
            PrintN("Error in buildProcList()! Linked list lines() error!")
            End 1
        EndIf
        l = lines()

        found = is_procedure_line(l) 
        If found
            ;
            ; get name
            ;
            found + 9
            If found > Len(l):  PrintN("Line ended too early! Merged code Line " + Str(i)) : End 1 : EndIf
            Define ch.s = Mid(l,found,1)
            If ch <> " " And ch <> Chr(9) And ch <> "."  ; Not a procedure
                Continue
            EndIf

            If ch = "."
                While ch <> " " And ch <> Chr(9) ; roll past procedure return type
                    found + 1
                    If found > Len(l):  PrintN("Line ended too early! Merged code Line " + Str(i)) : End 1 : EndIf
                    ch = Mid(l,found,1)
                Wend
            EndIf

            ch = Mid(l,found,1)
            While ch = " " Or ch = Chr(9)  ; roll past spaces and tabs
                found + 1
                ch = Mid(l,found,1)
            Wend
            

            AddElement(proclist())
            While ch <> "(" And ch <> " " And ch <> Chr(9)
                procList()\name + ch
                found + 1
                ch = Mid(l,found,1)
            Wend

            ;
            ; get line location for quick return later
            ;
            procList()\start = @lines()
        Else
            If is_end_procedure_line(l)
                procList()\endproc  = @lines()
            EndIf 
        EndIf
    Next i
EndProcedure

Procedure declarationLine(l.s)
    Define found = FindString(LCase(l), "declare",1)
    If Not found : ProcedureReturn 0 : EndIf

    If found > 1
        Define ch.l = Asc(Mid(l,found-1,1))
        If ch <> ' ' And ch <> 9
            ProcedureReturn 0
        EndIf
    EndIf
    found + 7
    If found > Len(l): ProcedureReturn 0 : EndIf
    ch = Asc(Mid(l,found,1))
    If ch <> ' ' And ch <> 9
        ProcedureReturn 0
    EndIf

    ProcedureReturn 1
EndProcedure

Macro NORMAL: 1: EndMacro
Macro INQUOTES: 0: EndMacro
Procedure markProcs(List lines.s(), List procList.procstruct())
; look for procedure references on the line l
    Define ch.s, identifierBuffer.s, mode.l = NORMAL, l.s = lines()
    For i = 1 To Len(l)
        ch = Mid(l, i, 1)
        If Asc(ch) = '"'
            If mode = NORMAL
                mode = INQUOTES
            Else
                mode = NORMAL
            EndIf
        Else
            If mode = NORMAL
                If Asc(ch) = ';' ; no need to look further on this line
                    Break
                ElseIf isIdentifierChar(Asc(ch))
                    identifierBuffer + ch
                ElseIf ch = "("  ;; ding ding ding! we may have a procedure reference

                    If Len(identifierBuffer) > 0
                        ResetList(procList())           ;; loop through all proc names
                        For x = 1 To ListSize(procList())
                            If Not NextElement(procList())
                                PrintN("Error in markProcs()! Linked list procList() error!")
                                End 1
                            EndIf

                            If LCase(procList()\name) = LCase(identifierBuffer)
                                ; filter definition lines and recursive references
                                If @lines() < procList()\start Or @lines() > procList()\endproc 
                                    If Not declarationLine(l)
                                        procList()\used + 1
                                        Break
                                    EndIf
                                EndIf
                            EndIf
                        Next x
                        identifierBuffer = ""
                    EndIf
                Else
                    identifierBuffer = ""
                EndIf
            EndIf
        EndIf
    Next i
; Not sure why I had this in here at all; no identifier hanging off the end 
; of a line is going to be a procedure name, and it ended up creating a false
; call.
;    If Len(identifierBuffer) > 0
;            ResetList(procList())
;            For x = 1 To ListSize(procList())
;                If Not NextElement(procList())
;                    PrintN("Error in markProcs()! Linked list procList() error!")
;                    End 1
;                EndIf
;                If procList()\name = identifierBuffer
;                    procList()\used + 1
;                    Break
;                EndIf
;            Next x
;    EndIf
EndProcedure

Procedure blankToEndProcedure(List lines.s(), List procList.procstruct())
    For i = ListIndex(lines()) To ListSize(lines())
        Define found.l = FindString(LCase(lines()), "endprocedure",1)
        lines() = "; unused Procedure " + procList()\name + "() was here"
        If found
            Break
        EndIf
        If Not NextElement(lines())
            PrintN("Error in blankToEndProcedure()! Linked list lines() error!")
            End 1
        EndIf
    Next i
EndProcedure

Procedure purgeUnusedProcedures(List lines.s())
    NewList procList.procstruct()
    buildProcList(lines(), procList())
    ;
    ; check each line for procedure references
    ;
    ResetList(lines())
    For i = 1 To ListSize(lines())
        If Not NextElement(lines())
            PrintN("Error in purgeUnusedProcedures()! Linked list lines() error!")
            End 1
        EndIf
        markProcs(lines(), procList())
    Next i
    ;
    ; replace unused procedure lines with blank lines
    ;
    Define foundUnused
    ResetList(procList())
    For i = 1 To ListSize(procList())
        If Not NextElement(procList())
            PrintN("Error in purgeUnusedProcedures()! Linked list procList() error!")
            End 1
        EndIf
        If Not procList()\used
            ChangeCurrentElement(lines(), procList()\start)
            blankToEndProcedure(lines(), procList())
            foundUnused + 1
        EndIf
    Next i
    ProcedureReturn foundUnused
EndProcedure

; keep purging until nothing to purge
While purgeUnusedProcedures(lineList())
Wend

;
; save to file with name formed by mainFile + "TRIMMED"
;
Define outfile.s
ResetList(lineList())
For i = 1 To ListSize(lineList())
    If Not NextElement(lineList())
        ; uh-oh, should never get here
        PrintN("My linked list got foobared!")
        End 1
    EndIf
    If Len(lineList()) > 0
        outfile + lineList() + Chr(13) + Chr(10)
    EndIf
Next i
ClearList(lineList())

Define outfilename.s = mainFile + "TRIMMED"
f = CreateFile(#PB_Any, outfilename)
If Not f
    PrintN("Unable to create trimmed file: " + outfilename)
    End 1
EndIf

WriteString(f, outfile)

CloseFile(f)

End 0

Posted: Sat May 16, 2009 4:13 am
by Demivec
DoubleDutch wrote:I think that PB doesn't include unused procedures in the exe. Is this just to shorten the source code?
PB doesn't include procedures when they are not called by an other procedures or code. Unfortunately this results in procedures being included becaused they are called in other procedures that themselves are never called. In other words let say you had procedures A through F where A calls B and C calls D and E calls A and F calls C. If nowhere in your code do you ever call A, B, C, D, E, or F in the first place, they will all be included even though they were never used. Whew!

Posted: Sat May 16, 2009 7:43 am
by DoubleDutch
Has this problem not yet been reported as a bug with optimisation?

Posted: Sat May 16, 2009 9:13 am
by Seymour Clufley
I'm pretty sure it's to do with the compiler being single-pass. It's why we have to declare procedures.