Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How to parse a string with nested parentheses

366 views
Skip to first unread message

David Crayford

unread,
Sep 29, 2017, 3:25:24 AM9/29/17
to
Greetings,

I'm writing a simple command processor in REXX which parses the
following types of commands.

INDSN(data-set-name)

My code is pretty simple which uses the parse instruction with a template.

  parse var parm . '(' val ')'

Trouble is once I try to parse a data set name with a member I've got a
problem. Because of the nested parentheses it truncates the output value.

 *-* parm = 'INDSN(DSNAME(MEMBER))'
 >L>   "INDSN(DSNAME(MEMBER))"
 *-* parse var parm . '(' val ')'
 >.>   "INDSN"
 >>>   "DSNAME(MEMBER"

I can solve this easily in other languages using a regular expression
like "\((.*)\)$", but I can't figure out how to do it in REXX using the
parse instruction.

Any ideas?

----------------------------------------------------------------------
For TSO-REXX subscribe / signoff / archive access instructions,
send email to LIST...@VM.MARIST.EDU with the message: INFO TSO-REXX

Jeremy Nicoll

unread,
Sep 29, 2017, 4:23:17 AM9/29/17
to
On Fri, 29 Sep 2017, at 08:24, David Crayford wrote:
> Greetings,
>
> I'm writing a simple command processor in REXX which parses the
> following types of commands.
>
> INDSN(data-set-name)
>
> My code is pretty simple which uses the parse instruction with a
> template.
>
>   parse var parm . '(' val ')'
>
> Trouble is once I try to parse a data set name with a member I've got a
> problem. Because of the nested parentheses it truncates the output value.
>
>  *-* parm = 'INDSN(DSNAME(MEMBER))'
>  >L>   "INDSN(DSNAME(MEMBER))"
>  *-* parse var parm . '(' val ')'
>  >.>   "INDSN"
>  >>>   "DSNAME(MEMBER"
>
> I can solve this easily in other languages using a regular expression
> like "\((.*)\)$", but I can't figure out how to do it in REXX using the
> parse instruction.
>
> Any ideas?

Parse isn't the solution to all parsing problems.

You could always expect parms like: INDSN[data-set-name]
but that might cause no end of trouble.

Long ago I wrote something to work with named arguments
in a parameter string. This was used in hundreds of rexx execs
under netview as part of system automation; the mechanism
was designed to suit lots of execs which might pass internal
arguments back and forth, but also for commands that might
be typed in on any operator console.

Basically I wrote an external function (which in netview terms was
a preloaded exec so efficient for any other exec to call) which given
an arbitrary character string (of incoming arguments) could be told
to extract the 'XYZ' one and return that argument's value, and the
remaining argument string after that argument had been elided.

Since a rexx function can only return a single value, and either or
both of the things the function has to return can be empty or
contain any character, I made the utility function return

!keyword_value_in_hex!remaining_text_in_hex!

so in use one might see something like

parse arg allparms

hexkr = '#KYWDPRS'("DEL",allparms) ; parse var hexkr '!'khex'!'rhex'!'
delay = x2c(khex) ; allparms = x2c(rhex)
if ^datatype(delay,"W") then delay = 1

hexkr = '#KYWDPRS'("MAX",allparms) ; parse var hexkr '!'khex'!'rhex'!'
maxno = x2c(khex) ; allparms = x2c(rhex)
if ^datatype(maxno,"W") then maxno = 99999

hexkr = '#KYWDPRS'("MIN",allparms) ; parse var hexkr '!'khex'!'rhex'!'
minno = x2c(khex) ; allparms = x2c(rhex)
if ^datatype(minno,"W") then minno = 0

if allparms ^== "" then ... deal with unexpected arguments...


at the start of an exec which was processing an argument string that
might contain DEL() MAX() and MIN() arguments.


hexkr = '#KYWDPRS'("DEL",allparms) ; parse var hexkr '!'khex'!'rhex'!'

was the function call; hexkr is going to be set to the hex strings for
the
keyword value, and remaining arg string. The returned value is parsed
into khex and rhex which are those two things, in hex. For eg the DEL()
parm I then set

delay = x2c(khex) ; allparms = x2c(rhex)

ie made the using exec's "delay" variable into the returned keyword
value, and reset allparms so it could be fed into the following logic.


