Dumping my initial thoughts about adding a new language to the captcha game (will probably edit this post once I start implementation and they meet reality):
1. Imperative style or functional style?
It's a high-level language that supports lambda expressions and other goodies, but it's much closer to C than to Scheme.
Imperative.
1.1. Unlimited jumps or "structured" jumps?
The language doesn't have "goto" statements at all.
This implies that we'll transpile Go.
2. Source language
The generated Go code may have:
- "if", "else", and "else if"
- "for" (conditional and unconditional)
- "break" and "continue" (including multi-level jumps to labeled loops)
- "return"
- "goto" (forward, structured jumps only)
3. Eliminating "goto"
As said above, the language doesn't support "goto" statements, so they'll be replaced with blocks (like in Nim) and "break" statements.
You can think of a block as a loop with exactly one iteration, whose only reason for existence is to allow to exit it prematurely (i.e. jump to the block's end).
It's essentially like "for { ...body...; break; }" in Go.
4. Eliminating "return"
The semantics of "return" in the language is quite unusual and of little use to us. We'll treat "return" as a forward-jump to the end of the code, and accordingly replace it with a "break" statement inside a block spanning the entire function's body.
5. Eliminating "continue"
The language doesn't support "continue" either, so similarly, such statements will get replaced with blocks and "break" statements.
This time, each block will be nested inside the loop so that "break" jumps to its end, starting the next iteration.
6. How to write conditions and loops?
This is the second stylistic choice.
The language supports conditions and loops in its standard library, but exclusively using it will not demonstrate the language's unique features.
6.1. Loops
We'll implement loops by using recursion. The language doesn't support tail-code optimization, but that's the only way to iterate, and how the standard library implements loops under the hood.
6.2. Conditions
I think that we'll use the standard library for conditions, at least for start.
That will make for simpler hints to players in the game.
And will greatly simplify the implementation, making it very similar to what I did for Rust/Zig/Nim and many other languages.
6.2.1. Eliminating "else if"
The standard library doesn't support "else if", so we'll replace it with "else" and a nested "if" statement.
7. Conditional-->unconditional loops
We'll replace "for <cond> { ...body... }" with "for { if !<cond> { break; } ...body... }", so that all loops become unconditional (syntactically).
This will allow us to treat all loops in a uniform way, regardless of whether they test the condition in the beginning of the body (while loop), the middle, or the end (do-while loop).
8. Resulting syntax
- "block { ... }", which can be exited using "break"
- "if" and "else"
- "for" (unconditional only)
- "break" (including multi-level breaks)
9. Using recursion for loops
A loop with an "if <cond> { break; }" statement at the top level of its body (i.e. not nested inside anything, including blocks), where that statement is the only "break" statement the exits the loop, will perform the recursive call only if the condition is false.
For example, "for { ...body1...; if <cond> { break; } ...body2... }"
will get translated to "func f() { ...body1...; if !<cond> { ...body2...; f(); } }"
All other loops will perform the recursive call after their body, and be wrapped inside a block:
"block { func f() { ...body...; f(); } }"
This way, from now on, "break" statements only break blocks, not loops.
In fact, we don't have loops anymore, just functions and recursive calls.
(to be continued? little here is specific to the target language...)