Pick code review: two approaches to the same utility

102 views
Skip to first unread message

Robert Herbin

unread,
Aug 21, 2026, 4:01:04 PM (7 days ago) Aug 21
to Pick and MultiValue Databases

I have been reacquainting myself with Pick after a long hiatus, using String Database as my current Pick environment. I used Pick professionally over 20 years ago. Over the last year, I've been writing some programs for a hobby project (and to see how much of the language I still remember). The project has been a combination of code I wrote myself and code generated by an AI assistant. Because my AI assistant didn't fully understand multivalue file structure, it wound up writing data with trailing empty attributes and multivalues.

I treated the data cleanup program as a coding challenge. I wrote the first version to be efficient, readable, maintainable, and to use features that weren't available in the Pick environments I used back in the day (GA Zebra, Fujitsu, R83, D3). The specific features I incorporated are CONTINUE, LOCAL FUNCTION, and VARIABLE[n].

After deliberately over-engineering the routine, I wondered how quickly I could write a "fast and dirty" version. The latter took about ten minutes to write and another 20 or so minutes to test (it might have been quicker if there hadn't been a few typos in the early draft).

Since I'm working on my own, there's no opportunity for peer review at home. Therefore, I'm sharing the code with the pros in this group and looking for feedback. Specifically:

  • Which of the two versions do you prefer, and why?
  • Please review my coding approaches. I think I've reached my old level of proficiency, but there's always room for improvement, and I appreciate suggestions.

If you have a use for either utility, feel free to use it. A progress indicator would be an easy addition if you find one useful.

PROGRAM TRIM.TRAILING.DELIMITERS
* COPYRIGHT 2026 ROBERT HERBIN
* SIMPLE PROGRAM TO SHORTEN ALL RECORDS IN A FILE BY REMOVING TRAILING AM, VM, AND SVM
* 8/14/2026 Added check of last character of string to prevent unnecessary reassignment of identical values
* 8/14/2026 Added check for trailing SVMs within each attribute
* 8/14/2026 - track a change flag rather than compare original record to modified record
* 8/20/2026 - Assigned dedicated delimiter search variables for each level rather than reassigning during program execution
* note that the sequence of these characters is important; the VM-level INDEX search for trailing SVMs relies on it
$DEBUG
$CATALOGUE
   DEFFUN TRUNCATE (TEXT, STRIP.CHARS) LOCAL
   PROMPT ""
   RECORD.STRIPCHARS = @SVM:@VM:@AM ; ATTRIBUTE.STRIPCHARS = @SVM:@VM ; MV.STRIPCHARS = @SVM
   CRT "ENTER FILE TO PROCESS: ":
   INPUT FILENAME
   OPEN FILENAME TO FILE.VAR ELSE STOP "CANNOT OPEN ": FILENAME
   SELECT FILE.VAR
   LOOP
      READNEXT ID ELSE STOP "PROCESSING COMPLETE"
      READU REC FROM FILE.VAR, ID ELSE CONTINUE
      CHANGE.FLAG = 0
* STRIP TRAILING DELIMITERS FROM END OF RECORD
      IF INDEX(RECORD.STRIPCHARS, REC[1], 1) THEN
         REC = TRUNCATE(REC, RECORD.STRIPCHARS)
         CHANGE.FLAG = 1
      END
* LOOP THROUGH ATTRIBUTES. STRIP TRAILING VMs/SVMs FROM THE ATTRIBUTE
* THEN SEARCH FOR TRAILING SVMs AT VM BOUNDARIES WITHIN THE ATTRIBUTE
      LAST.ATT = DCOUNT(REC, @AM)
      FOR ATT.CTR = 1 TO LAST.ATT
         ATT.CHANGE.FLAG = 0
         CUR.ATT = REC<ATT.CTR>
         IF INDEX(ATTRIBUTE.STRIPCHARS, CUR.ATT[1], 1) GT 0 THEN
            CUR.ATT = TRUNCATE(CUR.ATT, ATTRIBUTE.STRIPCHARS)
            ATT.CHANGE.FLAG = 1
         END
         IF INDEX(CUR.ATT, ATTRIBUTE.STRIPCHARS, 1) GT 0 THEN  ;* There is at least one SVM at a VM boundary (trailing SVM)
            LAST.VM = DCOUNT(CUR.ATT, @VM)
            FOR VM.CTR = 1 TO LAST.VM-1   ;* LAST.VM already checked at attribute level, stop at LAST.VM - 1
             IF INDEX(MV.STRIPCHARS, CUR.ATT<1, VM.CTR>[1], 1) GT 0 THEN
               CUR.ATT<1, VM.CTR> = TRUNCATE(CUR.ATT<1, VM.CTR>, MV.STRIPCHARS)
              END
            NEXT VM.CTR
            ATT.CHANGE.FLAG = 1
         END
         IF ATT.CHANGE.FLAG = 1 THEN
            REC<ATT.CTR> = CUR.ATT
            CHANGE.FLAG = 1
         END
      NEXT ATT.CTR
      IF CHANGE.FLAG = 1 THEN WRITE REC ON FILE.VAR, ID ELSE RELEASE FILE.VAR, ID
   REPEAT
   STOP
   LOCAL FUNCTION TRUNCATE(TEXT, STRIP.CHARS)
   LENTEXT = LEN(TEXT)
   FOR CHAR.CTR = LENTEXT TO 1 STEP -1
      IF INDEX(STRIP.CHARS, TEXT[CHAR.CTR, 1], 1) > 0 THEN     ;*  strip char; skip unless first char
         IF CHAR.CTR = 1 THEN TEXT = ""     ;* reached start; TEXT is all strip chars
      END ELSE     ;* non strip char
         IF CHAR.CTR < LENTEXT THEN TEXT = TEXT[1, CHAR.CTR]
         EXIT
      END
   NEXT CHAR.CTR
   RETURN TEXT
END

*********************************************************************************************

PROGRAM TRIM.TRAILING.DELIMITERS.TERSE
* COPYRIGHT 2026 ROBERT HERBIN
* ALTERNATE VERSION OF TRIM.TRAILING.DELIMITERS
* THE INTENT OF THIS VERSION WAS TO WRITE THE PROGRAM AS QUICKLY AS POSSIBLE
* AND FOR THE RESULTING CODE TO BE AS TERSE AS POSSIBLE.
* INEFFICIENCY IN THE CODE IS TOLERABLE BECAUSE THIS IS A "ONE OFF" PROGRAM,
* IF IT TAKES EXTRA TIME TO RUN, NO BIGGIE (e.g. DCOUNT IN FOR STATEMENT)
*
$DEBUG
$CATALOG
   PROMPT ""
   CRT "ENTER FILE TO PROCESS: ":
   INPUT FILENAME
   OPEN FILENAME TO FILE.VAR ELSE STOP "CANNOT OPEN ":FILENAME
   SELECT FILE.VAR
   LOOP
      READNEXT ID ELSE EXIT
      READU REC FROM FILE.VAR, ID THEN
         CHANGE.FLAG = 0
         FOR A = DCOUNT(REC, @AM) TO 1 STEP -1 UNTIL CONVERT(@VM:@SVM, "", REC<A>) <> ""
            DEL REC<A> ; CHANGE.FLAG = 1
         NEXT A
         FOR A = 1 TO DCOUNT(REC, @AM)
            FOR V = DCOUNT(REC<A>, @VM) TO 1 STEP -1 UNTIL CONVERT(@SVM, "", REC<A, V>) <> ""
               DEL REC<A, V> ; CHANGE.FLAG = 1
            NEXT V
            FOR V = 1 TO DCOUNT(REC<A>, @VM)
               FOR S = DCOUNT(REC<A, V>, @SVM) TO 1 STEP -1 UNTIL REC<A, V, S> <> ""
                  DEL REC<A, V, S> ; CHANGE.FLAG = 1
               NEXT S
            NEXT V
         NEXT A
         IF CHANGE.FLAG = 1 THEN WRITE REC ON FILE.VAR, ID ELSE RELEASE FILE.VAR, ID
      END
   REPEAT
   STOP
END





Brian Speirs

unread,
Aug 21, 2026, 8:46:07 PM (7 days ago) Aug 21
to Pick and MultiValue Databases
If you are using SD, you should have been able to use the CROP function that is built in. i.e.

    trimmed = CROP(raw)

When I was using UniVerse, I wrote an equivalent function (well subroutine) for that environment:

    CALL Q.CROP(raw)
Cheers,

Brian

SUBROUTINE Q.CROP(rec)
*****************************************************************
* Author : BSS
* Created: 31 May 2013
* Updated: 31 May 2013
* Version: 1.0.0
* Desc   : Crop redundant marks from an item.
*
* Copyright 2013 Rush Flat Software
* See licence conditions in: BP.Q licence.txt
*
* ------------------------------------------------------------- *
*
$INCLUDE Q.INCLUDES QB.COMMON.H

$IFDEF QM
  rec = CROP(rec)
$ELSE
  level = 1
  CALL Q.CROP.SUB(rec, level)
$ENDIF

RETURN
*
* ------------------------------------------------------------- *
*
END

SUBROUTINE Q.CROP.SUB(fragment, level)
*****************************************************************
* Author : BSS
* Created: 31 May 2013
* Updated: 31 May 2013
* Version: 1.0.0
* Desc   : Crop redundant marks from an item.
*
* Copyright 2013 Rush Flat Software
* See licence conditions in: BP.Q licence.txt
*
* ------------------------------------------------------------- *
*
$INCLUDE Q.INCLUDES QB.COMMON.H

BEGIN CASE
  CASE (level EQ 1)
    mark = @AM
    marks = @AM:@VM:@SM:@TM
  CASE (level EQ 2)
    mark = @VM
    marks = @VM:@SM:@TM
  CASE (level EQ 3)
    mark = @SM
    marks = @SM:@TM
  CASE (level EQ 4)
    mark = @TM
    marks = @TM
END CASE

xx = fragment
CONVERT marks TO '' IN xx
IF (xx EQ fragment) THEN RETURN

nextlevel = level + 1
dcm = DCOUNT(fragment, mark)
IF (nextlevel LT 4) THEN
  FOR mno = dcm TO 1 STEP -1
    segment = FIELD(fragment, mark, mno)
    CALL Q.CROP.SUB(segment, nextlevel)
    fragment = FIELDSTORE(fragment, mark, mno, 1, segment)
  NEXT mno
END
fragment = TRIM(fragment, mark, 'T')

RETURN
*
* ------------------------------------------------------------- *
*
END

Steven Martin Trimble

unread,
Aug 22, 2026, 11:16:38 AM (6 days ago) Aug 22
to mvd...@googlegroups.com
I concur sir Brian

CDMI
Steven Trimble
(501) 772-3450 cell/text


--
You received this message because you are subscribed to
the "Pick and MultiValue Databases" group.
To post, email to: mvd...@googlegroups.com
To unsubscribe, email to: mvdbms+un...@googlegroups.com
For more options, visit http://groups.google.com/group/mvdbms
---
You received this message because you are subscribed to the Google Groups "Pick and MultiValue Databases" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mvdbms+un...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/mvdbms/8ac91e2f-126d-45b4-8987-00d6823752ebn%40googlegroups.com.

Robert Herbin

unread,
Aug 22, 2026, 4:10:13 PM (6 days ago) Aug 22
to Pick and MultiValue Databases
Thanks. I had not discovered CROP(), so obviously that would be the appropriate solution in SD for the actual problem. The exercise was partly about reacquainting myself with Pick, though, so the coding exercise wasn't wasted. I also find your recursive implementation interesting. The recursion is clever, although I found the level/nextlevel and FIELD/FIELDSTORE mechanics somewhat difficult to follow on first reading.

Anthony Youngman

unread,
Aug 23, 2026, 6:41:51 PM (5 days ago) Aug 23
to mvd...@googlegroups.com
On 21/08/2026 21:01, Robert Herbin wrote:
> I have been reacquainting myself with Pick after a long hiatus, using
> String Database as my current Pick environment. I used Pick
> professionally over 20 years ago. Over the last year, I've been writing
> some programs for a hobby project (and to see how much of the language I
> still remember). The project has been a combination of code I wrote
> myself and code generated by an AI assistant. Because my AI assistant
> didn't fully understand multivalue file structure, it wound up writing
> data with trailing empty attributes and multivalues.
>
> I treated the data cleanup program as a coding challenge. I wrote the
> first version to be efficient, readable, maintainable, and to use
> features that weren't available in the Pick environments I used back in
> the day (GA Zebra, Fujitsu, R83, D3). The specific features I
> incorporated are CONTINUE, LOCAL FUNCTION, and VARIABLE[n].
>
> After deliberately over-engineering the routine, I wondered how quickly
> I could write a "fast and dirty" version. The latter took about ten
> minutes to write and another 20 or so minutes to test (it might have
> been quicker if there hadn't been a few typos in the early draft).
>
> Since I'm working on my own, there's no opportunity for peer review at
> home. Therefore, I'm sharing the code with the pros in this group and
> looking for feedback. Specifically:
> Dunno which I prefer, I'd take a third approach. Not sure which command
it is - I suspect it would be called REPLACE not CONVERT.

StringLength = 0
Loop while StringLength <> len(Record)
StringLength = len(Record)
replace @svm:@vm with @vm in Record
End Loop

Ditto @svm:@fm to @fm

Ditto @vm:@fm to @fm

Ditto trimb trailing @fms.

If you know there's only one trailing delimiter, then you don't even
need the loop.

Cheers,
Wol

Bruce Decker

unread,
Aug 23, 2026, 6:51:23 PM (5 days ago) Aug 23
to mvd...@googlegroups.com
watch out for items over 56k in size.   thats a breakover point for reality's internal item stoage and can sometimes cause restore utilities to miss them.  

former Reality engineers built jBASE.  look at the number of reality behavioral switches in jbase to gain a sense of the depth of compatibility.


From: mvd...@googlegroups.com <mvd...@googlegroups.com> on behalf of Anthony Youngman <ant...@youngman.org.uk>
Sent: Sunday, 23 August 2026 17:41:46
To: mvd...@googlegroups.com <mvd...@googlegroups.com>
Subject: Re: [mvdbms] Pick code review: two approaches to the same utility
 
--
You received this message because you are subscribed to
the "Pick and MultiValue Databases" group.
To post, email to: mvd...@googlegroups.com
To unsubscribe, email to: mvdbms+un...@googlegroups.com
For more options, visit http://groups.google.com/group/mvdbms
---
You received this message because you are subscribed to the Google Groups "Pick and MultiValue Databases" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mvdbms+un...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages