Skip to content

feature/loopNestedContinue #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Yi2255
Copy link
Contributor

@Yi2255 Yi2255 commented Apr 30, 2025

Hi Saelo,

First of all, I sincerely apologize for the long delay regarding the implementation of LabelStatement.
Following your suggestion #479 , I have split this feature into two separate PRs, LoopNestedContinue and NestedBreak.
This PR implements LoopNestedContinue. As discussed in #479, I add a new JsOperation loopNestedContinueand and a new CodeGenerator LoopNestedContinueGenerator. Beside, the code design in JavaScriptLifter.swift is as follows:

  1. Use LabelStack to trace the nested code block.
  2. The element in LabelStack is LabelPin, which has two fields: beginPos and hasLabel. beginPos is the index of the starting point of the js code block, and hasLabel indicates whether beginPos has generated a label (with a small probability of generating N lines of break or continue in the same block).
  3. Because there is no need to label every code entry with a label, which will expand the code size, it is only inserted when continueNested appears. Therefore, the core operation of LabelStack is insertLabel, which has three operations: ① Insert label string into js code; ② mark that a label has been inserted here; ③Move the positions of all LabelPins behind the stack at the corresponding depth back by the corresponding size.

Please let me know if any changes are needed; Thanks a lot!

Copy link
Collaborator

@saelo saelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! Did a first round of review now

@@ -54,7 +54,7 @@ public let CodeGenerators: [CodeGenerator] = [
ValueGenerator("StringGenerator") { b, n in
for _ in 0..<n {
b.loadString(b.randomString())
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit unnecessary whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your suggestion, and my apologies for the bad code style.

@@ -2256,7 +2256,7 @@ final class BeginFinally: JsOperation {

final class EndTryCatchFinally: JsOperation {
override var opcode: Opcode { .endTryCatchFinally(self) }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit same here

@@ -2464,6 +2464,17 @@ class BindMethod: JsOperation {
}
}

final class LoopNestedContinue: JsOperation {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here would be a good place for a short comment describing how this operation works, in particular how depth is interpreted (e.g. that it wraps around if there are fewer open loops)

w.emitBlock("while (\(COND)) {")
w.enterNewBlock()


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More whitespace here and below

@@ -101,6 +101,8 @@ public class JavaScriptLifter: Lifter {

var w = JavaScriptWriter(analyzer: analyzer, version: version, stripComments: !options.contains(.includeComments), includeLineNumbers: options.contains(.includeLineNumbers))

var loopLabelStack = LabelStack(type: .loopblock)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assert at the end of the lifting that this stack is now empty? Just to catch any cases where we forget the pop()


enum LabelType {
case loopblock
case ifblock
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess for this PR, only loopBlock would be supported? The others should probably move to the corresponding PR

}
}

mutating func translateBreakLabel(w: inout ScriptWriter, expDepth: Int){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing space before { (also below)

@@ -84,4 +84,12 @@ struct ScriptWriter {
assert(currentIndention.count >= indent.count)
currentIndention.removeLast(indent.count)
}

mutating func insert(_ pos: Int, _ content: String){
if code.index(code.startIndex, offsetBy: pos, limitedBy: code.endIndex) != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this ever not be the case? Wouldn't that be a bug?

@@ -3095,4 +3095,35 @@ class LifterTests: XCTestCase {
"""
XCTAssertEqual(actual, expected)
}
}

func testLoopNestedContinueLifting(){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering, does the label insertion work correctly if we also print line numbers? Did this test:

func testLiftingWithLineNumbers() {
ever fail? Maybe you could add an extra test like this one that inserts labels but also prints line numbers?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this change breaks line numbering, maybe we could just add the label to the same line as the loop begin? So lift to something like label1: for (...) { instead?

}

/// A structure that manages label positions within a specific control flow block (e.g., loop, if, switch, etc.).
public struct LabelStack {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this struct should actually be part of the ScriptWriter or the JavaScriptWriter itself. It feels a bit awkward that you need to pass in the ScriptWriter or internal state of the script writer (the current position). I think this would all go away if the ScriptWriter/JavaScriptWriter itself maintained this stack. And I think it would also remove the need for the withScriptWriter method above. Would that work? That way, the ScriptWriter also wound't need to expose the insert method which probably shouldn't be used for any other purposes.

@Yi2255 Yi2255 force-pushed the feature-loopNestedContinue branch from 17edc6b to e0e81c9 Compare May 11, 2025 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants