BBC BasicSDL2 Diary Program

Discussions about the BBC BASIC language, with particular reference to BB4W and BBCSDL
RNBW
Posts: 38
Joined: Thu 05 Apr 2018, 21:41

BBC BasicSDL2 Diary Program

Post by RNBW »

I'm developing a diary program in BBC BASIC SDL 2.0 (version 1.44a on Windows). I have a small program that works when it's minimal, but when I add more code I get "Syntax error" with ERL = 0.

Here is the minimal program:

Code: Select all

      INSTALL @lib$ + "dlglib"
      PROC_setdialogpalette

      DIM DayView$(300)
      FOR I% = 0 TO 300
        DayView$(I%) = ""
      NEXT
      DayView$(1) = "Testing the update procedure"

      dlg% = FN_newdialog("Test", 80, 60)

      PROC_static(dlg%, "Day View", 101, 4, 4, 70, 10, 0)
      DayListID% = FN_setproc(PROCdummy())
      PROC_listbox(dlg%, "", DayListID%, 4, 16, 70, 30, 0)

      PROC_setlistboxarray(dlg%, DayListID%, DayView$(), DIM(DayView$(),1))

      PROC_button(dlg%, "Close", 1, 20, 48, 40, 10, 0)

      result% = FN_showdialog(dlg%, &80000000, &80000000)
      PROC_closedialog(dlg%)
      CLS
      END

      DEF PROCdummy()
      ENDPROC     
But when I extend it with helper functions defined after END, I get Syntax error with ERL = 0. Can you tell me the exact rules for where DEFs must be placed in relation to (a) the main program, (b) other DEFs, and (c) END? The manual doesn't seem to say explicitly and I'm struggling.

It would be useful if someone could give me a working template or an example of a dialog box plus some helper functions.

I am trying to build the Diary Program and this was my last attempt to get runnable code, which gives me a syntax error:

Code: Select all

      REM ================================================================
      REM MY DIARY - BBC BASIC SDL 2.0
      REM Phase 3 Step 6: Full diary with correct definition order
      REM ================================================================

      INSTALL @lib$ + "dlglib"
      PROC_setdialogpalette

      OfflinePath$ = "C:/Users/raywo/Documents/DiaryData_BBCSDL2/"

      DIM MonthName$(12)
      MonthName$(1)  = "January"
      MonthName$(2)  = "February"
      MonthName$(3)  = "March"
      MonthName$(4)  = "April"
      MonthName$(5)  = "May"
      MonthName$(6)  = "June"
      MonthName$(7)  = "July"
      MonthName$(8)  = "August"
      MonthName$(9)  = "September"
      MonthName$(10) = "October"
      MonthName$(11) = "November"
      MonthName$(12) = "December"

      DIM DayView$(300)
      DIM MonthView$(3000)

      Year%  = 2026
      Month% = 9
      Day%   = 17

      dlg% = FN_newdialog("My Diary", 160, 140)

      PROC_static(dlg%, "Day View", 101, 4, 20, 60, 10, 0)
      DayListID% = FN_setproc(PROCdummy())
      PROC_listbox(dlg%, "", DayListID%, 4, 32, 70, 90, 0)

      PROC_static(dlg%, "Month View", 103, 80, 20, 60, 10, 0)
      MonthListID% = FN_setproc(PROCdummy())
      PROC_listbox(dlg%, "", MonthListID%, 80, 32, 76, 90, 0)

      PROC_button(dlg%, "PrevYear",  FN_setproc(PROCprevYear()),  4,   126, 24, 12, 0)
      PROC_button(dlg%, "PrevMonth", FN_setproc(PROCprevMonth()), 30,  126, 24, 12, 0)
      PROC_button(dlg%, "Today",     FN_setproc(PROCtoday()),     56,  126, 24, 12, 0)
      PROC_button(dlg%, "NextMonth", FN_setproc(PROCnextMonth()), 82,  126, 24, 12, 0)
      PROC_button(dlg%, "NextYear",  FN_setproc(PROCnextYear()),  108, 126, 24, 12, 0)
      PROC_button(dlg%, "Close",     1,                            136, 126, 20, 12, 0)

      PROC_button(dlg%, "Open",    FN_setproc(PROCopen()),    4,  140, 20, 12, 0)
      PROC_button(dlg%, "New",     FN_setproc(PROCnew()),     26, 140, 20, 12, 0)
      PROC_button(dlg%, "Refresh", FN_setproc(PROCrefresh()), 48, 140, 28, 12, 0)

      PROC_button(dlg%, "Search", FN_setproc(PROCsearch()), 4,  154, 20, 12, 0)
      PROC_button(dlg%, "Export", FN_setproc(PROCexport()), 26, 154, 20, 12, 0)
      PROC_button(dlg%, "Recent", FN_setproc(PROCrecent()), 48, 154, 20, 12, 0)
      PROC_button(dlg%, "Sync",   FN_setproc(PROCsync()),   70, 154, 20, 12, 0)

      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()

      result% = FN_showdialog(dlg%, &80000000, &80000000)
      PROC_closedialog(dlg%)
      CLS
      END

      REM ================================================================
      REM DEFINITIONS IN DEPENDENCY ORDER
      REM ================================================================

      DEF PROCdummy()
      ENDPROC

      DEF FN_TwoDigits$(n%)
      IF n% < 10 THEN = "0" + STR$(n%) ELSE = STR$(n%)
      ENDPROC

      DEF FN_FourDigits$(n%)
      LOCAL s$
      s$ = STR$(n%)
      WHILE LEN(s$) < 4
        s$ = "0" + s$
      ENDWHILE
      = s$

      DEF FN_GetField$(s$, field%, sep$)
      LOCAL i%, p%, endpos%
      p% = 1
      FOR i% = 1 TO field%-1
        p% = INSTR(s$, sep$, p%) + 1
      NEXT
      IF p% = 0 THEN = ""
      endpos% = INSTR(s$, sep$, p%)
      IF endpos% = 0 THEN = MID$(s$, p%) ELSE = MID$(s$, p%, endpos%-p%)
      ENDPROC

      DEF FN_DayOfWeek%(y%, m%, d%)
      LOCAL a%, yy%, mm%
      yy% = y% : mm% = m%
      IF mm% < 3 THEN mm% += 12 : yy% -= 1
      a% = d% + (13*(mm%+1)) DIV 5 + yy% + yy% DIV 4 + yy% DIV 100 + yy% DIV 400
      a% = (a% + 6) MOD 7
      = a%

      DEF FN_DayName$(dow%)
      LOCAL names$
      names$ = "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday"
      = FN_GetField$(names$, dow%+1, ",")

      DEF FN_DiaryFile$()
      LOCAL d$
      d$ = OfflinePath$
      d$ = d$ + FN_FourDigits$(Year%) + "/"
      d$ = d$ + FN_TwoDigits$(Month%) + "/"
      d$ = d$ + FN_FourDigits$(Year%) + "-"
      d$ = d$ + FN_TwoDigits$(Month%) + "-"
      d$ = d$ + FN_TwoDigits$(Day%) + ".txt"
      = d$

      DEF PROC_updateviews()
      LOCAL items%
      items% = DIM(DayView$(),1)
      PROC_setlistboxarray(dlg%, DayListID%, DayView$(), items%)
      items% = DIM(MonthView$(),1)
      PROC_setlistboxarray(dlg%, MonthListID%, MonthView$(), items%)
      ENDPROC

      DEF PROC_FillDayView()
      LOCAL i%, line%, f%, t$, d$
      FOR i% = 0 TO 300
        DayView$(i%) = ""
      NEXT
      d$ = FN_DiaryFile$()
      line% = 1
      DayView$(line%) = FN_DayName$(FN_DayOfWeek%(Year%,Month%,Day%)) + ", "
      DayView$(line%) = DayView$(line%) + FN_TwoDigits$(Day%) + " " + MonthName$(Month%) + " " + FN_FourDigits$(Year%)
      line% += 1
      DayView$(line%) = "----------------------------------------"
      line% += 1
      f% = OPENIN(d$)
      IF f% = 0 THEN
        DayView$(line%) = "  (No entry for this day)"
        line% += 1
      ELSE
        WHILE NOT EOF#f%
          t$ = GET$#f%
          IF LEN(t$) > 0 THEN
            IF LEN(t$) > 66 THEN t$ = LEFT$(t$, 66) + "..."
            DayView$(line%) = "  " + t$
            line% += 1
            IF line% > 290 THEN EXIT WHILE
          ENDIF
        ENDWHILE
        CLOSE #f%
      ENDIF
      ENDPROC

      DEF PROC_FillMonthView()
      LOCAL i%, line%
      FOR i% = 0 TO 3000
        MonthView$(i%) = ""
      NEXT
      line% = 1
      MonthView$(line%) = "Month: " + MonthName$(Month%) + " " + FN_FourDigits$(Year%)
      line% += 1
      MonthView$(line%) = "----------------------------------------"
      line% += 1
      MonthView$(line%) = "Selected: " + FN_TwoDigits$(Day%) + " " + MonthName$(Month%) + " " + FN_FourDigits$(Year%)
      ENDPROC

      DEF PROCprevYear(D%, I%)
      Year% -= 1
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCprevMonth(D%, I%)
      Month% -= 1
      IF Month% < 1 THEN Month% = 12 : Year% -= 1
      IF Day% > 28 THEN Day% = 28
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCtoday(D%, I%)
      Year% = 2026 : Month% = 9 : Day% = 17
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCnextMonth(D%, I%)
      Month% += 1
      IF Month% > 12 THEN Month% = 1 : Year% += 1
      IF Day% > 28 THEN Day% = 28
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCnextYear(D%, I%)
      Year% += 1
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCopen(D%, I%)
      PRINT "Open clicked"
      ENDPROC

      DEF PROCnew(D%, I%)
      PRINT "New clicked"
      ENDPROC

      DEF PROCrefresh(D%, I%)
      PROC_FillDayView()
      PROC_FillMonthView()
      PROC_updateviews()
      PROC_refreshdialog(D%)
      ENDPROC

      DEF PROCsearch(D%, I%)
      PRINT "Search clicked"
      ENDPROC

      DEF PROCexport(D%, I%)
      PRINT "Export clicked"
      ENDPROC

      DEF PROCrecent(D%, I%)
      PRINT "Recent clicked"
      ENDPROC

      DEF PROCsync(D%, I%)
      PRINT "Sync clicked"
      ENDPROC
Help will be appreciated, because I seem to be going around in circles.
Phil_Thatcham
Posts: 13
Joined: Tue 03 Apr 2018, 11:59

Re: BBC BasicSDL2 Diary Program

Post by Phil_Thatcham »

The ERL for your Syntax error is not helpful because your program lines are not numbered.

Numbering (Utilities/Renumber...) is useful when writing/debugging.

Numbering conventionally (start at 10, increment in 10s) reveals that the error emerges in line 580 when you call PROC_FillDayView().

I can't help with the dialog box / Syntax error themselves.

In another post I referred you to the manual for its explanation of where to place procedures.

They must go somewhere they won't be executed accidentally, but only when called. The safest place to put them is after END.

I don't think the order is important. If PROC_A calls PROC_B, you don't need to define PROC_B before PROC_A.

PT
User avatar
JeremyNicoll
Posts: 97
Joined: Sun 26 Jul 2020, 22:22
Location: Edinburgh

Re: BBC BasicSDL2 Diary Program

Post by JeremyNicoll »

Two of your /function/ definitions wrongly have ENDPROC statements after them. That's in FNs: FN_TwoDigits$ and FN_GetField$.

I don't know if they could cause errors - nothing should try to execute them... but it's careless coding.
svein
Posts: 61
Joined: Tue 03 Apr 2018, 19:34

Re: BBC BasicSDL2 Diary Program

Post by svein »

You can't define or use PROC with empty parentheses.

Either you use:

PROCdummy(list, of, variables)
END

DEF PROCdummy(list, of, variables)
ENDPROC

OR

PROCdummy
END

DEF PROCdummy
ENDPROC

Svein
KenDown
Posts: 331
Joined: Wed 04 Apr 2018, 06:36

Re: BBC BasicSDL2 Diary Program

Post by KenDown »