In the '#KYWDPRS' external function itself, in my case I planned the
thing's use so that the specific nested bracket problem you have
wouldn't ever happen, so parsing of the argument string could be
done by a series of substr() calls. In your case you'd need to look
for "XYZ(" in an argument string, then scan along that looking for
both further "(" characters and ")" characters and count their
depth of nesting, before separating the different parts of the
overall string.

You can find a copy of the function code at

https://www.dropbox.com/s/etu82u9xj8mzd7k/KYWDPRS2.rex?dl=0


--
Jeremy Nicoll - my opinions are my own.

ITschak Mugzach

unread,
Sep 29, 2017, 4:28:07 AM9/29/17
to
First translate the string:
Zdsn=translate(zdsn,' ',')(')
Parse zdsn dsn mem .

You can than test to see if member specified.

ITschak

בתאריך 29 בספט׳ 2017 10:26 לפנה״צ,‏ "David Crayford" <dcra...@gmail.com>
כתב:

David Crayford

unread,
Sep 29, 2017, 5:39:10 AM9/29/17
to
On 29/09/2017 4:22 PM, Jeremy Nicoll wroteOn Fri, 29 Sep 2017, at 08:24,
David Crayford wrote:
>> Greetings,
>>
>> I'm writing a simple command processor in REXX which parses the
>> following types of commands.
>>
>> INDSN(data-set-name)
>>
>> My code is pretty simple which uses the parse instruction with a
>> template.
>>
>>   parse var parm . '(' val ')'
>>
>> Trouble is once I try to parse a data set name with a member I've got a
>> problem. Because of the nested parentheses it truncates the output value.
>>
>>  *-* parm = 'INDSN(DSNAME(MEMBER))'
>>  >L>   "INDSN(DSNAME(MEMBER))"
>>  *-* parse var parm . '(' val ')'
>>  >.>   "INDSN"
>>  >>>   "DSNAME(MEMBER"
>>
>> I can solve this easily in other languages using a regular expression
>> like "\((.*)\)$", but I can't figure out how to do it in REXX using the
>> parse instruction.
>>
>> Any ideas?
> Parse isn't the solution to all parsing problems.

Indeed, but it's elegant and often the most efficient when compared to
calling multiple built-in functions that all run the entire string.

I've already solved the problem by writing my own function but I was
wondering if there was a parse trick I am unaware of.

/* A version of parse var str . '(' v ')' which handles nested parens */
yank: arg str
  startpos = pos('(', str); endpos = lastpos(')', str)
  return substr(str, startpos + 1, endpos - startpos - 1)

I really wish mainframe REXX had regular expressions. The lack thereof
motivated me to write my own API https://github.com/daveyc/RTK.
Unfortunately, I can't use that
as this is code that's going to be shipped to customers.

>
> You could always expect parms like: INDSN[data-set-name]
> but that might cause no end of trouble.

Indeed! Square brackets vary greatly by code page so that would cause
problems. EBCDIC really is the work of the devil.

Jeremy Nicoll

unread,
Sep 29, 2017, 6:21:43 AM9/29/17
to
On Fri, 29 Sep 2017, at 10:38, David Crayford wrote:
> On 29/09/2017 4:22 PM, Jeremy Nicoll wroteOn Fri, 29 Sep 2017, at 08:24,
> David Crayford wrote:

> /* A version of parse var str . '(' v ')' which handles nested parens */
> yank: arg str
>   startpos = pos('(', str); endpos = lastpos(')', str)
>   return substr(str, startpos + 1, endpos - startpos - 1)


Surely if the exec is called with eg .... INDSN(A) ODSN(B)

this will return "A) ODSN(B" ?

--
Jeremy Nicoll - my opinions are my own.

Ward Able, Grant

unread,
Sep 29, 2017, 6:50:47 AM9/29/17
to
This may be a rookie way but how about:

parm = parm||'?'
parse var parm . '(' val '?'

Regards – Grant





DTCC Internal (Green)
DTCC DISCLAIMER: This email and any files transmitted with it are confidential and intended solely for the use of the individual or entity to whom they are addressed. If you have received this email in error, please notify us immediately and delete the email and any attachments from your system. The recipient should check this email and any attachments for the presence of viruses. The company accepts no liability for any damage caused by any virus transmitted by this email.

Jeremy Nicoll

unread,
Sep 29, 2017, 7:25:23 AM9/29/17
to
On Fri, 29 Sep 2017, at 11:50, Ward Able, Grant wrote:
> This may be a rookie way but how about:
>
> parm = parm||'?'
> parse var parm . '(' val '?'

