Open dragoncoder047 opened 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.
- 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:
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.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).
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).
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.
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.
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!
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
andLet
being parser macros, I propose that they be syntactic macros much likedefmacro
works in Lisp. (Cognate lists are the same sort of linked list as Lisp lists are, anyway.)The general process would be this:
;
s even if it spans multiple text lines) Cognate would scan backwards from the end until it gets to a macro.For example, in
Def X Y Z;
,Def
would be passed the stackX Y Z
, do what it does (defineX
to meanY
) and then returns the stackZ
which is executed as usual. In the case ofY; Def X;
,Def
would see the same thing because theY
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
orDef-macro
construct added.