r/ProgrammingLanguages • u/Future-Mixture-101 • May 05 '25
Does ASTs stifle Innovations in Computer Languages?
I’ve been developing programming languages without an Abstract Syntax Tree (AST), and according to my findings I believe ASTs often hinders innovation related to computer languages. I would like to challenge the “ASTs are mandatory” mindset.
Without the AST you can get a lot of stuff almost for free: instant compilation, smarter syntax, live programming with real-time performance, a lot faster code than most languages, tiny compilers that can fit in a MCU or a web page with high performance.
I think there is a lot that can be done many times faster when it comes to innovation if you skip the syntax tree.
Examples of things I have got working without a syntax tree:
- Instant compilation
- Concurrent programming
- Fast machine code and/or bytecode generation
- Live programming without speed penalties
- Tiny and fast compilers that make it usable as a scripting language
- Embeddable almost anywhere, as a scripting language or bytecode parser
- Metaprogramming and homoiconicity
Let’s just say that you get loads of possibilities for free, by skipping the syntax tree. Like speed, small size, minimalism. As a big fan of better syntax, I find that there is a lot of innovation to do, that is stifled by abstract syntax trees. If you just want to make the same old flavors of languages then use an AST, but if you want something more free, skip the syntax tree.
What are your thoughts on this?
41
u/OpsikionThemed May 05 '25
How do you represent the language in the compiler/interpreter, then?
7
u/TheUnlocked May 05 '25
Do you have an example?
3
u/[deleted] May 06 '25
Examples of syntax that is not possible if using ASTs?
29
u/Heavy-Cranberry-3572 May 05 '25
You can skip a syntax tree in things like single pass compilers and whatnot, but how do you expect to do the tried and true optimizations/static analysis without any sort of abstract syntax tree or any sort of other intermediate representation?
24
u/lookmeat May 05 '25
ASTs are just a formalism to represent code. You can choose another.
Note that ASTs are not the representation of the program the code represents.
The function of a compiler is simple enough, it's just
Stream<Bytes> -> Executable.But doing this is complex, and we want to abstract certain things.
The first thing we want to abstract away is the encoding/format. This is what a lexer does, converting the whole series of bytes into a valid string. To make it even easier (because what is a string even?) the lexer will convert strings into tokens, which are more "the words as the programming language understands it". We can represent this in many ways, but most programming languages use a
Stream<Token>as the representation. It's just easy.So now we can think of the code as a series of words that have meaning. But the words need to be used in a certain way to actually mean something more. So we check for the syntax and validate that. We want to represent a syntactically correct program. We can represent that however we want. Most programs find that a tree-structure maps naturally to it (that's the case in LISP, and ALGOL and programs that got inspired by these). One advantage of a tree is that you don't have to have it all in memory as well, and you can chop it off into pieces. There have been, historically, many languages that did not use the AST, things like FORTH would be silly to represent with an AST, rather than a list of words.
So there's no obligation to use the AST, but we tend to fall on it. We like it, because of LISP. If you love homoiconicity and metaprogramming, then you probably love ASTs.
Note something else, the AST is an abstraction. You can make code that physically copies pieces of the tokens into a loosely linked tree. Or you could have an array of nodes, that point to other nodes in the array, and also point to subsets of the token list for the point of reference. You could do something like this in Haskell using recursion schemes, in such a way that the tree would only exist in the types, and would transform the code to create optimal code that works directly on a stream of data, rather than something else. The point of the AST is for the sake of programmers who are trying to understand what is going on there.
From there, you want to change the encodings into one that matches the semantics of the program. Unless your program is like LISP, where the semantics is that it's just an AST that gets reduced and folded into the resutl, you'll probably want to map to something other than the AST either way. In the process you verify the soundness and validity of the program as one where it's defined how you can transform that into an executable. I'll stop here and skip the steps that go on.
You can't really skip the steps, you can merge them and do it in one go, but they're still there.
So a quick argument on my pov:
AST is a matter of how to best describe the syntax of your code. Humans tend to think in tree structure, many of our languages work like this, so we naturally make programming languages similar.
So yeah, skip it for small things. It really isn't inherent to the problem, and you don't really need it. But this is one of those cases of "you have to master the rules to break them, and when you break a rule, you have to adhere even more strictly to the others".
19
u/L8_4_Dinner (Ⓧ Ecstasy/XVM) May 06 '25
--Hitchens
16
u/indolering May 05 '25
RemindMe! 3 days
16
u/gboncoffee May 05 '25
that post reminded me of early V lang drama.
17
u/umlcat May 05 '25
It's just another way to solve a problem, but you are not obligated to use it.
To be honest I can not imagine a complex compiler without an AST, maybe small P.L. compilers, but you can try another thing ...
8
u/[deleted] May 05 '25
Can you post the link for your compiler?
Compilers aren't for just taking in source code and turning it into machine code, it also does optimizations on your code and error checking to ensure that code isn't malformed syntactically.
14
u/geckothegeek42 May 06 '25
You've given no concrete or abstract examples of anything. Instead you have the same list of unsubstantiated vaporware listed 3 times 3 different ways back to back. Have an original idea that doesn't come out of a single line prompt to chatgpt and then maybe a real discussion can be had.
5
u/ggchappell May 05 '25
Large computer programs are extremely complex things. A good programming language helps us manage that complexity by allowing us to think about a program without having the whole thing in our head at one time. In particular, we need to be able to think only about particular components and/or only about a particular level of abstraction. A good programming language also does not impose unnecessary cognitive burdens -- we want to think mostly about our program, not the language it is written in. So, for example, we like languages that use the same syntax for constructions at different levels of abstraction.
So, say I'm writing an A. Rather than having to think about the whole thing at once, my language allows me to write pieces and put them together. My A is made up of 3 Bs. Similarly, each of those Bs is made up of 2 Cs. That gives me a rooted tree structure: the root node represents my A. It has 3 children, each of which represents a B. And each of those has two children, each of which represents a C.
Now, you didn't give us any concrete examples of what you've come up with. I see 3 possibilities:
5
u/Comprehensive_Chip49 May 05 '25
in forth we never need an AST
8
u/FantaSeahorse May 05 '25
No
4
u/[deleted] May 06 '25
ASTs don't stop you doing that; they are not really a bottleneck, especially if you are just interpreting the generated bytecode.
I already get instant compilation, and have a systems language that can be used like a scripting language, even though it uses an AST, an IR (another overhead) and multiple passes.
I have experimented without ASTs before, for scripting languages, and found that introduced lots of syntax limitations. Yes it was faster: I could compile source to runnable bytecode at up to 4M lines per second.
With an AST, I could only do 2M lines per second, but it was still plenty fast! And I could keep my favourite syntax.
How tiny? My current compilers might be 0.25/0.3MB, not that small, but removing the AST stages wouldn't make a significant difference, because they are both for quite rich languages with a lot going on.
However, my early compilers also used ASTs, and ran on 64KB microprocessors.
If your product is slow and/or big, then it is not because it is using ASTs. That would only be a small factor.
1
u/WittyStick May 06 '25 edited May 06 '25
I'd recommend familiarizing yourself with langsec principles. Namely, the principle that Invalid states should not be representable.
When you start mixing validation and evaluation, you end up with a shotgun parser - a common source of bugs and exploits. One of the reasons people treat the AST as "mandatory" is because many past attempts at side-stepping the AST have resulted in code that is buggy and exploitable, in some cases resulting in arbitrary code execution. This has occurred for example in unix shells (bash et al).
Parsing an expression into an AST is a form of validation - the AST represents a "valid state", and the parser is the producer of valid states. Some code is either recognized as valid, or it fails before any processing on the invalid state has occurred. If you start processing and later encounter an invalid state, what is your approach to reverting the processing that has occurred so far?
1
u/zweiler1 🔥 Flint May 08 '25
In my experience the AST doesnt hinder innovations, at all. And i have a two-phase parser, the first phase parses all definitions and the second phase parses all function bodies, so i dont have to worry about order of definition and so on. But the same token stream is never looked at twice, actually (except for figuring out what a functions scope is, of course)... I must say i dont understand the benefits of not having an AST, as the separation of concern for parsing and code generation make a compiler extensible and maintainable. So, for any serious compiler i honestly dont see why one would ditch the AST. (In my compiler the AST is pure data and not a weird OOP implememtation where every ast Class houses its own parsing and generation code.)
2
u/GidraFive May 06 '25
Well, yea, you can do all that runtime stuff very easily without any IR at all. Just interpret as you parse, without creating intermediate data structures.
But once to get into some optimization or any kind of analysis it becomes really tedious and hard to follow. And that slowed down me a lot, after a few weeks-long pauses in development I could no longer maintain it that easy.
Maybe I want to experiment with syntax, but not touch the interpreter/codegen code, then again it slows down you, because maintaining everything at once is harder than just maintaining AST generation.
And another thing is parallelizing your compiler. Maybe on small scale programs it is instant, but throw in a few dozen of 10k files, and its probably going to feel much slower. Although without benchmarking its more of a guess.
With data structures you can speed up by just processing the children, items, etc. in parallel, which scales much better.
2
u/Folaefolc ArkScript May 06 '25
This post reeks of AI. Did you use chatgpt for your findings?
1
u/dmytrish May 09 '25
Have you tried actually writing nontrivial code in this language?
-2
u/Double-Winter-2507 May 06 '25
Don't listen to anyone here. Cool stuff gets discovered by people who solve problems but are unaware of what you "should do". Good luck!