Building a reduced Pascal and a full Forth demonstrates why the latter was popular on tiny machines: the former will be roughly half compiler and half machinery to deal with Pascal having a grammar. (less if you're into definitely-non-Wirthian optimising compilers; more if you're into attempting to provide useful-to-newbies error messaging)
I enjoyed reading the transcription, it is always nice to know I’m not just being a crazy lone implementer of unconventional programming language constructs.
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:
I am trying to design a language based on Ian Piumarta's metaprogrammable object model [1] (i.e. a Smalltalk-like where two methods are defined "a priori" in the entire system, everything else is late bound and can be changed at runtime — believe it or not, more flexible than any Lisp) and I was trying to figure out how to implement the basic stuff like number addition in the language, when nothing has been defined yet.
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:
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.
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.
This seems relevant to my open ended curiosity around the question 'what is the irreducible form of compute?'
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.
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.
This is what the the COLA papers talk about as you're aware, a REPL where you can modify, recompile and use the environment dynamically. Smalltalk is also similar in this regard, with a nicer UX.
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
The hypothesis I want to test is that image/live development has a much higher ceiling of productivity than what we're used to, we just haven't tried hard enough. The failure of Smalltalk are known and easily found, my issue with it is that we've abandoned research on this "fancy and new" aspect from the 80s because of its failures and returned to the mainframe style of the 60s, which is still the model today. It is incredible how many advanced ideas are starting to pop in our languages today, that were active areas of research in the 50+ years ago. Some papers from that era still read like science fiction today.
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 :)
Assemblers are mostly very large tables of data about instructions. If you're lucky, the architecture will have XML format descriptions of the ISA available that you can derive an assembler from. I think x86-64 even has versions with experimentally determined instruction latency in the same data.
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 think that if you have an escape hatch for something like numbers, it will get abused to the point where the language is incomplete without it, and you have two problems ;)
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.
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.
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.
Spending 12 hours a day in front of a PC, being on a sabbatical for a year, being obsessed with this problem for the entire duration.
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.
> This actually parses a subset of Pascal and translates it into another representation. Perhaps you can call it an interpreter.
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).
> The philosophy behind this idea is in the widely held view that, while most languages are good for some things few, if any, are good at everything and instead of trying to design new all embracing wonder-languages, it might be more productive to develop multilingual programming environments!
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.
For the curious where this came from, it’s from a FIG (Forth Interest Group) publication. FIG publications typically consisted of a colored card stock cover stapled to a stack of photocopied pages.
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.
Many years ago, I worked with the Pascal Compiler team at Sperry Network Systems, they Forth-based compiler for the Sperry DCP Minicomputers, used in Air Traffic Control (I believe). Jan E Jonak wrote the compiler and Wim Morrison was the architecture expert. This help our sister team led by Les Arnott who implemented an OS written in Pascal, to allow us to run and debug a Monolithic program which used to be uploaded directly via a Univac Mainframe. I helped write the Assembler and Pascal Debugger back in the 80s. We also had a student write an assembler code optimiser in Prolog.
22 comments
[ 2.9 ms ] story [ 62.8 ms ] threadThis 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:
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: 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).
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 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.
refs: - https://dl.acm.org/doi/10.1145/988316.988320 - https://dl.acm.org/doi/pdf/10.1145/15022.15025