StavromulaBeta / cognate-rewrite

Cognate but like much faster and less stable
https://cognate-lang.github.io
BSD 2-Clause "Simplified" License
7 stars 0 forks source link

Macro ideas #1

Open dragoncoder047 opened 2 years ago

dragoncoder047 commented 2 years ago

Just found this repository -- i figured it would be the better place to share this idea instead of the main Cognate repo.

Instead of Def and Let being parser macros, I propose that they be syntactic macros much like defmacro works in Lisp. (Cognate lists are the same sort of linked list as Lisp lists are, anyway.)

The general process would be this:

  1. For each logical line (i.e. a group of tokens between ;s even if it spans multiple text lines) Cognate would scan backwards from the end until it gets to a macro.
  2. The current work stack depth would be recorded.
  3. Everything after (to the right of) the macro would be pushed unevaluated onto the work stack and the macro's code executed.
  4. If the old work stack depth (from step 2) is still less than the current work stack depth, everything on top of that is popped off and replaced to the "written here" position.
  5. When the beginning of the line is reached, everything on it (which may have been transformed by macros) is executed as usual.

For example, in Def X Y Z;, Def would be passed the stack X Y Z, do what it does (define X to mean Y) and then returns the stack Z which is executed as usual. In the case of Y; Def X;, Def would see the same thing because the Y on the stack would be silently appended to the tokens after the macro.

I don't know how useful this would be, but provided that there will be a way to differentiate a macro from a regular lambda, there could be a new Make-syntax or Def-macro construct added.

StavromulaBeta commented 2 years ago

I've had a few similar ideas to what you're saying, though there's still a lot I need to work out.

Macros would really help generalise language constructs, but they also risk adding a lot of complexity. Most of what macros do can be achieved using combinators, which is why I'm being cautious about adding them.

Also would the Defmacro (or whatever it'd be called) be a macro itself? If so it'd have to read 2 arguments, including a block at compile time.

Btw Def only actually reads one token at compile time, allowing it to be bound to the output of a combinator, such as in the Def F as Case ... case.

dragoncoder047 commented 2 years ago
  • Should macros 'see' semicolons?

Since the semicolon marks the end of the line they would get everything up to the end of the line unevaluated.

On the other hand, Def and Let only need to see one token ahead (i.e. the symbol to bind to), so actually macros may be better off having a field that specifies how many unevaluated arguments they need (and Def and Let would have it both set to 1 -- edit: and Defmacro would have it set to 2). Maybe that would be a better approach.

  • Should the entire language be available at compile time? This would mean building another evaluator.

Lisp has #.(...) to force evaluation at compile-time (for e.g. to cache the computation of a large constant) so yes, possibly. Only if you want to support this kind of language construct.

  • What would the data type for an unevaluated word be? What about a literal?

Since you already have symbols, an unevaluated word would probably best be a symbol. Of course, there are two ways you could go on lowercased symbols:

  1. If a user tries to Do a lowercase symbol (i.e. Do \foo;) the symbol could just be silently pushed to the stack (a no-op, same as Do 1; with numbers). A runtime warning might also be issued.
  2. The symbol could be silently uppercased and run, and then of course if Foo doesn't exist, the program would crash.
  • Would I need to add quasiquoting?

Only if you want to add syntactic macros. I'm not sure how to concatenate two lambdas together so the second has access to the first's Let vars, or if it is even possible, so this may be out of the scope of Cognate.


Another idea I had was keyword/optional arguments, using constructs of the form foo: to set it and :foo to get it. The colon-at-the-end form would take one item from the stack and push it onto a "keywords mapping", and colon-at-the-beginning looks it up and pushes it onto the stack. For example the Range command could be extended so you could do something like Range 1 to 10 step: 2 and it would produce the list 1 3 5 7 9, whereas if step: were not set it would still only take 2 items from the stack and produce the list 1 2 3 4 5 6 7 8 9 10. More would have to be decided on the behavior of these (should :foo copy the argument out or pop it out?) and if any more should be added (such as :foo? to get it but default to what's on the stack or something).

dragoncoder047 commented 2 years ago

I had another idea: Def and Let could be implemented as a runtime macro and a function.

How they would work is before the line is run Cognate would scan left-to-right (backwards) through each line and when it sees a macro it pushes the rest of the list to the stack, executes the macro's code, pops the result off the stack, and then splices the code list back together, and moves on. Then when it executes the line, it will see Define \Foo (...) instead of Def Foo (...).

This would necessitate that code be a doubly linked list (so it could scan forwards and backwards).

StavromulaBeta commented 2 years ago

While these are all really good ideas, they each seem to hinge on being able to do arbitrary symbol lookup at runtime - which is perfectly fine in an interpreted setting but wouldn't really fit here because cognate is an AOT compiled language. Macros with code evaluating at compile time could definitely be made to work - though with a fair bit of effort, but anything that requires a dynamic word lookup is probably off the table.

dragoncoder047 commented 2 years ago

perfectly fine in an interpreted setting but wouldn't really fit here because cognate is an AOT compiled language.

I was afraid of that, after I noticed that cognate.c and runtime.c never interact.

I am currently working on a new programming language called TEHSSL and it's modeled after Cognate. I only have it about 1/4 done right now, so it doesn't really work at all. (But the garbage collector works, and it works really well!) Maybe you'd like to take a look at it once it's complete.

StavromulaBeta commented 2 years ago

I am currently working on a new programming language called TEHSSL and it's modeled after Cognate. I only have it about 1/4 done right now, so it doesn't really work at all. (But the garbage collector works, and it works really well!) Maybe you'd like to take a look at it once it's complete.

Ah cool I'll take a look!