Do procedures really have to appear first?

Just starting out? Need help? Post your questions and find answers here.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Do procedures really have to appear first?

Post by Oso »

I'm curious how procedures need to be defined at the beginning of the programme code. It feels logical to put them at the end of the routine (like a GOSUB), but it doesn't work, because the compiler hasn't seen the procedure at the time it's called. Is this the correct approach with PureBasic?

I developed an application with C# during the past year or two and I tend to regard PureBasic's procedure's as being C# functions, or at least very similar to them. Do I need to put procedures at the beginning of PureBasic code, or is there another way? The below is an example. It will compile only if I move the procedure to the top.

Code: Select all

OpenConsole()

; We need to move Procedure displayname(myname.s) here

Print("Hello, what is your name? ")
Define name.s
name.s=Input()

displayname(name.s)
End

Procedure displayname(myname.s)
  PrintN("Hello "+myname.s)
EndProcedure
In programming, I'm just mindful that it's possible for functions to call each other. For instance you could have a function (A) that calls function (B) and function (B) calls (A). That's presumably not possible with procedures, because the procedure must have been defined first. In other words, function (A) is going to fail to compile because function (B) hasn't yet been 'seen'. Hope that makes sense.
User avatar
Tenaja
Addict
Addict
Posts: 1949
Joined: Tue Nov 09, 2010 10:15 pm

Re: Do procedures really have to appear first?

Post by Tenaja »

Yes, if you don't declare them ahead of time you are required to do top down. Otherwise PB won't be able to tell if you are using correct syntax. (Unlike gosub, which has no parameters.)

Pb is a one-pass compiler, so it doesn't look ahead for context.
mestnyi
Addict
Addict
Posts: 999
Joined: Mon Nov 25, 2013 6:41 am

Re: Do procedures really have to appear first?

Post by mestnyi »

Code: Select all

OpenConsole()

; We need to declare Procedure displayname(myname.s) here
Declare displayname(myname.s)

Print("Hello, what is your name? ")
Define name.s
name.s=Input()

displayname(name.s)
End

Procedure displayname(myname.s)
  PrintN("Hello "+myname.s)
EndProcedure
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Do procedures really have to appear first?

Post by Oso »

Tenaja wrote: Fri Jul 29, 2022 10:44 pm Yes, if you don't declare them ahead of time you are required to do top down.
Okay, got it. I didn't know what you meant by 'declare' at first, but I see from @mestnyi that there's a declare command! Great, I'm getting to know this more now. Thanks.
Last edited by Oso on Fri Jul 29, 2022 11:05 pm, edited 1 time in total.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Do procedures really have to appear first?

Post by Oso »

mestnyi wrote: Fri Jul 29, 2022 10:49 pm

Code: Select all

; We need to declare Procedure displayname(myname.s) here
Declare displayname(myname.s)
Okay, I think I understand this. We're telling the compiler that there's a procedure called displayname(), that it hasn't found yet. This is interesting! I'm not sure how that works but it seems to me that we're just reassuring the compiler not to worry, that it hasn't yet found the procedure yet? And we're even supplying the parameter too.
User avatar
jacdelad
Addict
Addict
Posts: 1473
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Do procedures really have to appear first?

Post by jacdelad »

Yes. And the parameters have to be exact the same as in the procedure.

Just out of curiosity: What speaks against declaring the procedures before the main code? My codes look like
- EnableExplicit
- Usexxx-commands
- Enumerations
- Structures
- declaration of variables
- XIncludes
- procedures
- main code
...but this is just my prototype and has to be adapted sometimes.
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Do procedures really have to appear first?

Post by Oso »

jacdelad wrote: Fri Jul 29, 2022 11:11 pm Yes. And the parameters have to be exact the same as in the procedure.

