Syntax/Semmantics for an extensible language

Patrick Premont
Wed, 3 Apr 1996 08:43:34 -0500

> First Have a default syntax for programs -- with no extension
> I propose a simple lisp syntax
> Grammer:
> symbol -- sequence of characters no spaces
> function-name -- symbol you can execute // sematic constraint 
> function-call ==> ( function-name arg-list)
> arg-list ==> nil | arg | arg arg-list      // nil is no symbol at all
> arg ==> symbol | function-call
> Semmantics:
> To allow this grammer to be extensively extensible you need only
> support two distint types of function-call exection.
> Run-time execuition -- the normal variant
> Compile-time execution -- Functions of this type run during
>   compilation, and optionally may be allowed to parse the file from
>   where they first occur.  Semmantically these functions are just
>   arbitray functions that run during compilation time.  
>     They may be used for:
>   installable parsers, 
>   object initialization,
>   macro expansion,
>   comments.
> ---
> This organization is basically stolen from Forth.

I'm surprised that you credit Forth for this. You basicaly describred
how Scheme (with macros like in MIT-Scheme works) is structured.
* SEEend of message for very-short description of MIT-Scheme macros.
Except for the suggestion of letting "compile-time functions" read the
rest of the file, which I wouldn't allow. Well, I mean that there are
macros which are part of the program and there is interpreter code
which isn't.  Both execute at compile time but they are different. The
macro is only aware of the arguments on which it is applied and its
semantics is defined by the interpreter. But one could allow changing
the interpreter though some special-form like (add-eval-rule ...) or
(replace-eval ...), although macros already provide a clean and
controlled way of doing that to some extent.

There are other ways for code to execute at compile-time also :
(define a (+ 4 5)). The sum will be computed at compile time and this
is very different from macros of interpreter code. Execution time is
an attribute of code but it is not a defining one. What is important
is the semantics of the language and then the implementations is free
to execute whatever it wants whenever it wants to comply with the
semantics (remember partial evaluation).

So all the applications of compile-time constructs you mentionned
above should be accomplished through special forms (or macros when
applicable) which define the proper semantics... (replace-reader ...)
which I would try to avoid (macro ...) (add-eval-rule ...) which might
not do much more than macros. Anyway, macros like in MIT-Scheme should
be enough for most applications.

> How would this organization work for the HLL?

Except for my objections, it seems that's where we were heading.

> This organization should make syntax completely orthogonal to the
> semantics of the language.

It already is. You can always replace the Scheme "read" function and
write your code in infix or whatever. And allowing a program to change
the reader while it is being read is allowing semantics to change
syntax, making them less orthogonal. Then you can't change the syntax
of a program without changing (semantically) the parts of it which
define new syntaxes, unless you don't modify any of the
program-specifies syntaxes.

> My thought example.
> ---
> (module x 
>   // blah blah blah 
>   // code in the default syntax
>   (require 'c-parser)
>   (c-parser 
>     int main(int argc, char *argv)
>      {
>        /* blah blah some C code */
>      }
>   )

Not bad, if you are willing to force all special syntaxes into a
standard s-exp like (something-parser ...) then I'm not as repulsed by
the idea.  Note that (c-parser "int main ....") would then work just
as well and would not need anything more special that a standard
string to whatever function. BTW, what do you do with the parsed stuff
returned but (c-parser ...) ? Or probably you mean that c-parser doesn't
just parse but also evaluates ? No problem, it can still so that as
a simple function. You might might want it to be a macro if you
want it to define main to something (a macro which would return
(define main ...).

>   (require 'asm-i386-parser)
>   (asm-i386-parser
>    # blah blah you get the idea
>   )
> )

Conclusion: macros a nice and powerful, let's use them. 


MIT-Scheme-style macros :

It is just a function which takes its arguments just after the parsing
stage and returns something else which is already parsed.  For
example, to define an if with the test in the middle :
(define-macro (if-m x y z) (list 'if y x z))
I'm not sure that is the exact syntax in MIT-Scheme (I think so) but
that's not important. What is is that what is returned on (if-m 3 #t 4)
is the list (if #t 3 4) which is then evaluated as if it had been
entered directly in the source.