Hi Bob,
I just realized that there are two bugs in my code and a bug in your specs:
1. You wrote:
>excluded-files: {^(?!(notxt|ask_new|only_owner_may_edit))}
>is-memberof-re excluded-files "myfavoritemartian.html"
>>> false
Note that excluded-files contains a negate instruction "?!(". Therefore,
what we are looking for is NOT the list of options.
In other words, anything that does NOT match any of the options should
evaluate to TRUE.
Therefore
>is-memberof-re excluded-files "myfavoritemartian.html"
should evaluate to TRUE, not to FALSE, as you proposed in your example.
What's a little misleading is that you called the string excluded-files.
One tends to think that files that do not match the options in the string
are files that are not being excluded, and therefore if a file does not
match the list, it should be FALSE == do not exclude this file.
Then again, one could also interpret the name of this list as saying, all
files that are in this list are included, therefore, when you negate the
list, you get the excluded files, which means that TRUE == this a file not
contained, excluded from this list.
In any event, the negation instruction is there, which means that:
1. If a file MATCHES one of the conditions in the list,
a) then the match will return true,
b) since we are instructed to negate, true becomes false
c) therefore we report the fact that a file matched some item in the list
as FALSE.
2. If a file did NOT MATCH any of the conditions in the list,
a) then all matches return false
b) we are instructed to negate, therefore false becomes true
c) therefore we report that a file did not match any of the items in the
list as TRUE.
It did not match any prefixes in the list, aha, it is indeed a file that is
excluded from the list, return TRUE, yep, it's an excluded file.
2. Where I say
either not all [front-anchor end-anchor] [
return false
it should be
either not any [front-anchor end-anchor] [
return false
3. Where I say
either use-negate [
return not all check-block
it should be
either use-negate [
return not any check-block
4. It would have been more elegant to retrieve the options from the string
while we are parsing it. It would have saved us a few lines of code in the
function, the mark: and end: in the rules, and the rule for checking for
closing paren, at the expense of one additional rule to collect the options.
Here's the fixed and improved function and rules:
REBOL []
verbose: off ;- set to on in order to trace operation
vprint: func [string] [
if verbose [print string]
]
reset: func [] [
front-anchor: end-anchor: use-negate: false
condition-block: make block! []
]
front-anchor-rule: [thru "^^(" mark: (vprint "found front-anchor"
front-anchor: true)]
negate-rule: [thru "?!(" mark: (vprint "in negate-rule" use-negate: true)]
end-anchor-rule: [thru ")$" end: (vprint "found end-anchor" end-anchor: true)]
collect-options: [some [copy condition to "|"
(vprint rejoin ["in collect-options" condition] append condition-block
condition) skip]
]
parse-rule: [(reset)
some [front-anchor-rule | negate-rule] collect-options
end-anchor-rule to end
]
is-memberof-re: func [ condition [string!] candidate [string!]
/local check-block match
] [
parse condition parse-rule
vprint mold condition-block
either all [front-anchor end-anchor] [
match: candidate
][
either not any [front-anchor end-anchor] [
return false
] [
if (length? parse candidate ".") = 1 [return false]
match: do either front-anchor [ :first ] [ :second ] parse candidate "."
]
]
check-block: foreach option condition-block [append [] (= match option)]
vprint mold check-block
either use-negate [
return not any check-block
] [
return any check-block
]
]
userlist: {^^(ted|admin|bobr|andrew|elan|keith|cheryl|joel|brady)$}
current-user: "bobr"
excluded-files: {^^(?!(notxt|ask_new|only_owner_may_edit))}
print [
"is-memberof-re userlist current-user:"
is-memberof-re userlist current-user
]
print [
"is-memberof-re excluded-files myfavoritemartian.html:"
is-memberof-re excluded-files "myfavoritemartian.html"
]
;- end
;- Elan >> [: - )]