So, if parm starts off as " this that other idsn(one) fred
odsn(jim) "

we then append a "?", do the parse, and val becomes

"one) fred odsn(jim) "


How does that help?

--
Jeremy Nicoll - my opinions are my own.

Bill Ashton

unread,
Sep 29, 2017, 7:53:42 AM9/29/17
to
It seems to me that maybe the OP can best set the rules for his parms...so
maybe he would use INDSN(a DSN only) and INMEM(member). Then in the code,
test for the existence of a DSN and member, or only a DSN, and work from
there. I think that leaving the parms open to the discretion of the caller
is an invitation to a train wreck.

Billy
--
Thank you and best regards,
*Billy Ashton*

David Crayford

unread,
Sep 29, 2017, 8:44:16 AM9/29/17
to
On 29/09/2017 6:21 PM, Jeremy Nicoll wrote:
> On Fri, 29 Sep 2017, at 10:38, David Crayford wrote:
>> On 29/09/2017 4:22 PM, Jeremy Nicoll wroteOn Fri, 29 Sep 2017, at 08:24,
>> David Crayford wrote:
>> /* A version of parse var str . '(' v ')' which handles nested parens */
>> yank: arg str
>>   startpos = pos('(', str); endpos = lastpos(')', str)
>>   return substr(str, startpos + 1, endpos - startpos - 1)
>
> Surely if the exec is called with eg .... INDSN(A) ODSN(B)
>
> this will return "A) ODSN(B" ?

Nope because I tokenize each parameter as a word.  So EXTRACT
INDSN(dsname) OUTPATH(pathname) TEMPDIR(temp-dir) is 4 words. It's a
very basic parser
and quite brittle but that's ok it's not designed to be perfect. If I
wanted production strength parsing I would break out the compiler.

David Crayford

unread,
Sep 29, 2017, 8:53:37 AM9/29/17
to
Well I did consider using a Unix like convention with options like:

extract --indsn DSNAME(MEMBER) --outpath /u/txcweb --tempdir /tmp

Which is very easy to parse but doesn't have a syntax familiar to
mainframe folks.

Steve Coalbran

unread,
Oct 13, 2017, 10:44:04 AM10/13/17
to
I am trying to match a text-string to a Dataset-Pattern (style) string.

For instance, assume that there was a MATCHPATT function...

MATCHPATT:
ARG varval,vertype,verpatt

/* clever stuff */
RETURN yesno


