reconcileLabels handles all GOTO operations that may point to a label, since labels may be nested/shadowed. It's easier to perform this is a final step, rather than trying to reconcile them during the operation conversion step.
(ops []InterpreterOperation)
| 30 | // reconcileLabels handles all GOTO operations that may point to a label, since labels may be nested/shadowed. It's |
| 31 | // easier to perform this is a final step, rather than trying to reconcile them during the operation conversion step. |
| 32 | func reconcileLabels(ops []InterpreterOperation) error { |
| 33 | labels := utils.NewStack[labelStackItem]() |
| 34 | gotos := make(map[int]*InterpreterOperation) |
| 35 | for opIndex, operation := range ops { |
| 36 | switch operation.OpCode { |
| 37 | case OpCode_Goto: |
| 38 | // When this is true, we have a label |
| 39 | if len(operation.PrimaryData) > 0 { |
| 40 | if operation.Index < 0 { |
| 41 | // This is a CONTINUE, so we already know the index that we need to go to |
| 42 | found := false |
| 43 | for i := 0; i < labels.Len(); i++ { |
| 44 | stackItem := labels.PeekDepth(i) |
| 45 | if stackItem.label == operation.PrimaryData { |
| 46 | if !stackItem.isLoop { |
| 47 | return errors.New("CONTINUE cannot be used outside a loop") |
| 48 | } |
| 49 | found = true |
| 50 | ops[opIndex].Index = stackItem.start |
| 51 | ops[opIndex].PrimaryData = "" |
| 52 | break |
| 53 | } |
| 54 | } |
| 55 | if !found { |
| 56 | return errors.Errorf(`there is no label "%s" attached to any block or loop enclosing this statement`, operation.PrimaryData) |
| 57 | } |
| 58 | } else { |
| 59 | // This is an EXIT, so we'll save it for later |
| 60 | gotos[opIndex] = &ops[opIndex] |
| 61 | } |
| 62 | } |
| 63 | case OpCode_ScopeBegin: |
| 64 | // We'll push the label and loop status to the stack |
| 65 | labels.Push(labelStackItem{ |
| 66 | label: operation.PrimaryData, |
| 67 | start: opIndex + 1, // We want to go to the operation after this one, else we'll continually increase the scope |
| 68 | isLoop: len(operation.Target) > 0, |
| 69 | }) |
| 70 | // We clear the label and loop status since we only set them for reconciliation |
| 71 | ops[opIndex].PrimaryData = "" |
| 72 | ops[opIndex].Target = "" |
| 73 | case OpCode_ScopeEnd: |
| 74 | stackItem := labels.Pop() |
| 75 | for gotoIdx, gotoOp := range gotos { |
| 76 | if gotoOp.PrimaryData == stackItem.label { |
| 77 | gotoOp.Index = opIndex // We want to go to this operation, as we want to exit the scope |
| 78 | gotoOp.PrimaryData = "" |
| 79 | delete(gotos, gotoIdx) |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | if len(gotos) > 0 { |
| 85 | for _, op := range gotos { |
| 86 | return errors.Errorf(`there is no label "%s" attached to any block or loop enclosing this statement`, op.PrimaryData) |
| 87 | } |
| 88 | } |
| 89 | return nil |