A way to pass arguments to PARSE rules, so that it becomes possible to define parametrized rules.
Despite the huge power of the parse function, and the ability to define rules that can be reused inside other rules (they are just blocks), there is a lack of generic, reusable rules that solve very common problems. The main reason for this is the inability of passing parameters to rules, which means that only the most simple rules can really be made reusable across different "callers". This module proposes a possible solution.
Basically, we create a "global" stack, and use it to pass arguments to rules. In other words, we add a stack to the parse dialect so that it can be used to pass arguments around.
〈Overview〉 ≡
stack: [ ]
push-arguments: func [
"Push values into the arguments/results stack"
values [block!]
] [
repend stack values
]
push-result: func [
"Push one value into the arguments/results stack"
value
] [
append/only stack :value
]
pop-arguments: func [
{Pop and set values from the arguments/results stack} [catch]
names [block!] "Words to set to the values from the stack"
] [
if greater? length? names length? stack [
throw make error! "Not enough arguments in the stack"
]
set names names: skip tail stack negate length? names
clear names
]
pop-result: func [
"Pop one value from the arguments/results stack" [catch]
] [
throw-on-error [
also last stack
remove back tail stack
]
]
Then, make-rule is just a shortcut for the common case of defining a rule with some arguments:
〈Overview〉 +≡
make-rule: func [
{Create a rule that takes arguments (via PUSH-ARGUMENTS)}
spec [block!] "Argument spec (similar to function spec)"
body [block!] "PARSE rule"
/local 〈make-rule's locals〉
] [
〈Create a rule taking the arguments defined in spec〉
]
The body argument is modified, so if you plan to reuse it you must use copy.
Say we have the rule:
〈Examples〉 ≡
[#"<" copy name to #">" skip]
It would be nice to reuse this in a number of places; however, this means that name has to be global so that it is available to all rules that call it. If we could provide a word as argument... now we can!
〈Examples〉 +≡
make-rule [word /local name] [
#"<" copy name to #">" skip (set word name)
]
You can then call 〈Examples〉 this way:
(push-arguments ['tag-name]) 〈Examples〉 (if tag-name = "a" [...])
〈Create a rule taking the arguments defined in spec〉 ≡
ctx: clear [ ]
local?: no
args: copy [ ]
parse spec [
some [
set word word! (
append ctx to set-word! word
unless local? [append args word]
)
|
/local (local?: yes)
|
skip
]
]
ctx: context append ctx none
bind args ctx
bind body ctx
head insert/only body to paren! reduce ['pop-arguments args]
〈make-rule's locals〉 ≡
ctx args word local?