More ChatGPT code generation

I asked ChatGPT to convert a PHP program to Go, then to Pascal and then to a customised dialect of Basic, it did a pretty good job at Go and Pascal, but what surprised me was how it managed to do the customed Basic dialect pretty good too, it’s not perfect, there is no PostgreSQL or YAML support, but other than that it’s structurally correct, although could do with some cleaup.

It used all the new features I told it about, so pretty amazed.

' gbackup — structured BASIC dialect (labels, BLOCK/SUB/CALL, IF/SWITCH, RECORD)
' Mirrors the Pascal version’s control flow with external tools (cp, rsync, mount, ln)
' NOTE: adjust SHELL commands/paths for your OS/environment.

' ----------------------------- RECORD declarations -----------------------------

RECORD Job LEN = 512
  Id        AS STRING * 64
  Source    AS STRING * 200
  Dest      AS STRING * 200
  Mount     AS STRING * 32
  Copies    AS STRING * 4     ' store numeric text like "7"
  Enabled   AS STRING * 1     ' "1" or "0"
END RECORD

RECORD Exclude LEN = 256
  Path AS STRING * 256
END RECORD

' ----------------------------- Globals / settings ------------------------------

' Lock & config
LockFile$   = "/var/run/gbackup.pid"
ConfigFile$ = "/etc/gbackup.yaml"     ' not parsed here; stubbed loader below

' Current job (single-job stub; extend to iterate a file/list)
Job_Id$      = ""
Job_Source$  = ""
Job_Dest$    = ""
Job_Mount$   = ""
Job_Copies$  = "7"        ' default keep
Job_Enabled$ = "1"

' Scratch
TmpDir$        = "/tmp"
ExcludesFile$  = TmpDir$ + "/gbackup.excludes"
Day$           = "YYYYMMDD-1"  ' <-- set via CALL ComputeYesterday or replace with real value
RsyncOpts$     = "-aru --delete-after --delete-excluded --stats"

' Mode controls for SWITCH demo (debug/safe/normal)
Mode$ = "normal"

' ----------------------------- Program entry -----------------------------------

PRINT "gbackup starting…"
CALL ParseArgs
CALL AcquireLock

GOTO precheck

BLOCK precheck
  PRINT "Pre-checks…"
  CALL ComputeYesterday
  CALL LoadConfig
END BLOCK

main:
  CALL BackupJob
  GOTO done

done:
  PRINT "gbackup complete."
  CALL ReleaseLock

' ============================= SUBROUTINES =====================================

SUB ParseArgs
  ' In classic BASIC there are no argv; keep defaults or load from a small text file if needed.
  ' For demo we keep LockFile$/ConfigFile$ as-is and RsyncOpts$ default.
END SUB

SUB AcquireLock
  ' Best-effort: if lock file exists, abort; otherwise create.
  SHELL "test -f " + LockFile$ + " && echo 'already running' && exit 3"
  SHELL "echo $$ > " + LockFile$
END SUB

SUB ReleaseLock
  SHELL "rm -f " + LockFile$
END SUB

SUB ComputeYesterday
  ' If you have a date utility, compute yesterday in YYYYMMDD.
  ' GNU date example (Linux):
  SHELL "date -d 'yesterday' +%Y%m%d > /tmp/.gbackup_day"
  OPEN "/tmp/.gbackup_day" FOR INPUT AS #1
  LINE INPUT #1, Day$
  CLOSE #1
  IF Day$ = "" THEN Day$ = "19700101"
END SUB

SUB LoadConfig
  ' Stub: set a single job. Replace this with your own loader if desired.
  ' (You could parse a simple INI or read fixed records with FIELD here.)
  Job_Id$      = "daily-home"
  Job_Source$  = "/home/"
  Job_Dest$    = "/backups/home"
  Job_Mount$   = ""          ' e.g., "/"
  Job_Copies$  = "7"
  Job_Enabled$ = "1"

  ' Excludes file: keep a static list or generate from a small text source.
  OPEN ExcludesFile$ FOR OUTPUT AS #1
  PRINT #1, "*.tmp"
  PRINT #1, "*.cache"
  PRINT #1, "/home/*/.cache/"
  PRINT #1, "/home/*/.local/share/Trash/"
  CLOSE #1

  ' Optional mode (for SWITCH demo)
  Mode$ = "normal"
END SUB

SUB BackupJob
  IF Job_Enabled$ <> "1" THEN
    PRINT "Job disabled: "; Job_Id$
    GOTO after_job
  END IF

  PRINT "==> Job "; Job_Id$

  IF Job_Mount$ <> "" THEN
    CALL Remount(Job_Mount$, 1)
  END IF

  CALL DeleteOldestBeyond(Job_Dest$, VAL(Job_Copies$))

  ' If dest/current exists, ensure day dir via cp phase; else initialize day dir and mark cp done.
  IF DirExists(Job_Dest$ + "/current") THEN
    IF NOT DirExists(Job_Dest$ + "/" + Day$) THEN
      IF NOT CpPhase() THEN
        PRINT "cp phase failed (will retry next run)"
        GOTO job_epilogue
      END IF
    END IF
  ELSE
    CALL MkdirP(Job_Dest$ + "/" + Day$)
    CALL PhaseMark("cp", Day$)
  END IF

  IF NOT RSyncPhase() THEN
    PRINT "rsync phase failed (will retry next run)"
    GOTO job_epilogue
  END IF

  IF DirExists(Job_Dest$ + "/" + Day$) THEN
    CALL UpdateCurrentSymlink
  END IF

job_epilogue:
  IF Job_Mount$ <> "" THEN
    CALL Remount(Job_Mount$, 0)
  END IF

after_job:
  CALL FlushToDisk