You asked for a basic outline. My experience is with BB4W not SDL, but I think the basic routine would be similar if not the same. Please note that what follows is "pseudo-code", it is not a working program.

PROCinit
REPEAT
poll routine
CASEclick%OF
WHEN1:PROCfirsticonclicked
WHEN2:PROCsecondiconclicked
ENDCASE
CASEkey%OF
WHEN65:PROCapressed
WHEN66:PROCbpressed
ENDCASE
UNTILendcondition
END
:
DEFPROCfirsticonclicked
ENDPROC
:
DEFPROCinit
iNSTALL any libraries
Create any dialog boxes
DIM any arrays or initialise variables
ENDPROC

So all the things necessary for your program to work are tucked away in PROCinit, after that you use whatever routines there are in SDL for monitoring mouse clicks or keyboard key-presses but what happens when a click takes place or a key is pressed is assigned to its own procedure or function.

Of course there is nothing special about the variable or procedure names in the above - it's pseudo-code, remember.

Richard does not follow the above in his example programs. He puts the initialisation routines at the start of his program and the polling loop half-way down the listing. It's not for me to criticise the master and I do recommend that you examine his programs if you want to learn more about programming. Nevertheless, for a bear of very little brain such as myself, a scheme such as I have given helps me keep things neat (by my standards).
Richard Russell
Posts: 724
Joined: Tue 18 Jun 2024, 09:32

Re: BBC BasicSDL2 Diary Program

Post by Richard Russell »

KenDown wrote: ↑Thu 08 Oct 2026, 19:10 Richard does not follow the above in his example programs.
The Cross Reference utility (Utilities menu) was my primary tool for checking the correctness of my programs, and I tried as far as possible to ensure that it generates no - or very few - warnings. If you initialise your global variables in a PROCedure that utility will complain that you are accessing non-global shared variables in a PROC or FN, which it considers bad practice (as do I).

It's a pity that the BBC BASIC IDEs don't support code folding, as that is arguably the best way to hide the initialisation of globals. If anybody fancies adding code folding to SDLIDE.bbc it would be an interesting challenge, and probably not that difficult. But in its absence, you could put your global initialisation in a separate file which you run using CALL.

Somebody is going to say that encouraging the use of multiple modules, incorporated using CALL, means that the IDE should support editing multiple files at once. I agree, but that would be a much more difficult feature to retro-fit to SDLIDE.bbc. Anyway I'm not developing BBC BASIC any more (I don't do any programming to speak of), so none of this is relevant to me.

This is what Gemini AI has to say:

"Initializing global variables inside a regular function is generally considered poor programming practice.

Why It Is Problematic

  • Hidden Side Effects: A function should ideally take inputs and return outputs. Modulating or declaring global state inside a function makes the function's behavior dependent on hidden side effects and external states.
  • Execution Order Risks: If a global variable is only initialized when a specific function runs, calling that function out of order (or forgetting to call it before using the variable) leads to undefined behavior, UnboundLocalError, or null-reference crashes.
  • Tight Coupling and Hard-to-Track Bugs: Any part of the program can modify or read a global variable. When a bug occurs, tracking down which function or thread changed the global state becomes notoriously difficult.
  • Testing and Concurrency Issues: Global variables break unit testing (since tests can leak state into one another) and cause race conditions in multi-threaded environments.
Better Alternatives
  • Pass Parameters Explicitly: Pass the required data into the function as arguments and return the result.
  • Use Local or Static Variables:
  • If a variable only needs to persist within a single function across multiple calls, use a local static variable (in languages like C/C++) or a closure/default argument (in languages like Python) rather than a true global.
  • If state needs to be shared across related functions, encapsulate them inside a class or object using instance attributes (self.variable) rather than global scope.
  • Constants Are an Exception: True global constants (immutable values like mathematical constants or configuration paths) are generally safe and acceptable, provided they are initialized safely at compile-time or startup.


User avatar
JeremyNicoll
Posts: 97
Joined: Sun 26 Jul 2020, 22:22
Location: Edinburgh

Re: BBC BasicSDL2 Diary Program

Post by JeremyNicoll »

If on the other hand you ask google what the advantages of initialising such things in a function are ... you get quite a positive list.

