add support for continue statement#112
Conversation
|
Thanks for the PR, I don't think continue is good style and should be used at all. But its probably a good thing to have support for it anyway. Also I think it would be much cleaner to use Lua 5.2s goto statement. The output looks a bit nicer that way. And please add at least some translation tests and if you want some functional tests. |
|
I have been searching for this kind of project for a while, since I have some old games that I would love to mod, but they support only Lua 5.1; that was the reason for me going with this solution. I could implement the And you're right, I should probably add more tests with this feature. Edit: Also I disagree with the notion that continue should not be used, I have encountered a few cases where the implementation of continue helps a lot. But I guess, that boils down to opinion. |
|
We dropped support for Lua versions below 5.3 a while ago because we wanted to implement all Lua features first, without having to think of backwards-compatibility all the time. But we already have a system to transpile differently for different version, we use that for bit operations for example, so if you want to add 5.1 support please go ahead. (Unless @Perryvw has an issue with that.) While your at it you can also look at some other features that won't work in 5.1 (e.g bit ops, can't think of more right now) and throw exceptions if those are used when the 5.1 flag is set. Another thing make sure you don't add additional overhead if no continue is used in a loop and use goto in versions <5.1 |
|
Alright then, I think that it would be wise for me to go with the goto implementation for this PR, and implement the 5.1 compatibility in a different batch with a different PR. On a side note, is there a list of the Lua features that are already implemented and features that are still being worked on? If I remember correctly, things such as |
|
Sounds good 👍 We have a list of things that don't work, but I don't think everything is listed there: https://github.com/Perryvw/TypescriptToLua/wiki/Limitations |
|
And tell me if I got this right: the plan is to get rid of all the limitations, correct? |
|
I would say the nicest way to do this is maintain one 'main' transpiler class and have a different extension class for other versions that override required methods. We can then simply select between them from compiler.ts |
|
Alright, got it. Thank you. |
Perryvw
left a comment
There was a problem hiding this comment.
A couple things, I am seeing a lot of code reuse: it would probably be a good idea to make a transpileLoopBody method.
Furthermore you should move tests that just check transpilation string result to translation tests. Furthermore I would appreciate some unit tests where you actually execute some transpiled code with continues.
src/Transpiler.ts
Outdated
|
|
||
| let result = this.indent + `while ${condition} do\n`; | ||
| this.pushIndent(); | ||
| result += this.indent + "local ____continue = false\n"; |
There was a problem hiding this comment.
4 _ seems a little overkill, we generally use 2 for transpiler stuff
test/unit/loops.spec.ts
Outdated
| ____continue = true | ||
| until true | ||
| if not ____continue then break end | ||
| end`; |
There was a problem hiding this comment.
This should be a translation test. (Just add the typescript to translation/ts and the lua to translation/lua with the same name and it will automatically include the test).
test/unit/while.spec.ts
Outdated
| repeat | ||
| a=a+1 | ||
| ____continue = true | ||
| until true if not ____continue then break end |
|
Ok, I've done everything that was requested:
|
|
Thanks for the update! I like the functional tests you added, this seems like a good test coverage. I am not such a fan of the way this is implemented however. After discussing with @lolleko we came to a couple points:
|
|
My bad, for some reason I completely missed the |
|
Question, should I be using the genVarCounter variable for keeping track of the stack? Or should I create a new variable designed to be used with loops only? Your message suggested that I should use genVarCounter, but if I do, it will interfere with switch/case statements. |
|
I suggest creating a new stack to keep track of the numbers of nested loops for when you need to write a goto in one of them. You can simply populate the stack by using genVarCounter. If any time you start using a new genVarCounter value you simply increment it, it should not interfere with switch/case statements. |
|
The namespace array is also used as a stack by the way, you can look at how that is used. |
|
Requested changes are now implemented. |
src/Transpiler.ts
Outdated
| result += this.transpileStatement(node.statement); | ||
| this.popIndent(); | ||
| result += this.indent + "end\n"; | ||
| result += this.indent + `::__continue${this.loopStack[this.loopStack.length - 1]}::\n`; |
There was a problem hiding this comment.
You can just use this.loopStack.pop() instead of this.loopStack[this.loopStack.length - 1]. and remove the pop on the next line. pop will return the last value.
| const condition = this.transpileExpression(node.expression); | ||
|
|
||
| let result = this.indent + `while ${condition} do\n`; | ||
| this.pushIndent(); |
There was a problem hiding this comment.
you can push the indent inside the transpileLoopBody function, this will reduce code reuse even more.
There was a problem hiding this comment.
I disagree with this. If you look at the regular for loop it translates the incrementor after the loop body but inside the indent. If you move the indent inside the loop body method this no longer works. You could ofcourse move only the push inside but I think that is bad form, push and pop should be obvious in the same context.
Perryvw
left a comment
There was a problem hiding this comment.
Thanks for the changes, they look very good! I have only one issue and that is that none of your translation tests actually contain a continue. Also having a test with a nested loop would be nice. Once you address that and the other minor comments in the code this looks ready to merge to me!
| | ts.ForInStatement | ||
| ): string { | ||
| this.genVarCounter++; | ||
| this.loopStack.push(this.genVarCounter); |
There was a problem hiding this comment.
Increment genVarCounter after you use it instead of before (same as switch does)
This implements the support for TS's
continuestatement. Normally, tstl would throw an exception whenever thecontinuestatement is used, saying that Lua does not support the statement.However, while Lua indeed does not support the
continuestatement, it is still perfectly possible to implement the functionality of a continue statement by changing how loops get transpiled.Here's a simple example:
The following TS
Gets transpiled to
As can be seen from the example above, the functionality of a continue statement gets achieved via a clever usage of the
repeat until trueblock, which acts a simpledo endblock, but allows for a possibility of stopping the execution of the block via abreakstatement. The____continuelocal allows for differentiation between abreakstatement for therepeat until trueand the parent loop. If abreakstatement was used without setting the____continuelocal to true, then it will have an effect of a normal break (break fromrepeat until trueand break from the parent loop). However, if abreakstatement was used after setting the____continuelocal to true, then it will act with accordance to how the effect of thecontinuestatement should be, which is: stop executing the current cycle, and jump to the next one (breakrepeat until true, but do not break the parent loop). The____continue = truegets automatically added at the end of the body of the parent loop.So, the
continuestatement simply gets transpiled to:With this implementation, you can nest multiple loops inside of each other, and the
____continuelocals of the nested loops will not interfere with each other, thanks to how scoping works in Lua.The following loops were used for testing:
The transpiled code:
The outcomes match.
I don't really know if there's a certain way that the pull requests should be made, or if there's some kind of standard for the workflow. If I've done something wrong with the presentation, then please don't hesitate to correct me. :)