END SUB

SUB CpPhase
  ' Returns via shared flag CpOk%
  CpOk% = 0

  DayDir$     = Job_Dest$ + "/" + Day$
  CurrentDir$ = Job_Dest$ + "/current"

  IF PhaseDone("cp", Day$) THEN
    CpOk% = 1
    RETURN
  END IF

  IF DirExists(DayDir$) THEN
    CALL PhaseMark("cp", Day$)
    CpOk% = 1
    RETURN
  END IF

  IF NOT DirExists(CurrentDir$) THEN
    CALL MkdirP(DayDir$)
    CALL PhaseMark("cp", Day$)
    CpOk% = 1
    RETURN
  END IF

  CALL MkdirP(DayDir$)
  Cmd$ = "nice -n 19 ionice -c 3 -n 7 cp -ralf " + CurrentDir$ + "/* " + DayDir$
  CALL RunLogged(Cmd$, Rc%)

  IF Rc% <> 0 THEN RETURN

  ' Remove symlinks from the copied tree
  Cmd$ = "nice -n 19 ionice -c 3 -n 7 find " + DayDir$ + " -type l -delete"
  CALL RunLogged(Cmd$, Rc%)
  IF Rc% <> 0 THEN RETURN

  CALL PhaseMark("cp", Day$)
  CpOk% = 1
END SUB

SUB RSyncPhase
  ' Returns via shared flag RsOk%
  RsOk% = 0
  IF PhaseDone("rsync", Day$) THEN
    RsOk% = 1
    RETURN
  END IF

  DestDir$ = Job_Dest$ + "/" + Day$ + "/"
  CALL MkdirP(DestDir$)

  ' Build and run rsync with retries
  Cmd$ = "nice -n 19 ionice -c 3 -n 7 rsync " + RsyncOpts$ + " --exclude-from=" + ExcludesFile$ + " " + Job_Source$ + " " + DestDir$

  Attempt% = 0
  Rc% = 1
  WHILE Attempt% < 3 AND Rc% <> 0
    CALL RunLogged(Cmd$, Rc%)
    IF Rc% = 0 THEN
      CALL PhaseMark("rsync", Day$)
      RsOk% = 1
      RETURN
    END IF
    CALL SleepMs(2000 * (Attempt% + 1))
    Attempt% = Attempt% + 1
  WEND
END SUB

SUB Remount(MountPoint$, Rw%)
  IF MountPoint$ = "" THEN RETURN

  ' best effort show
  SHELL "mount " + MountPoint$

  IF Rw% = 1 THEN Mode$ = "rw" ELSE Mode$ = "ro"
  SHELL "mount -o remount," + Mode$ + " " + MountPoint$
END SUB

SUB UpdateCurrentSymlink
  SHELL "ln -sfn " + Day$ + " " + Job_Dest$ + "/current"
END SUB

SUB DeleteOldestBeyond(Dest$, Keep%)
  ' Deletes oldest subdirs beyond Keep (sorted lexicographically by name)
  ' Caution: simplistic; adjust for your system.
  Cmd$ = "ls -1 " + Dest$ + " | grep -Ev '^(current|\\.gbackup)$' | sort | head -n -" + STR$(Keep%) + " | xargs -r -I{} nice -n 19 ionice -c 3 -n 7 rm -rf " + Dest$ + "/{}"
  CALL RunLogged(Cmd$, Rc%)
END SUB

SUB PhaseDone(Phase$, Day$)
  PhaseFlag% = 0
  Flag$ = Job_Dest$ + "/.gbackup/" + Phase$ + "." + Day$ + ".done"
  SHELL "test -f " + Flag$ + " || exit 1"
  ' If test succeeded, shell exit code 0; we can't read it directly, so also touch a temp marker:
  ' For portability, just try to open; if fails, assume not done:
  OPEN Flag$ FOR INPUT AS #1
  IF LOF(1) >= 0 THEN PhaseFlag% = 1
  CLOSE #1
END SUB

SUB PhaseMark(Phase$, Day$)
  CALL MkdirP(Job_Dest$ + "/.gbackup")
  Flag$ = Job_Dest$ + "/.gbackup/" + Phase$ + "." + Day$ + ".done"
  OPEN Flag$ FOR OUTPUT AS #1
  PRINT #1, "done"
  CLOSE #1
END SUB

SUB MkdirP(Path$)
  SHELL "mkdir -p " + Path$
END SUB

SUB DirExists(Path$)
  ' Sets DirOk% via a temporary test; classic BASIC lacks a direct API
  SHELL "test -d " + Path$ + " && echo 1 > /tmp/.dir_ok || echo 0 > /tmp/.dir_ok"
  OPEN "/tmp/.dir_ok" FOR INPUT AS #1
  LINE INPUT #1, T$
  CLOSE #1
  DirOk% = 0
  IF T$ = "1" THEN DirOk% = 1
END SUB

SUB RunLogged(Command$, Rc%)
  PRINT Command$
  SHELL Command$ + " ; printf ' : rc=[%d]\n' $?"
  ' Grab last exit code by writing to a file:
  SHELL "(" + Command$ + "); printf '%d\n' $? > /tmp/.rc"
  OPEN "/tmp/.rc" FOR INPUT AS #1
  LINE INPUT #1, T$
  CLOSE #1
  Rc% = VAL(T$)
END SUB

SUB SleepMs(Ms%)
  ' crude sleep using POSIX sleep; adjust if unavailable
  Secs% = Ms% \ 1000
  IF Secs% <= 0 THEN Secs% = 1
  SHELL "sleep " + STR$(Secs%)
END SUB

SUB FlushToDisk
  SHELL "/bin/sync"
END SUB

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.