IF( MATCHPATT(field,P,'*ABC%DEF*Y') THEN DO

...

END

ELSE NOP


then get a 1 for: field = 'M222ABCTDEFBLIMEY'

and a 0 for: field = 'NNNACBTDEFOHDEARY'


Anyone got something that'll do this?

Thinking translating to a REGEXP which I believe REXX supports? Never used it though!


TIA 😎

/Steve

Paul Gilmartin

unread,
Oct 13, 2017, 11:05:21 AM10/13/17
to
On 2017-10-13, at 08:43, Steve Coalbran wrote:
>
> Thinking translating to a REGEXP which I believe REXX supports? Never used it though!
>
I believe regular expressions are supported by ISPF Edit, but not
by Rexx itself.

-- gil

ITschak Mugzach

unread,
Oct 13, 2017, 11:54:43 AM10/13/17
to
Is this me only or on aecond call ita ACB?

ITschak

בתאריך 13 באוק׳ 2017 4:44 אחה״צ,‏ "Steve Coalbran" <coal...@hotmail.com>
כתב:

Robin

unread,
Oct 13, 2017, 1:48:47 PM10/13/17
to
I did a limited version of what you are wanting to do. The processing is contained within the Assembler Rexx function SCCPDSD on the CBT site. Therein the logic is to determine if a PDS member name matches a pattern. SCCPDSD is not the function you are looking for in that its purpose is to load the directory entries of a PDS into a (Rexx) stemmed variable. The code within SCCPDSD will filter the member names according to the callers selection pattern.

I tried but failed to translate the assembler code into native Rexx.

-----Original Message-----
From: TSO REXX Discussion List [mailto:TSO-...@VM.MARIST.EDU] On Behalf Of Steve Coalbran
Sent: October 13, 2017 10:44 AM
To: TSO-...@VM.MARIST.EDU

Andreas Fischer

unread,
Oct 13, 2017, 2:15:53 PM10/13/17
to
hi,

i wrote a rexx function once using bpxwunix and the /bin/expr command
then, but this was more like a proof of concept. the overhead of
establishing a unix environment does not pay off for that if you use it
massively.

practically, i never had the need for it, usually i process data set
patterns only when invoking the catalog search interface to get the
matching cataloged data sets. but if i remember well, several rexx
examples for your needs have been spread by this list in the past.

regards,
andi


TSO REXX Discussion List <TSO-...@VM.MARIST.EDU> schrieb am 13.10.2017
16:43:49:

Walter Pachl

unread,
Oct 13, 2017, 3:42:33 PM10/13/17
to
I hacked a little program (regx.rex)

works for the problem posted

E:\>rexx regx
M222ABCTDEFBLIMEY -> 1
NNNACBTDEFOHDEARY -> 0

but may need more work

you can get it here: https://www.dropbox.com/s/7q40vvkl1ftwpg5/regx.rex?dl=0
https://www.dropbox.com/s/7q40vvkl1ftwpg5/regx.rex?dl=0

Regards

Walter

Ze'ev Atlas

unread,
Oct 14, 2017, 7:36:52 PM10/14/17
to
Again, sometime next week John Gateley and I will publish a Rexx API to PCRE and thus a standard (used by PHP and others) regex library would be available in TSO-REXX to any facility that allows open source (strictly BSD, no GPL contamination). It will be available on the CBT.Ze'ev Atlas

Sent from Yahoo Mail on Android

On Fri, Oct 13, 2017 at 3:43 PM, Walter Pachl<christel....@CHELLO.AT> wrote: I hacked a little program (regx.rex)

Bob Bridges

unread,
Oct 15, 2017, 8:11:13 PM10/15/17
to
I once wrote up a REXX subroutine that compares a DSN to a RACF-style DSN pattern and returns 1 or 0. Walt Farrell, whom I think most still regard as the RACF Top Dog even now that he's retired from IBM, pointed out that it might not catch a few oddball resource names, but we still use it at my company for ordinary non-critical functions such as specifying datasets for unarchiving.

The RACF masks are % for one character, * for one node or the remainder of one node, and ** for zero or more nodes (usable only once in a pattern). The sample you gave would not be regarded as a valid z/OS DSN, though, so maybe you meant some other dataset than z/OS?

---
Bob Bridges, cell 336 382-7313
robhb...@gmail.com
rbri...@InfoSecInc.com

/* As mere biological entities, each with its separate will to live and to expand, we are apparently of no account; we are cross-fodder. But as organs in the Body of Christ, as stones and pillars in the temple, we are assured of our eternal self-identity and shall live to remember the galaxies as an old tale. -C S Lewis, "The Weight of Glory" */

-----Original Message-----
From: TSO REXX Discussion List [mailto:TSO-...@VM.MARIST.EDU] On Behalf Of Steve Coalbran

Pete Hartung

unread,
Oct 16, 2017, 9:15:52 AM10/16/17
to
Here is a copy of my Filter routine along with 2 examples.
I think I posted this once before.

/* REXX */
/* Example one: Does the string match the mask? */
mask1 = 'XX.Y*Y.**.*ZZZ'
mask2 = 'XX.Y%%Y.**.*ZZZ'
string1 = 'XX.Y1R2T3Y.ZYX.D53ZZZ'
string2 = 'XX.Y1R2T3.ZYX.D53ZZZ'
string3 = 'XX.Y12Y.ZYX.D53ZZZ'
string4 = 'XX.Y123Y.ZYX.D53ZZZ'

say '('string1') ('mask1')'
If Filter(string1,mask1) then say 'YES'; else say 'No, only upto' EndStr
say '('string2') ('mask1')'
If Filter(string2,mask1) then say 'YES'; else say 'No, only upto' EndStr
say '('string1') ('mask2')'
If Filter(string3,mask2) then say 'YES'; else say 'No, only upto' EndStr
say '('string2') ('mask2')'
If Filter(string4,mask2) then say 'YES'; else say 'No, only upto' EndStr

/* Example two: does string contain mask? */
String = 'The quick brown fox jumps over the lazy dog'
ruler = '1...5....0....5....0....5....0....5....0...4'
Mask.1 = '%%E%%%%%%%BROWN'
Mask.2 = '*QUICK*F%X'
Mask.3 = '*t '
Mask.4 = 'THE%Q%*%**BR%%%'
Mask.5 = ' E*BROWN'
Mask.6 = '*THE*BRXWN'
Mask.7 = '*THE*BROWN**F%%'
Mask.8 = '*FOX*'
Mask.9 = '*F%X*'
Mask.10= '*F%*%MP*G'
Mask.11= 'T*E LAZY DOG'
Mask.12= '*E LAZY DOG'
Mask.13= '* JU'
Mask.14= '*O%E'
Mask.15= '%*V*D'
Mask.16= '*G*'
Mask.17= '*G%'
Mask.18= '-*7'
say string
say ruler
Do m = 1 to 18
If Filter(string,mask.m,1) & left(mask.m,1) <> '-' then
Status = 'Found ',
right(BgnStr,3) Right(EndStr,3),
substr(string,BgnStr,EndStr+1-BgnStr)
else Status = 'NOT Found' z
say left(Mask.m,25) Status
End
Exit 0

/*-----------------------------The Filter----------------------------*/
Filter:
Arg stringx,maskx,strt
/* Adjust mask: */
/* if there is an * in a set of wild characters it should be */
/* the last char in the set ie %%*%**%% s/b %%%%%* */
/* only one * is needed within each set of wild characters */
/* an ending * is not needed */
/* To find a match anywhere within the string, */
/* put an * at the begining of the mask */
/* To return "true" if the string does not match the mask, */
/* put a - at the begining of the mask */
/* mask = '-*X' returns true if X not in string */
If maskx = '' then Return 1
RtnCd0 = 0; RtnCd1 = 1
If left(maskx,1) = '-' then Do
maskx = substr(maskx,2)
RtnCd0 = 1; RtnCd1 = 0
End
Do while pos('*%',maskx) > 0
maskx = overlay('%*',maskx,pos('*%',maskx))
End
Do while pos('**',maskx) > 0
maskx = delstr(maskx,pos('**',maskx),1)
End
maskx = strip(maskx,'T','*')
LenMask = length(maskx)
If LenMask = 0 then Return RtnCd1
If datatype(strt) <> "NUM" then strt = 1
If strt < 1 | strt > length(stringx) then strt = 1
Nxtj = strt-1 /* The next position (-1) of the string to check */
Nxti = 1 /* The next position of the mask to match */
BgnStr = 0
EndStr = 0
Savei = Nxti /* save where we are in mask and string */
Savej = Nxtj
SaveBgnStr = 0
NoAst = 1
Do z = 1 to length(stringx) /* z = number of iterations */
i = Nxti
j = Nxtj
Do while j < length(stringx)
j = j+1
Select
when substr(maskx,i,1) = '*' then Do
Savei = i /* save where we are in mask and string */
Savej = j
SaveBgnStr = BgnStr
NoAst = 0
/* look for mask/string match after an *, */
/* if found set pointers to continue looking */
If substr(maskx,i+1,1) == substr(stringx,j,1) then Do
i=i+1
j=j-1
End
End
when substr(maskx,i,1) = '%' |,
substr(maskx,i,1) == substr(stringx,j,1) then Do
If BgnStr = 0 then BgnStr = j
EndStr = j
i=i+1
If i > LenMask then Return RtnCd1
End
/* mismatch, go back to last * and look again */
otherwise Do
If NoAst then Return RtnCd0
BgnStr = SaveBgnStr
Nxti = Savei
Nxtj = Savej
iterate z
End
End
End
Return RtnCd0
End
Return RtnCd0
/* */

-----Original Message-----
From: TSO REXX Discussion List [mailto:TSO-...@VM.MARIST.EDU] On Behalf Of Steve Coalbran
Sent: Friday, October 13, 2017 10:44 AM
To: TSO-...@VM.MARIST.EDU

Steve Coalbran

unread,
Oct 16, 2017, 10:27:56 AM10/16/17
to
Thanks Pete,

This is really useful! Especially the second set of examples!

/Steve


________________________________
From: TSO REXX Discussion List <TSO-...@VM.MARIST.EDU> on behalf of Pete Hartung <phar...@RITEAID.COM>
Sent: 16 October 2017 13:15
To: TSO-...@VM.MARIST.EDU
Subject: Re: [TSO-REXX] Matching a text-string to a Dataset-Pattern (style) string
0 new messages