Having a strange problem with a program

General FreeBASIC programming questions.
Post Reply
Ed Davis
Posts: 37
Joined: Jul 28, 2008 23:24

Having a strange problem with a program

Post by Ed Davis »

I'm using: FreeBASIC Compiler - Version 1.10.1 (2023-12-24), built for win64 (64bit)
On Windows 10.

I have a simple program (650 lines of code, 36 functions) that is causing me problems :(

I compile with: fbc64 -lang qb tinybasic.bas

I run with: tinybasic hello.bas

If I put a debugging function at the begin and end of every function -
My program works great!
However, if I comment out said statements, in all 36 functions, my program crashes, but without any kind of error message.

for instance:

Code: Select all

function isdigit&(c as string)
call logit("isdigit begin")
  isdigit& = (c >= "0" and c <= "9")
call logit("isdigit end")
end function
Below is the logit debugging function - yes, everything is commented out:

Code: Select all

sub logit(s as string)
  'if instr(s, "isalpha") then exit sub
  'if instr(s, "isdigit") then exit sub
  'if instr(s, "nexttok") then exit sub
  'print s
end sub
With the above, my program works fine.

However, if I comment out all 72 calls (e.g., 2 in each function) to logit(), as per below:

Code: Select all

function isdigit&(c as string)
'call logit("isdigit begin")
  isdigit& = (c >= "0" and c <= "9")
'call logit("isdigit end")
end function
My program crashes without nary a word :(

tinybasic.bas - calls to logit commented out. Crashes. Uncomment the calls to logit, and the program works fine.

Code: Select all

'For QB64
'$console:only
const True = -1, False = 0, c_maxlines = 1000, c_maxvars = 26, c_at_max = 500, c_g_stack = 100
dim shared as string c_tab, c_squote, c_dquote
c_tab = chr$(9): c_squote = chr$(39): c_dquote = chr$(34)
dim shared as long c_default, c_ident, c_number, c_string, c_punct
c_default = 0: c_ident = 1: c_number = 2: c_string = 3: c_punct = 4

dim shared as string pgm(c_maxlines)    ' program stored here
dim shared as long vars(c_maxvars)			' variable store
dim shared as long gstackln(c_g_stack)  ' gosub line stack
dim shared as long gstacktp(c_g_stack)  ' gosub textp stack
dim shared as long gsp									' gosub stack index
dim shared as long atarry(c_at_max)     ' the @ array
dim shared as long forvar(c_maxvars)
dim shared as long forlimit(c_maxvars)
dim shared as long forline(c_maxvars)
dim shared as long forpos(c_maxvars)

dim shared as string tok                ' current token
dim shared as string thelin, thech      ' current program line, current character
dim shared as long curline, textp       ' position in current line
dim shared as long num                  ' last number read by scanner
dim shared as long toktype, errors, tracing
dim shared as long hi_index							' highest used index in pgm

'Needed to compile with FreeBasic
declare function accept&(s as string)
declare function expression&(minprec as long)
declare function findline&(goal as long)
declare function getfilename$(action as string)
declare function getvarindex&
declare function isalpha&(c as string)
declare function isdigit&(c as string)
declare function parenexpr&
declare function validlinenum&
declare function idxtoline&(n as long)

call main

sub logit(s as string)
  'if instr(s, "isalpha") then exit sub
  'if instr(s, "isdigit") then exit sub
  'if instr(s, "nexttok") then exit sub
  'print s
end sub

sub main
'call logit("main begin")
  call newstmt	' resets gsp, hi_index
  if command$ <> "" then
    pgm(0) = "run " + c_dquote + command$ + c_dquote
    call initlex(0)
    call docmd
  else
    call help
  end if
  do
    errors = False
    line input "tb> ", pgm(0)
    if pgm(0) <> "" then
      call initlex(0)
      if toktype <> c_number then
        call docmd
      else
        if validlinenum then
          dim as long n, i, deleted

          deleted = False
          n = findline(num)

          if n < 0 then     ' replace line
              n = abs(n)
              ' if just a number, delete that line
              if textp > len(pgm(0)) then
                for i = n to hi_index - 1
                  pgm(i) = pgm(i + 1)
                next i
                hi_index = hi_index - 1
                deleted = True
              end if
          elseif n > 0 then ' insert line
              for i = hi_index + 1 to n step -1
                  pgm(i) = pgm(i - 1)
              next i
              hi_index = hi_index + 1
          else              ' append
              hi_index = hi_index + 1
              n = hi_index
          end if

          if not deleted then pgm(n) = pgm(0)
        end if
      end if
    end if
  loop
'call logit("main end")
end sub

sub docmd
'call logit("docmd begin")
  dim as long need_colon
  do while not errors
    if tracing and left$(tok, 1) <> ":" then print idxtoline(curline); " "; tok; thech; mid$(thelin, textp)
    need_colon = False
    select case tok
      case "bye", "quit" : call nexttok: end
      case "end", "stop" : call nexttok: exit sub
      case "load", "old" : call nexttok: call loadstmt: exit sub
      case "new"         : call nexttok: call newstmt:  exit sub
      case "gosub"       : call nexttok: call gosubstmt
      case "goto"        : call nexttok: call gotostmt
      case "if"          : call nexttok: call ifstmt
      case "next"        : call nexttok: call nextstmt    ' colon checked in rtn
      case "return"      : call nexttok: call returnstmt  ' colon checked in rtn
      case "run"         : call nexttok: call runstmt
      case "clear"       : call nexttok: call clearstmt  :need_colon = True
      case "cls"         : call nexttok: cls             :need_colon = True
      case "for"         : call nexttok: call forstmt    :need_colon = True
      case "help"        : call nexttok: call help       :need_colon = True
      case "input"       : call nexttok: call inputstmt  :need_colon = True
      case "list"        : call nexttok: call liststmt   :need_colon = True
      case "print", "?"  : call nexttok: call printstmt  :need_colon = True
      case "save"        : call nexttok: call savestmt   :need_colon = True
      case "troff"       : call nexttok: tracing = False :need_colon = True
      case "tron"        : call nexttok: tracing = True  :need_colon = True
      case ":", ""       : call nexttok
      case else
        if tok = "let" then call nexttok
        if toktype = c_ident then
          call assign
        elseif tok = "@" then
          call nexttok: call arrassn
        else
          print "Unknown token '"; tok; "' at line:"; idxtoline(curline); " Col:"; textp; " : "; thelin: errors = True
        end if
    end select

    if tok = "" then
      while tok = "" and not errors
        if curline = 0 or curline >= hi_index then
          errors = True
        else
          call initlex(curline + 1)
        end if
      wend
    elseif need_colon and not accept(":") then
      print ": expected but found: "; tok: errors = True
    end if
  loop
'call logit("docmd end")
end sub

sub help
'call logit("help begin")
   print "Tiny Basic (QBJS)"
   print ""
   print "    bye, clear, cls, end/stop, help, list, load/save, new, run, tron/off"
   print "    for <var> = <expr1> to <expr2> ... next <var>                       "
   print "    gosub <expr> ... return                                             "
   print "    goto <expr>                                                         "
   print "    if <expr> then <statement>                                          "
   print "    input [prompt,] <var>                                               "
   print "    <var>=<expr>                                                        "
   print "    print <expr|string>[,<expr|string>][;]                              "
   print "    rem <anystring>  or ' <anystring>                                   "
   print "    Operators: ^, * / \ mod + - < <= > >= = <>, not, and, or            "
   print "    Integer variables a..z, and array @(expr)                           "
   print "    Functions: abs(expr), asc(ch), rnd(expr), sgn(expr)                 "
   print ""
'call logit("help end")
end sub

sub assign	' ident = expr
'call logit("assign begin")
  dim as long xvar
  xvar = getvarindex: call nexttok
  call expect("=")
  vars(xvar) = expression(0)
  if tracing then print "*** "; chr$(xvar + asc("a")); " = "; vars(xvar)
'call logit("assign end")
end sub

sub arrassn	' array assignment: @(expr) = expr
'call logit("arrassn begin")
  dim as long atndx
  atndx = parenexpr
  call expect("=")
  atarry(atndx) = expression(0)
  if tracing then print "*** @("; atndx; ") = "; atarry(atndx)
'call logit("arrassn end")
end sub

sub clearstmt	' clear all variables
'call logit("clearstmt begin")
  dim as long i
  for i = 1 to c_maxvars
    vars(i) = 0
  next i
  for i = 0 to c_at_max
    atarry(i) = 0
  next i
  gsp = 0
'call logit("clearstmt end")
end sub

sub forstmt   ' for i = expr to expr
'call logit("forstmt begin")
  dim as long xvar, forndx

  xvar = getvarindex
  call assign
  ' vars(xvar) has the value; xvar has the number value of the variable in 0..25
  forndx = xvar
  forvar(forndx) = vars(xvar)
  if tok <> "to" then
    print "For: Expecting 'to', found:"; tok: errors = True
  else
    call nexttok
    forlimit(forndx) = expression(0)
    ' need to store iter, limit, line, and col
    forline(forndx) = curline
    if tok = "" then forpos(forndx) = textp else forpos(forndx) = textp - 1
  end if
'call logit("forstmt end")
end sub

sub gosubstmt   ' gosub expr: for gosub: save the line and column
'call logit("gosubstmt begin")
  gsp = gsp + 1
  num = expression(0)
  gstackln(gsp) = curline
  if tok = "" then gstacktp(gsp) = textp else gstacktp(gsp) = textp - 1
  call go(num, "gosub")
'call logit("gosubstmt end")
end sub

sub gotostmt	' goto expr
'call logit("gotostmt begin")
  num = expression(0)
  call go(num, "goto")
'call logit("gotostmt end")
end sub

sub ifstmt	' if expr then {stmt} {: stmt}
'call logit("ifstmt begin")
  if expression(0) = 0 then call skiptoeol: exit sub
  if tok = "then" then call nexttok
  if toktype = c_number then call gotostmt
'call logit("ifstmt end")
end sub

sub inputstmt   ' "input" [string ","] xvar
'call logit("inputstmt begin")
  dim as long xvar
  dim as string st
  if toktype = c_string then
    print mid$(tok, 2);
    call nexttok
    call expect(",")
  else
    print "? ";
  end if
  xvar = getvarindex: call nexttok
  line input st
  if st = "" then st = "0"
  if (left$(st, 1) >= "0" and left$(st, 1) <= "9") or left$(st, 1) = "-" then
    vars(xvar) = val(st)
  else
    vars(xvar) = asc(st)
  end if
'call logit("inputstmt end")
end sub

sub liststmt
'call logit("liststmt begin")
  dim as long i
  for i = 1 to hi_index
    print pgm(i)
  next i
  print
'call logit("liststmt end")
end sub

sub loadstmt	' load ["string"]
'call logit("loadstmt begin")
  dim as string filename

  filename = getfilename("Load")
  if filename = "" then exit sub
  call newstmt
  open filename for input as #1
  hi_index = 0
  while not eof(1)
    hi_index = hi_index + 1
    line input #1, pgm(hi_index)
  wend
  close #1
  curline = 0
'call logit("loadstmt end")
end sub

sub newstmt	' clears program and variable store
'call logit("newstmt begin")
  dim as long i
  call clearstmt
  for i = 1 to c_maxlines
    pgm(i) = ""
  next i
  hi_index = 0
'call logit("newstmt end")
end sub

sub nextstmt	' next ident - ident is required
'call logit("nextstmt begin")
  dim as long forndx

  ' tok needs to have the variable
  forndx = getvarindex&
  forvar(forndx) = forvar(forndx) + 1
  vars(forndx) = forvar(forndx)
  if forvar(forndx) <= forlimit(forndx) then
    curline = forline(forndx)
    textp   = forpos(forndx)
    call initlex2
  else
    call nexttok ' skip the ident for now
    if tok <> "" and tok <> ":" then
      print "Next: expected ':' before statement, but found:"; tok: errors = True
    end if
  end if
'call logit("nextstmt end")
end sub

' "print" [[#num "," ] expr { "," [#num ","] expr }] [","] {":" stmt} eol
' expr can also be a literal string
sub printstmt
'call logit("printstmt begin")
  dim as long printnl, printwidth, n
  dim as string junk

  printnl = True
  do while tok <> ":" and tok <> "" and tok <> "else"
    printnl = True
    printwidth = 0
    if accept("#") then
      if num <= 0 then print "Expecting a print width, found:"; tok: exit sub
      printwidth = num
      call nexttok
      if not accept(",") then print "Print: Expecting a ',', found:"; tok: exit sub
    end if

    if toktype = c_string then
      junk = mid$(tok, 2)
      call nexttok
    elseif toktype = c_ident and tok = "chr" and thech = "$" then
        textp = textp + 1 ' consume $
        call nexttok      ' get (
        n = parenexpr&
        junk = chr$(n)
    else
      n = expression(0)
      junk = ltrim$(str$(n))
    end if
    printwidth = printwidth - len(junk)
    if printwidth <= 0 then print junk; else print space$(printwidth); junk;

    if accept(",") or accept(";") then printnl = False else exit do
  loop

  if printnl then print
'call logit("printstmt end")
end sub

sub returnstmt ' exit from a subroutine
'call logit("returnstmt begin")
  curline = gstackln(gsp)
  textp   = gstacktp(gsp)
  gsp = gsp - 1
  call initlex2
  if tok <> "" and tok <> ":" then
    print "Return: expected ':' before statement, but found:"; tok: errors = True
  end if
'call logit("returnstmt end")
end sub

sub runstmt	' run ["string"]
'call logit("runstmt begin")
  if toktype = c_string then call loadstmt
  call clearstmt
  call initlex(1)
'call logit("runstmt end")
end sub

sub savestmt ' save ["string"]
'call logit("savestmt begin")
  dim as long i
  dim as string filename

  filename = getfilename("Save")
  if filename = "" then exit sub
  open filename for output as #1
  for i = 1 to hi_Index
    if pgm(i) <> "" then print #1, pgm(i)
  next i
  close #1
'call logit("savestmt end")
end sub

sub go(n as long, s as string)	' transfer control to line n
'call logit("go begin")
  dim as long i

  if validlinenum then
    i = findline(n)
    if i < 0 then
      call initlex(abs(i))
    else
      print s; " target not found: "; n: errors = True
    end if
  end if
'call logit("go end")
end sub

' find the goal line
' found exact match, return -i
' found first greater, return i
' else goal is > all,  0
function findline&(goal as long)
'call logit("findline begin")
  dim as long lo, hi, closest, i, aline

  findline = 0
  aline = idxtoline(hi_index)
  if toktype = c_number and goal > aline then exit function

  lo = 1
  hi = hi_index

  while lo <= hi
    i = lo + int((hi - lo) \ 2)
    aline = idxtoline(i)
    if aline = goal then
      findline = -i: exit function
    elseif aline < goal then
      closest = i
      lo = i + 1
    else
      hi = i - 1
    end if
  wend

  i = closest + 1
  while i <= hi_index
    call initlex(i)
    if aline < goal then
      i = i + 1
    else
      closest = i
      exit while
    end if
  wend

  findline = closest
'call logit("findline end")
end function

function getfilename$(action as string)
'call logit("getfilename begin")
  dim as string filename
  if toktype = c_string then
    filename = mid$(tok, 2)
  else
    print action; ": ";
    line input filename
  end if
  if filename <> "" then
    if instr(filename, ".") = 0 then filename = filename + ".bas"
  end if
  getfilename$ = filename
'call logit("getfilename end")
end function

function validlinenum&
'call logit("validlinenum begin")
  validlinenum = True
  if num <= 0 then print "Line number out of range": errors = True: validlinenum = False
'call logit("validlinenum end")
end function

function parenexpr&
'call logit("parenexpr begin")
  call expect("("): if errors then exit function
  parenexpr& = expression(0)
  call expect(")")
'call logit("parenexpr end")
end function

function expression&(minprec as long)
'call logit("expression begin")
  dim as long n, n2

  ' handle numeric operands - numbers and unary operators
  if 0 then ' to allow elseif
  elseif toktype = c_number then: n = num: call nexttok
  elseif tok = "("   then: n =  parenexpr
  elseif tok = "not" then: call nexttok: n = expression(3) = 0
  elseif tok = "abs" then: call nexttok: n = abs(parenexpr)
  elseif tok = "asc" then: call nexttok: call expect("("): n = asc(mid$(tok, 2, 1)): call nexttok: call expect(")")
  elseif tok = "rnd" or tok = "irnd" then: call nexttok: n = int(rnd * parenexpr) + 1
  elseif tok = "sgn" then: call nexttok: n = sgn(parenexpr)
  elseif toktype = c_ident then: n = vars(getvarindex): call nexttok
  elseif tok = "@"   then: call nexttok: n = atarry(parenexpr)
  elseif tok = "-"   then: call nexttok: n = -expression(7)
  elseif tok = "+"   then: call nexttok: n =  expression(7)
  else: print "("; idxtoline(curline); ") syntax error: expecting an operand, found: ", tok: errors = True: exit function
  end if

  do  ' while binary operator and precedence of tok >= minprec
    if 0 then ' to allow elseif
    elseif minprec <= 1 and tok = "or"  then: call nexttok: n2 = expression(2): n = n or n2
    elseif minprec <= 2 and tok = "and" then: call nexttok: n2 = expression(3): n = n and n2
    elseif minprec <= 4 and tok = "="   then: call nexttok: n = abs(n = expression(5))
    elseif minprec <= 4 and tok = "<"   then: call nexttok: n = abs(n < expression(5))
    elseif minprec <= 4 and tok = ">"   then: call nexttok: n = abs(n > expression(5))
    elseif minprec <= 4 and tok = "<>"  then: call nexttok: n = abs(n <> expression(5))
    elseif minprec <= 4 and tok = "<="  then: call nexttok: n = abs(n <= expression(5))
    elseif minprec <= 4 and tok = ">="  then: call nexttok: n = abs(n >= expression(5))
    elseif minprec <= 5 and tok = "+"   then: call nexttok: n = n + expression(6)
    elseif minprec <= 5 and tok = "-"   then: call nexttok: n = n - expression(6)
    elseif minprec <= 6 and tok = "*"   then: call nexttok: n = n * expression(7)
    elseif minprec <= 6 and (tok = "/" or tok = "\") then: call nexttok: n = n \ expression(7): if n < 0 then n = fix(n) else n = int(n)
    elseif minprec <= 6 and tok = "mod" then: call nexttok: n = n mod expression(7): if n < 0 then n = fix(n) else n = int(n)
    elseif minprec <= 8 and tok = "^"   then: call nexttok: n = clng(n ^ expression(9))
    else: exit do
    end if
  loop

  expression& = n
'call logit("expression end")
end function

function getvarindex& ' return the index in var store, in 0..25
'call logit("getvarindex begin")
  if toktype <> c_ident then print "("; idxtoline(curline); ") Not a variable:"; tok: errors = True: exit function
  getvarindex& = asc(left$(tok, 1)) - asc("a")
'call logit("getvarindex end")
end function

sub expect(s as string)
'call logit("expect begin")
  if accept(s) then exit sub
  print "("; idxtoline(curline); ") expecting "; s; " but found "; tok; " =>"; pgm(curline): errors = True
'call logit("expect end")
end sub

function accept&(s as string)
'call logit("accept begin")
  accept& = False: if tok = s then accept = True: call nexttok
'call logit("accept end")
end function

sub initlex(n as long)	' if not line 0, skip the line number
'call logit("initlex begin")
  curline = n: textp = 1
  call initlex2
  if n <> 0 and toktype = c_number then call nexttok
'call logit("initlex end")
end sub

sub initlex2	' entry point to continue where we left off
'call logit("initlex2 begin")
  thelin = pgm(curline)
  call nexttok
'call logit("initlex2 end")
end sub

sub nexttok
'call logit("nexttok begin")
  tok = "": toktype = c_default: thech = ""
  do while textp <= len(thelin)
    thech = mid$(thelin, textp, 1)
    select case toktype
      case c_default
        if 0 then '
        elseif thech <= " " then:                      rem just skip space, cf, lf
        elseif isalpha(thech) then:                    toktype = c_ident
        elseif isdigit(thech) then:                    toktype = c_number
        elseif instr(",#()*+-/:;<=>?@\^", thech) then: toktype = c_punct
        elseif thech = c_dquote then:                  toktype = c_string
        elseif thech = c_squote then:                  call skiptoeol: exit sub
        else: print "("; idxtoline(curline); ","; textp; ") "; "What>"; tok; "< "; thelin: errors = True: exit sub
        end if
      case c_ident:  if not isalpha(thech) then exit do
      case c_number: if not isdigit(thech) then exit do
      case c_string: if thech = c_dquote then textp = textp + 1: exit sub
      case c_punct
        if (tok = "<" and (thech = ">" or thech = "=")) or (tok = ">" and thech = "=") then
          tok = tok + thech
          textp = textp + 1
        end if
        exit sub
    end select
    if toktype <> c_default then tok = tok + thech
    textp = textp + 1
  loop
  if toktype = c_number then num = val(tok)
  if toktype = c_string then print "String not terminated": errors = True
  if toktype = c_ident then
    tok = lcase$(tok)
    if tok = "rem" then call skiptoeol
  end if
'call logit("nexttok end")
end sub

sub skiptoeol
'call logit("skiptoeol begin")
  tok = "": toktype = c_default
  textp = len(thelin) + 1
'call logit("skiptoeol end")
end sub

function isalpha&(c as string)
'call logit("isalpha begin")
  isalpha& = (c >= "a" and c <= "z") or (c >= "A" and c <= "Z")
'call logit("isalpha end")
end function

function isdigit&(c as string)
'call logit("isdigit begin")
  isdigit& = (c >= "0" and c <= "9")
'call logit("isdigit end")
end function

function idxtoline&(n as long)	' return the line number at line n
'call logit("idxtoline begin")
  dim as string s
  dim as long p
  s = ltrim$(pgm(n))
  p = instr(s, " ")
  if p <= 0 then idxtoline = val(s) else idxtoline = val(left$(s, p - 1))
'call logit("idxtoline end")
end function
hello.bas:

Code: Select all

1 ? "hello"
2 ? "bye"
caseih
Posts: 2157
Joined: Feb 26, 2007 5:32

Re: Having a strange problem with a program

Post by caseih »

Compile with '-exx'. That will add in additional runtime checking for things like bounds. When I compile with -exx and run your example, I get:

$ ./tinybasic hello.bas

Aborting due to runtime error 6 (out of bounds array access) at line 573 of tinybasic.bas::INITLEX2()

So I think your problem is somewhere around line 573. Probably the logging code you add covers up a memory corruption problem. If you can make it run with -exx then you know at least you don't have any out-of-bounds conditions which can cause memory corruption.
Ed Davis
Posts: 37
Joined: Jul 28, 2008 23:24

Re: Having a strange problem with a program

Post by Ed Davis »

caseih wrote: Feb 21, 2024 1:15 Compile with '-exx'. That will add in additional runtime checking for things like bounds. When I compile with -exx and run your example, I get:

$ ./tinybasic hello.bas

Aborting due to runtime error 6 (out of bounds array access) at line 573 of tinybasic.bas::INITLEX2()

So I think your problem is somewhere around line 573. Probably the logging code you add covers up a memory corruption problem. If you can make it run with -exx then you know at least you don't have any out-of-bounds conditions which can cause memory corruption.
Thank you for the quick response! I will give that a try!
Ed Davis
Posts: 37
Joined: Jul 28, 2008 23:24

Re: Having a strange problem with a program

Post by Ed Davis »

caseih wrote: Feb 21, 2024 1:15 Compile with '-exx'. That will add in additional runtime checking for things like bounds. When I compile with -exx and run your example, I get:

$ ./tinybasic hello.bas

Aborting due to runtime error 6 (out of bounds array access) at line 573 of tinybasic.bas::INITLEX2()

So I think your problem is somewhere around line 573. Probably the logging code you add covers up a memory corruption problem. If you can make it run with -exx then you know at least you don't have any out-of-bounds conditions which can cause memory corruption.
Found the problem. I needed to prototype at least the initlex function:

declare sub initlex(n as long)

Once I added that, it works fine, passes all 180 unit tests. Case closed! :)

Thanks again for the help!
Post Reply