Somehow functions are sub-levels where the character is moving around in an isolated "scope" but can still see global variables. Could look nice as an isometric platform game. Variables as little boxes with names, with values inside. How to represent references to variables? Maybe boxes sit in their scope and when passed as references they appear to go below the floor down to the previous scope.
The program counter is represented by a little robot. Where it goes is where the code is going to be executed.
Each level is a task. Like "print hello world" and the robot has to move and push (call) little function boxes with values for the letters.
You can move the robot manually and it leaves a trail of commands that you can rewind and edit. You also have meta commands like loops that control the robot automatically.
Or perhaps the robot doesn't really move but act more like a CPU as it sits in one spot and performs basic operations by putting things in boxes and retrieving things from boxes. Simpler conceptually but how to make it an interesting game?
Perhaps the robot is moving along in a side scroller fashion from left to right (the world moving from right to left). Each step is the PC incrementing to the next instruction. A single function is a new level (or a different path) and the robot jumps across to the new path and returns to the previous path when done. There is a state that is the current variables. Before a path jump the robot has to copy values from the current state into a new empty state (param binding) and the old state is left attached to the old path.
Conditional blocks are like new levels but they sit closer and a new state is not created. Also the robot never returns to the start of the conditional but rather goes back up to the join of the branches.
How to represent conditional tests? The conditional looks like a function call binding but it doesn't result in the robot jumping to a new level.
Return values must be assigned into a variable in the callers state.
But what is the game? Somehow there is some expected output...
The side scroller aspect is for some expected output, like a sequence of numbers or letters. Completing the level means successfully generating the expected sequence. Then the little CPU character works from a program to generate the sequence somehow.
So the advancement of the character is in terms of advancing along the expected output. Pretty boring though as it implies there is only one "way" to satisfy the output.
At the lowest level is a virtual machine. Ideally a real virtual machine. Possibly a Z80 as it's relatively simple. Or possibly a 6502. A "level" may be for example a robot controlled by this virtual machine on hardware ports. Initially the player interacts with the virtual machine in a higher level language (something like Lisp) calling functions for everything. For example a for loop would be a high level "function" that accepts another anonymous function (or block) as an argument, but under the hood the VM is setting up a decrement counter and jumping to the inner block on each loop pass. What is important is that the player can complete the level using the high level language, but they can also explore the lower levels of they are interested in depth. Perhaps at an even higher level is a simple logo language like rotate(), forward() etc, under that is some kind of Lisp, and under that is the machine.
There are two ways to go: a) an abstract puzzle game focussed on producing some pattern of output like "make this memory chunk look like this", and b) some real-world analogy like controlling a robot to perform some tasks or navigate a maze. I think these are different games even if the VM is the same underneath.
You want some interactivity so the player can learn the language iteratively. Like a step forward and back in the program. However with the puzzle game this doesn't make sense as you can just brute force the result. The puzzle game can be like a series of test cases and you have to write the best code against those test cases.
New approach: just a programming language, not a game. The area is a grid of squares. A "cons pair" is two squares across. Tapping an empty space to the right of a pair is a "cons" operation. Clicking underneath a populated space is a "car" where the value is a reference to another pair (a list). Clicking into the "car" sets it's value (symbol). If the value is a string the car is a pointer to a null terminated string.