F-expressions are ancient (1960's era Lisp). newLisp dug them out from a Lisp landfill of discarded cruft; they do not chronologically belong in 1991. Tcl has them too, incidentally. That is to say, Tcl allows for user-defined interpretive operators that are very much like fexprs. Bash's eval can almost do something like this; an eval sees the surrounding scope. What's missing is the sugar to hide the eval.
"Text based fexpr":
gen_swap_code()
{
echo "local tmp=\$$1;"
echo "$1=\$$2;"
echo "$2=\$tmp;"
}
fun()
{
local a=3
local b=4
eval $(gen_swap_code a b) # sugar needed here
echo $a $b
}
$ fun
4 3
I've used Tcl and Scheme fairly extensively so it's interesting to compare them. Scheme has highly developed macros, and many implementations have non-hygienic macros along with syntax-rules. Most of the coders I know say it's rare to use anything other than the hygienic system, which is pretty easy to learn and quite powerful.
Tcl doesn't have comparable macros, though some writers say in Tcl coding, the whole thing is like writing a bunch of macros anyway, but I'm not sure I buy that.
I'm also not entirely clear what you meant by "user-defined interpretive operators ...". Current Tcl versions provide many facilities for specifying semantics of expressions (proc, ensembles, interpreters, lambdas, etc.) and the ability to redefine most any operator or built-in function if one so desires.
The philosophy of "it means what I say it means" can lead to strange constructions, but that is a basis for describing Tcl as "macro-like" in its abundant, highly flexible features. The many ways it reflects Lisp/Scheme-like traits is truly part of its appeal.
Using fresh symbols is not enough. GENSYM creates uninterned symbols - those are not interned in a package. Thus referencing them in user code is not possible.
Shadowing 'SETF' in Common Lisp would be non-conforming. SBCL for example detects it and gives an error. The ANSI CL Standard, Section 11.1.2.1.2.
http://www.lispworks.com/documentation/HyperSpec/Body/11_aba...
Still for user code in macro expansions it can be a problem.
If the SWAP macro really should work with places in a semantic compatible way, then it needs to do more. Compare with the built-in ROTATEF macro. It makes sure that place subforms are only evaluated once.
? (let ((a (vector 1 2)))
(swap (aref a (print 0)) (aref a (print 1)))
a)
0
0
1
1
#(2 1)
Note that it prints 0 and 1 twice.
The built-in ROTATEF does it better: it prints them only once. This shows that the subforms are only evaluated once.
? (let ((a (vector 1 2)))
(rotatef (aref a (print 0)) (aref a (print 1)))
a)
0
1
#(2 1)
The EACH-IT macro has a bug: the body needs to be put into a PROGN. Otherwise the LOOP macro might think that parts of the body are LOOP clauses.
(defmacro each-it (list &rest body)
`(loop for it in ,list
do (progn ,@body)))
For the purposes as an example, DOTIMES would be more idiomatic, since it provides the basic stuff like declarations and goto tags. It is also the traditional iteration macro for lists.
An alternative to PROGN would be LOCALLY, which also allows declarations. Thus one could declare IT to be of a certain type, for example.
However, I believe(?) that it depends on the version of the C standard whether this is hygienic or not. When I compile in GCC it certainly works fine because the i is counted as internal to the for loops scope, but I'm not sure that's always the case. There is also the separate question of whether "it" should be hygienic or not.
> However, it prevents you from writing generic macros that use any l-value – we can’t write swap!(x[0], x[1]) as we could in Common Lisp.
As long as `macro_rules!` has existed (I could be wrong about before 0.8 though), you can wrote a macro that takes any expression, and will error from the assignment when the expression is not an lvalue:
Dylan was one of the original languages to have an infix-syntax with a Lisp / Scheme style macro system. (It is fairly similar to Scheme's syntax rules, but there's an implementation of something more powerful that is used within the compiler but isn't currently available to users.)
The examples that he is demonstrating in the other languages would like this in Dylan:
define macro each-it
{ each-it (?collection:expression)
?:body
end }
=>
{ for (?=it in ?collection)
?body
end };
end;
Dylan's macros are hygienic by default, and as can be seen in the ``each-it`` macro, ``?=it`` is a way to violate hygiene without much difficulty, when needed.
My full post as linked above contains links to documentation and other minor details.
Also, the OP does the usual "everything is a function in C" error, which is weird in a text about macros in programming languages. The typeof extension, like sizeof, is not a function, it's a unary prefix operator. But nobody seems to care about that for (the standard) sizeof operator, so I guess I shouldn't be surprised.
Also, of course the local temporary variable should be marked as const.
There is also a somewhat newish approach in addition to the venerable ones mentioned: a Katahdin-style syntax extensions, built upon the natural PEG extensibility.
"Text based fexpr":