Both lists suggest problems which - to my reading - are likely if you have multiple such functions/procs each initialising a subset of vars. Then - yes - you could forget to call the right function at some point or call it twice (resetting some vars when it's a bad idea). It also suggests that problems can ensue if multiple threads each call it.

But if there's only one such func/proc & it's called just once (which you won't forget to do because none of the guts of the program will then work) I see it more as a cosmetic way to hide nitty-gritty details that no-one taking their first look at the code needs to be forced to read through.

I don't see how "side-effects" - mentioned in both answers - are an issue if the fn/proc is only called once & called before everything else starts.
Richard Russell
Posts: 724
Joined: Tue 18 Jun 2024, 09:32

Re: BBC BasicSDL2 Diary Program

Post by Richard Russell »

JeremyNicoll wrote: ↑Fri 09 Oct 2026, 09:38 But if there's only one such func/proc & it's called just once (which you won't forget to do because none of the guts of the program will then work) I see it more as a cosmetic way to hide nitty-gritty details
It's not necessarily true that "none of the guts of the program will then work" because a global variable (as opposed to a constant) may well not require declaration at all if the first time it is accessed is to be written. That will generally be true of global scalar variables because BBC BASIC does not require them to be declared before use.

Indeed I would think that the majority of global scalar variables are likely not to require initialisation, and there is a significant danger that you will fail to declare them as global anywhere because the program will still work without doing so. That is precisely why the Cross Reference utility issues a warning if it finds that a function references a variable which is neither local nor was declared with global scope.

That unavoidably means that if you do put global declarations and initialisations in a function, even one which is called only once, the Cross Reference utility cannot distinguish between that (arguably legitimate) usage and a function referencing a non-global but shared variable, which is undeniably bad programming practice.

Because I place so much faith in the Cross Reference utility for verifying the correctness of my programs, the last thing I want is for it always to report the presence of functions accessing shared variables, because they happen to be declared in an initialisation procedure (note that it will report their use everywhere, not just in that procedure).

The desire to "hide nitty-gritty details" presumably stems from an assumption that there may be a lot of them, but a well-written program should ideally have no global variables at all (except constants) or at least very few. Before considering where they should be declared or initialised, one should ask why they are global at all!

I would add that in some mainstream programming languages (notably C and C++, and also Pascal) a function declaration (or at least its prototype) must textually precede its invocation, so moving initialisation code from the main program into a function doesn't "hide" it, it actually moves it more towards the beginning of the file!
User avatar
JeremyNicoll
Posts: 97
Joined: Sun 26 Jul 2020, 22:22
Location: Edinburgh

Re: BBC BasicSDL2 Diary Program

Post by JeremyNicoll »

Richard Russell wrote: ↑Fri 09 Oct 2026, 10:40 It's not necessarily true that "none of the guts of the program will then work" because a global variable (as opposed to a constant) may well not require declaration ...
Ok; yes. But those 'constants' you're discounting /are/ variables in BB4W.

If you put enough critical things into 'constants' few non-trivial programs will get far without their definitions. In BBC BASIC (on RISC OS) that's particularly true if you reference SWIs by their internal integer numbers rather than by their names. Then you only use a named SWI once: to get the number of the "convert SWI names to numbers" service. Then you use that to look-up every other SWI you're going to use.


Richard Russell wrote: ↑Fri 09 Oct 2026, 10:40 I would add that in some mainstream programming languages (notably C and C++, and also Pascal) a function declaration (or at least its prototype) must textually precede its invocation, so moving initialisation code from the main program into a function doesn't "hide" it, it actually moves it more towards the beginning of the file!
The Algol I wrote in in the late 1970s was like that - but it just meant that nobody started reading a program at the top of a listing. One would scroll to the end (to find the mainline code) then look up & back to find declarations.
Richard Russell
Posts: 724
Joined: Tue 18 Jun 2024, 09:32

Re: BBC BasicSDL2 Diary Program

Post by Richard Russell »

JeremyNicoll wrote: ↑Fri 09 Oct 2026, 11:40 But those 'constants' you're discounting /are/ variables in BB4W.
Easily resolved: I've just modified crossref.bbc so that constants (defined per the usual naming convention) are not flagged as shared variables. :D