Thought - Why Abstract Syntax Tree (AST) makes sense for AI Code Migration
Large Language Models are probabilistic text generators. They are not compilers. They do not possess an underlying, deterministic model of the code they are ingesting.
Right now, in boardrooms and daily stand-ups across the world, executives are signing off on millions for “AI Modernization,” while engineers are blindly feeding legacy scripts into ChatGPT to refactor core business logic.
The pitch is intoxicatingly simple, take a 10,000-line C++ monolith, paste it into an LLM window, and ask it to output pristine, idiomatic Python, Go or Rust microservices.
This is not engineering. This is reckless gambling.
The fundamental flaw in this strategy, whether you are a VP buying an enterprise AI tool or a Senior Engineer writing a migration script, is a misunderstanding of what an LLM actually is.
Large Language Models are probabilistic text generators. They are not compilers. They do not possess an underlying, deterministic model of the code they are ingesting. They do not mathematically understand state mutation, variable shadowing, or lexical scope.
They simply predict the next most likely token based on latent space vectors.
An LLM might translate a complex legacy while loop correctly 99 times. But on the 100th time, distracted by a weirdly named variable or an obscure GOTO statement, it will silently hallucinate. It will drop a crucial state mutation. It will misinterpret the lexical scope of a nested variable.
When you are migrating a critical system such as, a core banking ledger or a flight control system, a 99% success rate is another term for a catastrophic production outage.
Let’s look at an example of how a pure LLM migration breaks.
Smart LLMs rarely make basic syntax errors anymore, instead, they make semantic errors by translating text perfectly while fundamentally changing how the computer manages memory which we will see in the example below
Imagine a pricing calculation in legacy C++ using a struct. By default, C++ passes structs by value (meaning it creates a safe copy).
struct Trade {
double price;
};
// Passed by VALUE. Modifies a local copy for a "what-if" simulation.
void simulateDiscount(Trade t) {
t.price *= 0.9;
logSimulation("Discounted price would be: ", t.price);
}
// ... elsewhere in the system ...
Trade myTrade = { 100.0 };
simulateDiscount(myTrade);
// myTrade.price is STILL 100.0 here. The original is safely untouched.
processPayment(myTrade); // Processes the actual, full price.An engineer pastes this into an LLM and asks for an “idiomatic Python” translation. The LLM confidently spits out beautifully formatted Python
class Trade:
def __init__(self, price: float):
self.price = price
# Passed by REFERENCE. Modifies the original object!
def simulate_discount(t: Trade):
t.price *= 0.9
log_simulation("Discounted price would be: ", t.price)
# ... elsewhere in the system ...
my_trade = Trade(100.0)
simulate_discount(my_trade)
# my_trade.price is now 90.0!
process_payment(my_trade)
# You just undercharged the client by 10% because of a simulation!The LLM got a 100% on the syntax. The code is highly readable.
But it completely corrupted your ledger.
The LLM didn’t know why this was wrong because it doesn’t build a memory execution graph. It mapped a C++ function signature to a Python function signature. It completely failed to realize it just changed a safe, immutable pass-by-value operation into a highly destructive pass-by-reference mutation.
Why AST matters
To translate code safely, we have to return to Computer Science 101.
Code is not text. Treating code as a string of characters is the original issue of pure-LLM migration.
Code is a tree. Specifically, it is an Abstract Syntax Tree (AST).
When a traditional compiler reads your code, the very first thing it does is strip away the formatting, the whitespace, and the text, converting the logic into a strict, hierarchical graph of nodes and edges.
If we parse our legacy C++ simulateDiscount(Trade t) function into an AST, the parser generates a structural map that looks something like this
{
"node_type": "FunctionDeclaration",
"identifier": "simulateDiscount",
"parameters": [
{
"node_type": "Parameter",
"identifier": "t",
"data_type": "Trade",
"memory_model": "PASS_BY_VALUE" // <-- The Lifesaver
}
],
"body": [ ... ]
}Notice the memory_model metadata.
The AST isn’t guessing based on text patterns, it mathematically knows that this specific C++ language construct creates a local copy.
When an AST-driven migration maps this to Python, it compares the source memory model (PASS_BY_VALUE) to the target language’s default memory model (in Python, objects are passed by reference).
Because it detects this mismatch, the structural translation engine intervenes before the LLM can make a mistake.
It physically forces a constraint into the generated Python code to preserve the semantic contract of the original architecture.
The resulting code structurally generated by the tool will look like this
import copy
def simulate_discount(t: Trade):
# AST-enforced safety boundary to preserve C++ pass-by-value
t_local = copy.copy(t)
t_local.price *= 0.9
log_simulation("Discounted price would be: ", t_local.price)A pure LLM drops the constraint because it just sees words.
An AST preserves the constraint because it enforces mathematical logic.
Probabilistic models guess. ASTs prove.
The Hybrid approach
If pure LLMs are dangerous, and pure AST translation (source-to-source compilers/transpilers) produces ugly, unmaintainable “machine code,” what is the solution?
If you are building an enterprise-grade migration tool, like the local AI agent architectures we build at BinaryBox, you must use a hybrid pipeline. You do not ask the AI to write code from scratch. You force it to operate within a deterministic pipeline
Parse
Use a traditional, deterministic parser (like Tree-sitter) to generate the AST of the legacy C++, or Java codebase.
This locks the exact structure, memory model, and control flow into a mathematical graph.
Analyze
Pass specific, isolated subtrees of the AST to the LLM.
Instead of asking the AI to “rewrite this file,” you ask it to identify intent.
“Analyze this AST node block. Is this an implementation of a bubble sort? Is this a deprecated synchronous network call?”
Generate
Use the AST to deterministically generate the new syntax, utilizing the LLM only to provide modern idiomatic mapping.
As shown above, if the AST dictates a pass-by-value parameter, the LLM is constrained to generating code that enforces a clone or copy in the target language.
The AST guarantees the structure. The AI translates the semantics.
Why you should care?
Why does this architectural distinction matter to a CTO?
Because of QA costs.
If you use a pure LLM to translate a 500,000-line monolith, you cannot trust the output.
Because the system is probabilistic, human Senior Engineers must manually read, verify, and test every single line of the generated code to ensure no silent bugs were introduced.
The time and salary cost of having a Principal Engineer QA 500,000 lines of AI-generated spaghetti is often higher than the cost of having them rewrite it by hand.
The ROI of the migration drops to zero.
An AST+AI hybrid approach mathematically guarantees structural equivalence.
If the AST parser proves that all control flows and state mutations have been preserved in the new language, your engineers do not need to read every line.
They only need to review the architectural patterns and idiomatic choices.
You eliminate the operational risk of silent logic drops, and you cut the migration timeline from years to months.
Conclusion
There is a recurring theme in resilient system design, Architecture requires constraints.
We saw this in the Editor Wars. Atom gave developers total freedom to touch the DOM, resulting in chaotic performance. VS Code built a strict Extension Host, a structural prison that constrained plugins and guaranteed a flawless user experience.
AI agents are no different. If you give an LLM the freedom to write whatever text it wants, it will eventually write a bug that bankrupts your company.
To do a reliable AI code migration, you must build a structural prison.
The Abstract Syntax Tree (AST) is that prison. You lock the LLM inside the boundaries of the AST, forcing it to translate node-by-node, bounded by the laws of lexical scope, memory models, and control flow.
Freedom is chaos. Constraints are reliability.
Stop trying to use a magic wand, and start building a compiler.


