Pascal in Forth (1983)
tangentstorm.github.io
tangentstorm.github.io
refs: - https://dl.acm.org/doi/10.1145/988316.988320 - https://dl.acm.org/doi/pdf/10.1145/15022.15025
This was how they produced their famous FIG Forth listings for the various CPUs (6502, PDP-11, 8088, etc.).
I recall my friend picking this up at our local technical book store. They carried all sorts of arcane stuff like the FIG listings. Definitely in the deep end in that book store compared to you local B. Dalton.
A very cool time where it was anything goes in terms of computing.
This still sounds right to me. So far it's been a bit of a disaster everywhere I've tried it - the interface between languages is usually a mess and most developers have negative feelings about swapping back and forth between different languages within a single project (or a single source file). Text editors are a bit wtf about it too.
I think there's something fundamentally right about the many languages on one VM approach of jvm/.net in terms of sane marshalling of information between languages.
Writing one language in macros on a other makes it very hard to get sane compiler front end diagnostics to the programmer.
I think there's something to implementing the languages as macros on the one true language substrate, and separately implementing compiler front end / linter things that tell people about problems in their code, though I really hated visual studio using inconsistent language front ends in the IDE and the compiler proper back around 2010.
Interesting design space.
This reminds me of what the language I use for work does, in some respects. The primary difference between the article’s subject and mine is that (using the article’s case) the ‘Forth’ is a strongly, statically typed language and the ‘macros’ representing the ‘Pascal’ syntax are typed constructs in the language. This enables both better compiler messages (error, expansion-relative info, etc) and ensures the surface ‘syntax’ is type correct before going through what could be complex expansion/alterations via the macro.
The place I use it most often as a macro-implemented-syntax is in Type expressions in definitions (with long or complicated math formulas coming in second), for a simple example:
Macro — (type of str_longer_than) -:[ Str_nt -> u32 -> Bool ]:-
Actual Syntax — {() || Str_nt || ({() || u32 || Bool} Pi)} Pi
It is just a convenience feature for me, because the macros are just a derivative feature of the language being multi-staged via modalities.
Piumarta shows that with his model any function/closure/method can in theory and very easily be written in any language, so at first I was thinking of having tagged asm blocks:
Number + other = {.asm.
mov rbx, [self]
mov rcx, [other]
add rbx, rcx
mov [self], rbx
}
assert: { 5+6 } equals: 11
But then I would have to write an assembler, in the bootstrapping language (currently C which I've grown to dislike). So I thought about Forth: Number + other = {.forth.
( self other -- self )
@ over @ + !
}
Which seems so elegant, portable, the bootstrapping language can be Forth (the Forth interpreter itself written in asm), and I don't have to write an x86_64 assembler in C. Win win win.And during my research I came across this, running Pascal in Forth. Time is a circle and computer science keeps reinventing itself :-)
I like the opinion of the author that a great platform is one that lets you choose the perfect language to the problem at hand. My (ideal) language is fully late bound, based on message passing, which is extremely flexible, but not ideal for number crunching. Is this much of a problem, if a user can drop down to FORTRAN in the hot loops? I feel that is a much better idea than creating a multi-paradigm language that does everything but the kitchen sink (cough C++ and Rust), and is either mediocre like the former, or very complex like the latter.
IMO, we need simpler languages, based on the next level of abstraction — communication and not computation.
1: https://www.piumarta.com/software/id-objmodel/
See also COLA: https://www.piumarta.com/software/cola/ based on this model, but still too advanced for my brain to digest.
---
If anyone reading is familiar with Piumarta's COLA/object model and/or very late bound minimal systems, please drop me a line, I would be happy to pick your brain.
I've been thinking of solving it with Forth, as well, coincidentally.
I basically want to build a bootstrappable Forth as a meta-transpiler over a Forth VM that initially starts out in the earliest stage as the most minimum possible representation and add from there.
In my research things that led to this being possible but I haven't sussed out:
1. Mov based architecture
2. Transport Triggered Architecture
3. Some two push-down automata based on some operation that assumes stack operations for left and right to manage params/addresses.
https://github.com/dan4thewin/FreeForth2/blob/master/ff.asm
Sectorforth (Minimal - 7 operators, everything else is in the prelude including IF)
https://github.com/cesarblum/sectorforth/blob/master/sectorf...
And Retroforth (Multi platform VM, quotations)
https://github.com/crcx/retroforth/tree/master/vm
For larger coding, you may need a GC, Retro has a list class, and Freeforth operates a compacting dictionary which you may be able to modify
That is a very interesting question, which I'm trying to explore from another angle: what is the irreducible form of computing? As in, how minimal can a modern (operating) system be, to be able to boot and develop it completely at runtime?
Modern development is like preparing a blueprint of a sculpture, magically applying it on real marble, and when something is not ideal, back to the blueprint. I don't know what the future operating system and post-UNIX paradigm looks like, but I want to explore that design space with a chisel and hammer on live silicon rock. A bare metal REPL to a fully reprogrammable environment/language.
I don't have an answer to my or your query, but reducing the hypercomplex world of computing down to its axioms is a very fascinating topic.
One of the reasons why this style has not taken off (image-based) is that its very easy to lose track of changes while developing - there's no formal iteration boundary. This guy
https://github.com/attila-lendvai/maru
Has been experimenting with git branches for this.
https://github.com/crcx/retroforth/blob/master/doc/RETRO-Boo... talks about this issue, and the Retroforth author's attempt to define more clean boundaries.
So there is this unknown field of research that can only be explored with the most malleable of environments, where you can switch between Self-like prototyping and Smalltalk-like rigid class model at runtime. Or turn it into an imperative language, why not. That is the beauty of COLA as a research ground to me.
That said, thanks for the reading list and food for thought :)
A C assembler with the bulk of the implementation mechanically derived from XML is probably a reasonable project to attempt. I haven't done it yet but it's planned.
I'd also agree about your ideal. Maru for a long time was my goto reference as it's small, has a compiler to x86, is self-hosting, and has fexprs as a fundamental design choice.
The main issue with Maru, aside from being coded in C (bootstrapping from fundamentals), is that environments are implicitly dynamic, with no static control.
https://github.com/GiacomoCau/wat-js
has explicit dynamic variable handling, it uses the concept of wrap aka quotation plus env to determine what gets evaluated from the runtime env vs the compile time (static) env.
One thing I'm less keen on, is the use of macros. I feel Zig has the right approach with a compiletime annotation.
Wat also handles delimited continuations, which you can build an algebraic effect system on. Thus you can get multithreading, fibers, async, exception handling, IO, actors etc. very easily in little code. Wat also has modules.
Forth is like a super power for bootstrapping, but is unopinionated and for higher level coding, could use more of the techniques from Wat.
You might want to look at https://github.com/dan4thewin/FreeForth2/blob/master/ff.asm
It compiles anonymous functions (words) at the shell. It may be possible to use this technique with dynamic variable handling to get an eager bound system when required.
Then it's just a matter of Googling stuff, printing papers and reading them. I'm a high school dropout so probably there is a much more efficient way of doing academic research than mine.
The path was mostly Lisp -> Forth -> Smalltalk -> Self -> COLA.
Hacker News search is particularly good if you have the right keywords and you stumble upon the comments of the Elders that roam about and share their long lost knowledge (user DonHopkins for example having an encyclopaedic knowledge on this stuff)
A minimal forth wouldn't need much to let you define words corresponding to assembler instructions using Forth syntax and leave the assembled instructions on the stack. Then you "just" need a word to create a compiled machine code word from what has been pushed to the stack.
Compiler = parse + codegen — Later, the OS executes.
Interpreter = parse + execute
On metaprogrammable languages like Forth, what is the point of compiling to disk, if the final goal is to execute it anyway? ;)
I didn't read TFA thoroughly, but traditionally a compiler that translates not to machine code but into another language (source level) is called a "transpiler".
> On metaprogrammable languages like Forth, what is the point of compiling to disk, if the final goal is to execute it anyway? ;)
Lua and probably other languages do have a "pre-compiler", something that parses and compiles to bytecode. This reminds of image-based systems, except it is only one half of them (the other half being the value of variables, stack state, etc.).
It must be much more rare for Forth dialects, as the parsing step is really simple - almost trivial. It has nothing to do with metaprogramming, though - actually, it adds a tiny bot of complexity (must check the "immediate" flag when compiling).