Just out of curiosity: What speaks against declaring the procedures before the main code? My codes look like
- EnableExplicit
- Usexxx-commands
- Enumerations
- Structures
- declaration of variables
- XIncludes
- procedures
- main code
...but this is just my prototype and has to be adapted sometimes.
Okay, I understand your established sequence in your code. I think that's fine if all your code follows that pattern. A lot of the time with languages, it's important to follow the conventions that other developers use. For me, coming to this as a new developer, it seems strange to have the procedures above 'main()' but for other PureBasic coders, it seems logical.
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Do procedures really have to appear first?

Post by Oso »

Oso wrote: Fri Jul 29, 2022 11:04 pm
mestnyi wrote: Fri Jul 29, 2022 10:49 pm

Code: Select all

; We need to declare Procedure displayname(myname.s) here
Declare displayname(myname.s)
Okay, I think I understand this. We're telling the compiler that there's a procedure called displayname(), that it hasn't found yet. This is interesting! I'm not sure how that works but it seems to me that we're just reassuring the compiler not to worry, that it hasn't yet found the procedure yet? And we're even supplying the parameter too.
Interesting this! I found that the declaration parameters don't really matter that much, so long as they are of the correct 'type', so the parameter "idontknow.s" is fine too.

Code: Select all

; We need to declare Procedure displayname(myname.s) here
Declare displayname(idontknow.s)
.
.
.
Procedure displayname(myname.s)
  PrintN("Hello "+myname.s)
EndProcedure

User avatar
Tenaja
Addict
Addict
Posts: 1949
Joined: Tue Nov 09, 2010 10:15 pm

Re: Do procedures really have to appear first?

Post by Tenaja »

It's good practice to keep the parameter names the same between the declare and the actual procedure.

I use similar format to jacdelad, but I put the xincludes at the top. That way dependencies have them.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Do procedures really have to appear first?

Post by infratec »

The reason for all of this:

PB is a one pass compiler.
So it is very fast compiling, but he needs to know the procedures before they are used.

I also place the procedures on top in the correct order, I don't like the extra declaration.
Too much typing for nothing.
User avatar
Mijikai
Addict
Addict
Posts: 1360
Joined: Sun Sep 11, 2016 2:17 pm

Re: Do procedures really have to appear first?

Post by Mijikai »

I like a fast compiler better than one that lets you code all over the place and then needs ages to make sense of your mess.
pTb
User
User
Posts: 16
Joined: Sat Apr 16, 2011 3:17 pm

Re: Do procedures really have to appear first?

Post by pTb »

Oso wrote: Fri Jul 29, 2022 11:18 pm
jacdelad wrote: Fri Jul 29, 2022 11:11 pm Yes. And the parameters have to be exact the same as in the procedure.

Just out of curiosity: What speaks against declaring the procedures before the main code? My codes look like
- EnableExplicit
- Usexxx-commands
- Enumerations
- Structures
- declaration of variables
- XIncludes
- procedures
- main code
...but this is just my prototype and has to be adapted sometimes.
Okay, I understand your established sequence in your code. I think that's fine if all your code follows that pattern. A lot of the time with languages, it's important to follow the conventions that other developers use. For me, coming to this as a new developer, it seems strange to have the procedures above 'main()' but for other PureBasic coders, it seems logical.
There are different paradigms among every programmer I think.

When it comes to smaller projects, I think it could suffice to have the procedures first and then the main code.
For larger projects I'd like to follow a top-down rule. Start with the highest abstraction level and slowly go down to the lowest level.

Furthermore, when the projects are even larger, the code might benefit from being written in modules (Module - End Module). By that you isolate selectable resources inside a module. Even though that is a bit outside what you started this thread for, I believe it can help you to a speed start to be able to write more complex projects.

One thing though. Resources, like file numbers, window numbers, gadget numbers and so on, are shared among the whole project. I'd suggest you to use global lists for saving the resource-# in, not to risc using the same number where it's not intended. But variables, macros, procedures and so on that are not declared in the DeclareModule-block, will be isolated to that same module.

Here is some code snippets from one of my projects:

a piece from my main module:

Code: Select all

DeclareModule GerberPeek
  
  EnableExplicit
  
  Global windowMain, menuMain
  
