On 2023-07-07, albert@cherry.(none) (albert) <albert@cherry> wrote:
> I'm trying to implement mal on ciforth
>
https://github.com/kanaka/mal
> Amounts to implementing a dialect of lisp using a dialect of Forth.
>
> In step 8 I encounter the following:
>
> -Add a new attribute is_macro to mal function types. This should
> default to false.
>
> Am I mistaken or is this the familiar immediate flag added to
> Forth definitions?
It may be similar. Macros are invoked during expansion.
Code can be expanded without being executed immediately. Or
at all. For instance
;; function is only defined, not called; but macro is called
(defun foo ()
(macro ..))
;; macro likely called in spite of being dead code.
;; (unless expander handles trivial dead code elimination cases).
(if nil
(macro ...))
> - Add a new special form defmacro!. This is very similar to the def!
> form, but before the evaluated value (mal function) is set in the
> environment, the is_macro attribute should be set to true.
>
> Much like colon definitions, later to be modified by IMMEDIATE to add
> that behaviour.
There amy be some similarities, but that doesn't necessarily mean
you can solve the MAL task by mapping macros directly to IMMEDIATE
words.
>
> - Add a macroexpand function: This function takes arguments ast and env.
> It calls is_macro_call with ast and env and loops while that condition
> is true.
>
> How is this different from executing an immediate definition in
> the middle of deferring an action? The only difference seems
> to be that Forth does this much cleaner and with less caveats.
> First and foremost it is far easier to keep track of arguments.
> They reside on the stack.
The user of the macro system doesn't have any problem with argument
tracking; arguments nicely appear as lexically scoped identifiers,
bound on entry into the function.
If you're writing code in Forth to make that work, then of course
that is your problem.
If you're making a Forth from scratch, you have to implement
several stacks yourself.
If you're making a Lisp from scratch, you have to implement
environments.
The simplest possible implementation of environments is stack-like.
The environment is the head of an association list, which looks like
this: ((b . 1) (a . 2)) for an environment which contains two
variables a and b bound to values 1 and 2.
A new binding is created with cons:
(cons (cons 'c 3) previous-env)
In some Lisps like Common Lisp, there is an acons for this:
(acons 'c 3 previous-env)
a new env is returned which looks like ((c . 3) (b . 1) (a . 2)).
The new environment doesn't clobber the old one; the environment
is a local variable in a recursive interpreter, passed around through
its recursion.
That's a reference model for a lexically scoped Lisp that came
into the Lisp culture mainly via the Scheme influence.
A lexically scoped Lisp doesn't have to reveal to the programs
the detailed representation of environments. Unless a special
escape hatch is provided for it, programs don't see the hidden
"env" variable. This is a good thing because it allows programs
to be compiled. Compiled code uses a radically different
representation of the environment.
> Am I the first to notice this? Chuck Moore was a student of McCarthy.
> Perhaps he invented Forth as a simpler version of lisp.
Even assembly languages have actions that are done now, while
assembling the code, and not at run-time.
--
TXR Programming Language:
http://nongnu.org/txr
Cygnal: Cygwin Native Application Library:
http://kylheku.com/cygnal
Mastodon: @
Kazi...@mstdn.ca