I'm finally getting serious about using complex macros for (properly) parsing and processing domain specific languages in PLT Scheme. To date, I have just been using syntax-rules to do the minimum parsing needed to break out the basic elements of a construct and generate code that passes them to the code that does the real work at run-time. This is relatively easy to write, but pretty much limits error messages to 'bad syntax', which isn't real useful. But, I haven't found any really good tutorial material to help me get started.<br>
<br>So first, can anyone point me to some good material to get started with syntax-case, define-for-syntax, and so on for doing this right? I've gotten started by looking at things like package.ss (in the scheme collection) that have some complex macros.<br>
<br>An example of what I would like to do is the define-rule macro in the inference collection.<br><br>Here is a contrived example of a forward-chaining rule:<br><br><font face="courier new,monospace">(define-rule (test-rule-1 test-rule)<br>
(class1 ?a b c)<br> ?class2 <- (class2 ?a b c)<br> (or (and (class3 ?a b c)<br> (class4 ?a b c))<br> (class5 ?a b c))<br> ==><br> (retract ?class2)<br> (printf "a = ~s~n" ?a))<br>
</font><br>And, here is an example of a backward-chaining rule:<br><br><font face="courier new,monospace">(define-rule (example-rule ancestor-rules)<br> (ancestor ?x >y)<br> <==<br> (ancestor ?x ?z)<br> (ancestor ?z ?y))<br>
</font><br>In the current (not so good) implementation, the define-rule macro just breaks the rule clauses into goal clauses, pattern clauses, and action expressions using <== and >== as delimiters. Then, it just passes those raw chunks in the run-time code to actually parse and process them.<br>
<br>I would like to do is to do all of the parsing and checking at expansion time and provide decent error messages. So, at expansion time, I'd like to (1) parse out the clauses, (2) check the syntax of the goal and pattern clauses, (3) normalize the form of the pattern clauses to (or (and . atomic-patterns) ...), and (4) generate the appropriate code. (Note that the action expressions are 'just code' that will be generated into a procedural object and 'checked' that way.) I am comfortable with (1) and (4) - at least with syntax-rules - and I have a working version using syntax-case with error checking at that level (e.g., the rule name must be an identifier; there can't be multiple <== or ==>; if both <== and ==> are present, they must be in that order). I can do (2) in run-time code, but don't know how to convert that into expansion-time code - maybe it's easy, maybe not. Finally, (3) is some pretty complex run-time code with several mutually recursive procedures doing the normalization. I don't even know where to start doing this (in something other than brute force pattern matching with syntax-rule/syntax case, but there are several dozens of cases and there has to be a better way).<br>
<br>So, can someone point me in the right direction to get started? Good examples in the current PLT Scheme code base would work.<br><br>And, just for the curious, here is a real example of a forward-chaining rule to show what they can look like.<br>
<br><span style="font-family: courier new,monospace;">;;; If a cell is numbered, remove that number from any other cell in the<br>;;; same row, col, or box.<br>(define-rule (rule-5 sudoku-rules)</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> (cell ?row ?col ?box </span><span style="font-family: courier new,monospace;">?value : (number? ?value))<br> cell-1 <- (or (cell ?row-1 : (= ?row-1 ?row)<br> ?col-1 : (not (= ?col-1 ?col))<br>
?box-1<br> ?value-1 : (and (pair? ?value-1)<br> (memv ?value ?value-1)))<br></span><span style="font-family: courier new,monospace;"> (cell ?row-1 : (not = (= ?row-1 ?row))<br>
?col-1 : (= ?col-1 ?col)<br>
?box-1<br>
?value-1 : (and (pair? ?value-1)<br>
(memv ?value ?value-1)))</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> (cell ?row-1<br>
?col-1<br>
?box-1 : (and (= ?box-1 ?box)<br> (or (not (= ?row-1 ?row)<br> (not (= ?col-1 ?col))))<br>
?value-1 : (and (pair? ?value-1)<br>
(memv ?value ?value-1)))</span>)<br> ==><br> (replace ?cell-1 `(cell ,?row-1 ,?col-1 ,?box-1 ,(delete ?value ?value-1))))<br style="font-family: courier new,monospace;">
<br>As always, thanks in advance for any advice, directions, mentoring, ...<br>Doug<br>