EndDeclareModule

XIncludeFile "Common.pbi"
XIncludeFile "Misc.pbi"
XIncludeFile "Gfx.pbi"
XIncludeFile "GerberPeekData.pbi"
XIncludeFile "Gerber.pbi"

Module GerberPeek
  
  UseModule Common
  UseModule Gerber
  UseModule Gfx
  
  Enumeration MenuItems
    ;project
    #mClose
    #mOpen
    #mPrint
    #mSizes
    ...
    ...
    ...
    
... and here is a compressed snippet of one of the modules:

Code: Select all

DeclareModule GerberPeekData
    
  EnableExplicit ; this forces you to define all variables.
  
  #statusUnknown = 0
  #statusOkay = 1
  #statusFileNotFound = 2 
  #statusLinkBroken = 4   
  #statusOrphan = 8       
  
  #statusTextDirty = #SOH$ + "Gerber not updated"
  #statusTextLinkBroken = #SOH$ + "Broken Link"
  #statusTextFileNotFound = #SOH$ + "File not found"
  #statusTextOrphan = #SOH$ + "no Gerber to match"
  #statusTextDfxAsVariant = #SOH$ + "dxf used as variant (deprecated)"
  #statusTextNoError = #SOH$ + "Status okay"
  
  #maxPause = 40 ; delay in ms for status progress bar
  
  Structure entry
    fullName.s    
    variantID.l   
    variantName.s 
    Alignment.l   
    qty.l         
    status.l      
  EndStructure
  
  Structure tree
    path.s
    pathModified.l  ; datum något i mappen uppdaterades
    name.s
    ext.s
    capsName.s  ; name in CAPS (för att lura sorteringsrutinens loppa som skiljer mellan stora och små svenska tecken trots nocase-flagga...)
    modified.l  ; datum filen modifierades
    dirtyFlag.l ; visa om denna entry är av äldre datum än beståndsdelarna.
    List dirtyTriggers.s() ; filer som triggar dirty-flaggan.
    errDxfAsVariant.l ; innehåller minst en dxf som används som variant - en icke rekommenderad egenskap
    status.l
    newInDatabase.l ; fil av senare datum än databasens eller helt ny. Nu ska vi se:
                    ; Om en mapp visar uppdaterat datum senare än innehållet i mappen så har någon fil tagits bort. Hitta denna i databasen och ta bort den och dess länkar (måste kolla alla filer inom den mappen)
                    ; Om en mapp har senare datum än innehållet så ska alla filer verifieras mot databas. De som har nyare datum ska uppdateras (radera består av om det finns)
                    ; Om mapp saknas. Radera allt inom den mappen i databasen.
    
    ;består av...
    List consistOfPattern.entry()
    List consistOfVariant.entry()
    List consistOfMarker.entry()
    ;ingår i...
    List partOfVariant.entry()
    List partOfMarker.entry()
    List partOfGBR.entry()
    ;relaterade (forks)
    List forkedMarker.entry()
    List forkedVariant.entry()
    ;List forkedPattern.entry()  ; mönster som har samma geometri, antal vektorer, antal hack, etc men olika mått - vet inte hur jag ska artbestämma detta dock. En romb och en kvadrat är inte en fork t ex. Eller?
  EndStructure
  
  Global gbrFolder$, dataRoot$, extRoot$, gbrPeek$, readXMLMarker, readXMLVariant, prefsFile$
  
  Define folderCnt, patternCnt, variantCnt, markerCnt, gerberCnt
  
  prefsFile$ = GetUserDirectory(#PB_Directory_ProgramData) + "GerberPeek\GerberPeekData.ini" ;(user dir - appdata)
  
  Declare CountDirectories(n, path.s)
  Declare DoDirectoryLevel(n, newPath$, count = 1)
  Declare.l Find(List target.tree(), search$)
  Declare.l BinFind(List myList.tree(), search$)
  Declare ParseVariant(file$)
  Declare ParseMarker(file$)
  
  
  NewList marker.tree()
  NewList variant.tree()
  NewList pattern.tree()
  NewList gerber.tree()
  
  NewList path.s()
  
EndDeclareModule

Module GerberPeekData
  
  ; These procedures will only be reachable within this module since they are declared inside the actual Module
  Declare InitPaths()
  Declare.l ChooseNode(xmlFile, path2Find$)
  
  ; You can even put code here that will be executed when the module is included. I prefer XInclude, not to risc including the same module twice.
  
  Procedure CountDirectories(n, path.s)
    
  EndProcedure
  
  Procedure DoDirectoryLevel(n, newPath.s, count = 1)
    
    ; Läser igenom en nivå i en mappstruktur och rekurserar igenom alla lägre nivåer
    
    Shared marker(), variant(), pattern(), path(), gerber(), 
           folderCnt, patternCnt, variantCnt, markerCnt, gerberCnt
    
    Static progress
    
    Define nil, timer
    
    progress + 1
    If Int(progress / 50) * 50 = progress
      StatusBarProgress(0, 1, progress, #PB_StatusBar_BorderLess, 0, folderCnt/2)
    EndIf 
    
    AddElement(path())
    path() = newPath
    
    If path() = dataRoot$ Or path() = gbrFolder$
      ExamineDirectory(n, path(), "")
    EndIf
    
    While NextDirectoryEntry(n)
      nil = WindowEvent()
      ;Debug Str(DirectoryEntryType(1))+" "+DirectoryEntryName(1)+" "+Str(DirectoryEntryAttributes(1))+" "+Str(DirectoryEntrySize(1))
      If DirectoryEntryType(n) = #PB_DirectoryEntry_Directory And path() <> gbrFolder$ And UCase(Right(path(), 9)) <> "\MÖNSTER\" And UCase(Right(path(), 8)) <> "\MODELL\"
        If DirectoryEntryName(n) <> "." And DirectoryEntryName(n) <> ".."
          ;Debug path() + DirectoryEntryName(n)
          folderCnt + 1
          ExamineDirectory(n + 1, path() + DirectoryEntryName(n) + "\", "")
          DoDirectoryLevel(n + 1, path() + DirectoryEntryName(n) + "\")
        EndIf
      Else
        If Left(path(), Len(dataRoot$)) = dataRoot$
          ;Debug GetExtensionPart(DirectoryEntryName(n))
          If UCase(GetExtensionPart(DirectoryEntryName(n))) = "DXF" Or UCase(GetExtensionPart(DirectoryEntryName(n))) = "CPX"
          ElseIf UCase(GetExtensionPart(DirectoryEntryName(n))) = "CMX"
            AddElement(marker())
            marker()\path = path()
            marker()\name = DirectoryEntryName(n)
            marker()\ext = GetExtensionPart(marker()\name)
            marker()\name = GetFilePart(marker()\name, #PB_FileSystem_NoExtension)
            marker()\modified = GetFileDate(path() + DirectoryEntryName(n), #PB_Date_Modified)
            markerCnt + 1
          ElseIf UCase(GetExtensionPart(DirectoryEntryName(n))) = "CVX"
          EndIf
        Else
          If UCase(GetExtensionPart(DirectoryEntryName(n))) = "GBR"
            ;Debug "gerber "+DirectoryEntryName(n)
            AddElement(gerber())
            gerber()\path = path()
            gerber()\name = DirectoryEntryName(n)
            gerber()\ext = GetExtensionPart(gerber()\name)
            gerber()\name = GetFilePart(gerber()\name, #PB_FileSystem_NoExtension)
            gerber()\capsName = UCase(gerber()\name)
            gerber()\modified = GetFileDate(path() + DirectoryEntryName(n), #PB_Date_Modified)
            gerberCnt + 1
          EndIf
        EndIf
      EndIf
      
    Wend
    FinishDirectory(n)
    DeleteElement(path())
    
  EndProcedure
  
  Macro ParseLinkTrees(parent, child, quantity, dirtTest = #False)
  EndMacro
  
  Procedure ParseVariant(file$)
    
    Shared variant.tree(), pattern.tree()
    
    Define xml, message$, *node, *child, a$, found, whereToInsertReference
    
    readXMLVariant + 1
    xml = LoadXML(#PB_Any, file$)
    If xml = 0
      Debug "Variant wasn't found - returning..."
    EndIf
    
    If XMLStatus(xml) <> #PB_XML_Success
      message$ = "Error in the XML file:" + Chr(13)
      message$ + "Message: " + XMLError(xml) + Chr(13)
      message$ + "Line: " + Str(XMLErrorLine(xml)) + "   Character: " + Str(XMLErrorPosition(xml))
      MessageRequester("Error", message$)
    EndIf
    
    *node = MainXMLNode(xml)
    
    If *node = 0 Or XMLNodeType(*node) <> #PB_XML_Normal
      ProcedureReturn
    EndIf
    
    *node = ChildXMLNode(*node)
    If *node = 0 Or XMLNodeType(*node) <> #PB_XML_Normal
      ;Debug "bla"
      ProcedureReturn
    EndIf
    
    While NextXMLNode(*node)
      Select UCase(GetXMLNodeName(*node))
          
        Case "PATTERNS"
          ;Debug file$
          ;Debug "patterns found"
          *child = ChildXMLNode(*node)
          While *child
            If *child And UCase(GetXMLNodeName(*child)) = "PATTERN" And ExamineXMLAttributes(*child) = #True
              a$ = GetXMLNodeText(*child)
              If Left(a$, 2) = ".\"
                a$ = dataRoot$ + Right(a$, Len(a$) - 2)
              EndIf
              
              ParseLinkTrees(variant, pattern, Val(GetXMLAttribute(*child, "Qty")))
                            
            EndIf
            *child = NextXMLNode(*child)
          Wend
      EndSelect
      *node = NextXMLNode(*node)
    Wend
    
  EndProcedure
  
  Procedure ParseMarker(file$)
  EndProcedure
  
  Procedure.l Find(List target.tree(), search$)
  EndProcedure
  
  Procedure.l BinFind(List myList.tree(), search$)
    
    Define min, max, idx, loop, index$
    
    search$ = UCase(GetFilePart(search$, #PB_FileSystem_NoExtension))
    
    max = ListSize(myList()) - 1
    min = 0
    
    idx = max / 2
    If Not SelectElement(myList(), idx)
      Debug "Houston we have a problem!"
      ProcedureReturn -2
    EndIf
    index$ = UCase(GetFilePart(myList()\name, #PB_FileSystem_NoExtension))
    
    Repeat
      loop + 1
      ;Debug "loop nr "+Str(loop)
      If search$ > index$ ; längre bak i listan tack
        If idx < max
          min = idx
          idx + (max - min + 1) / 2
        EndIf
      ElseIf search$ < index$ ; hälften fram i listan, tack
        If idx > min
          max = idx
          idx - (max - min + 1) / 2
        EndIf
      Else
        ProcedureReturn idx
      EndIf
      SelectElement(myList(), idx)
      index$ = UCase(GetFilePart(myList()\name, #PB_FileSystem_NoExtension))
    Until idx = min Or idx = max
    
    If index$ > search$ And idx > 0
      idx - 1
    EndIf
    ProcedureReturn -idx - 1
    
  EndProcedure
  
  Macro AddStructure(bomList, listHeader)
    
    If ListSize(dataList()\bomList()) > 0
      AddGadgetItem(gadget, #PB_Any, listHeader + " - " + Str(ListSize(dataList()\bomList())) + " item(s)", 0, 1)
      ForEach dataList()\bomList()
        AddGadgetItem(gadget, #PB_Any, Mid("! ?0§123¤456&nonsense", dataList()\bomList()\status + 1, 1) + " " +
                                       GetFilePart(MakePathString(dataList()\bomList()\fullName)) + " (qty: " + 
                                       Str(dataList()\bomList()\qty) + ") path: " +
                                       GetPathPart(MakePathString(dataList()\bomList()\fullName)), 0, 2)
        If dataList()\bomList()\status = MarkData::#statusFileNotFound
          SetGadgetItemColor(gadget, CountGadgetItems(gadget) - 1, #PB_Gadget_FrontColor, RGB(200,200,200))
        EndIf
        If datalist()\bomList()\qty = 0
          SetGadgetItemColor(gadget, CountGadgetItems(gadget) - 1, #PB_Gadget_FrontColor, RGB(200,200,200))
        EndIf
      Next
    EndIf  
    
  EndMacro
  
  Procedure InitPaths()
  EndProcedure
  
  Procedure.l ChooseNode(xmlFile, path2Find$)
  EndProcudure
  
  EndModule
Oso
Enthusiast
Enthusiast
Posts: 595
Joined: Wed Jul 20, 2022 10:09 am

Re: Do procedures really have to appear first?

Post by Oso »

pTb wrote: Thu Aug 04, 2022 9:16 am When it comes to smaller projects, I think it could suffice to have the procedures first and then the main code. For larger projects I'd like to follow a top-down rule. Start with the highest abstraction level and slowly go down to the lowest level.
Thanks for the post @pTb there's still quite a lot for me to absorb at this early stage, but I'm committed to working with PB.

Yes, my preference with the order in which procedures appear is that I like them to appear in the order they are called and executed. If I don't Declare them, then they will sometimes need to appear in reverse of that, since the first called procedure perhaps needs access to the second procedure, so therefore that second procedure needs to have been defined above the first.
pTb wrote: Thu Aug 04, 2022 9:16 am Furthermore, when the projects are even larger, the code might benefit from being written in modules (Module - End Module). By that you isolate selectable resources inside a module. Even though that is a bit outside what you started this thread for, I believe it can help you to a speed start to be able to write more complex projects.
A question I still have : what exactly is a project? Is it an single software routine, or can you store several separate routines in a project? Would you typically put your application into a sub-folder and have one .PBP file in that folder?

Put another way, say I'm working on two associated routines - one that writes some data and another tool that reads it back and displays it, is that one project or two?
User avatar
jacdelad
Addict
Addict
Posts: 1473
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Do procedures really have to appear first?

Post by jacdelad »

The project combines your sources and additional files plus additional info and settings into one file.
Try it, create a new project, give it a name. Then add a source file to the project. Now navigate to the project tab and set up all the settings for the project as you like (main source code, threadsafe, version info, which exe to create, icons...). These are mainly the same options that you can set via settings in the menubar. But setting them within a project overwrites the standard settings..for this project. I think it's best to try it out.
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
Marc56us
Addict
Addict
Posts: 1479
Joined: Sat Feb 08, 2014 3:26 pm

Re: Do procedures really have to appear first?

Post by Marc56us »

Hi,

Note that in PB, you can mix several techniques together in the same source file:
- Declare procedures: nothing obliges to declare them all. It is only done when the order of creation is not sufficient (it happens sometimes)
- Create the interface with the FD: you can then add and remove gadgets dynamically in the main code if you need
- Create a standard main loop AND use CallBack (BindEvent)
Each system has its advantages, there is no better one than another.

PS. I always recommend to newcomers to read (even quickly) at least the first 7 chapters of the official help. This allows to proceed by analogy when you have already practiced other languages. This way you can find the PB grammar which is not very different from the others.

https://www.purebasic.com/documentation/

The PureBasic IDE
We start programming
General Topics
Basic Keywords
Arrays, Lists & Structures
Procedure Support
Advanced Keywords

And for the project mode, I could not explain it better than in the help
Managing projects
In addition we can say that we often use the project mode to make several exe (ex: x86 and x64).
(That said, I stopped making 32bits versions since I found that nobody uses them anymore)
The project mode is especially useful to feed the completion without having to open each file
(Compiler > Build all targets)
:wink:
Post Reply