@@ -105,6 +105,8 @@ public struct VisuStructSimulationState: Equatable, Sendable {
105105 public var inputError : String ?
106106 public var lastTrace : String ?
107107 public var message : String ?
108+ /// Nach einem Fehler bleibt die Simulation auf dem fehlerhaften Schritt stehen, bis sie zurückgesetzt wird.
109+ public var isHalted : Bool
108110
109111 public init (
110112 stepIndex : Int = 0 ,
@@ -113,7 +115,8 @@ public struct VisuStructSimulationState: Equatable, Sendable {
113115 inputRequest : VisuStructSimulationInputRequest ? = nil ,
114116 inputError : String ? = nil ,
115117 lastTrace : String ? = nil ,
116- message : String ? = nil
118+ message : String ? = nil ,
119+ isHalted : Bool = false
117120 ) {
118121 self . stepIndex = stepIndex
119122 self . variables = variables
@@ -122,6 +125,7 @@ public struct VisuStructSimulationState: Equatable, Sendable {
122125 self . inputError = inputError
123126 self . lastTrace = lastTrace
124127 self . message = message
128+ self . isHalted = isHalted
125129 }
126130}
127131
@@ -135,7 +139,7 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
135139 }
136140
137141 public var canStep : Bool {
138- state . inputRequest == nil && state . stepIndex < steps . count
142+ ! state . isHalted && state . inputRequest == nil && state . stepIndex < steps . count
139143 }
140144
141145 public init ( document : VisuStructDocument ) {
@@ -152,7 +156,7 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
152156
153157 public mutating func step ( ) {
154158 guard canStep else {
155- if state . inputRequest == nil {
159+ if state . inputRequest == nil && ! state . isHalted {
156160 state . message = " Simulation finished. "
157161 }
158162 return
@@ -199,7 +203,7 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
199203 state . message = state . stepIndex >= steps . count ? " Simulation finished. " : nil
200204 } catch {
201205 state . message = error . localizedDescription
202- state . stepIndex += 1
206+ state . isHalted = true
203207 }
204208 }
205209
@@ -928,49 +932,93 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
928932 return try parser . parseDouble ( )
929933 }
930934
935+ /// Bedingungen wie in Java: `||` vor `&&` vor `!`, Klammern, Vergleiche von Zahlen und Strings, `a.equals(b)`.
931936 private func evaluateBoolExpression ( _ expression : String ) throws -> Bool {
932937 let trimmed = expression . trimmingCharacters ( in : . whitespacesAndNewlines )
933938 if trimmed == " true " { return true }
934939 if trimmed == " false " { return false }
935940
936- let orParts = splitBoolExpression ( trimmed , separator : " || " )
941+ let orParts = Self . splitTopLevel ( trimmed , separator : " || " )
937942 if orParts . count > 1 {
938943 for part in orParts where try evaluateBoolExpression ( part ) {
939944 return true
940945 }
941946 return false
942947 }
943948
944- let andParts = splitBoolExpression ( trimmed , separator : " && " )
949+ let andParts = Self . splitTopLevel ( trimmed , separator : " && " )
945950 if andParts . count > 1 {
946951 for part in andParts where try ! evaluateBoolExpression ( part ) {
947952 return false
948953 }
949954 return true
950955 }
951956
952- let operators = [ " >= " , " <= " , " == " , " != " , " > " , " < " ]
953- for op in operators {
954- if let range = trimmed . range ( of : op ) {
955- let lhs = String ( trimmed [ ..< range . lowerBound ] )
956- let rhs = String ( trimmed [ range . upperBound ... ] )
957- let leftValue = try evaluateDoubleExpression ( lhs )
958- let rightValue = try evaluateDoubleExpression ( rhs )
959- switch op {
960- case " >= " : return leftValue >= rightValue
961- case " <= " : return leftValue <= rightValue
962- case " == " : return leftValue == rightValue
963- case " != " : return leftValue != rightValue
964- case " > " : return leftValue > rightValue
965- case " < " : return leftValue < rightValue
966- default : break
967- }
968- }
957+ if trimmed . hasPrefix ( " ! " ) , ! trimmed . hasPrefix ( " != " ) {
958+ return try ! evaluateBoolExpression ( String ( trimmed . dropFirst ( ) ) )
959+ }
960+
961+ if let inner = Self . innerOfOuterParentheses ( trimmed ) {
962+ return try evaluateBoolExpression ( inner )
963+ }
964+
965+ if let call = Self . parseEqualsCall ( trimmed ) {
966+ return try evaluateComparison ( lhs : call . lhs , op : " == " , rhs : call . rhs )
967+ }
968+
969+ if let comparison = Self . splitComparison ( trimmed ) {
970+ return try evaluateComparison ( lhs : comparison . lhs , op : comparison . op , rhs : comparison . rhs )
969971 }
970972
973+ if try stringOperand ( trimmed ) != nil {
974+ throw SimulationError . invalidExpression ( trimmed )
975+ }
971976 return try evaluateDoubleExpression ( trimmed ) != 0
972977 }
973978
979+ private func evaluateComparison ( lhs : String , op : String , rhs : String ) throws -> Bool {
980+ let leftString = try stringOperand ( lhs )
981+ let rightString = try stringOperand ( rhs )
982+ if leftString != nil || rightString != nil {
983+ guard let left = leftString , let right = rightString else {
984+ throw SimulationError . invalidExpression ( " \( lhs ) \( op ) \( rhs ) " )
985+ }
986+ switch op {
987+ case " == " : return left == right
988+ case " != " : return left != right
989+ case " < " : return left < right
990+ case " <= " : return left <= right
991+ case " > " : return left > right
992+ case " >= " : return left >= right
993+ default : throw SimulationError . invalidExpression ( " \( lhs ) \( op ) \( rhs ) " )
994+ }
995+ }
996+
997+ let leftValue = try evaluateDoubleExpression ( lhs )
998+ let rightValue = try evaluateDoubleExpression ( rhs )
999+ switch op {
1000+ case " == " : return leftValue == rightValue
1001+ case " != " : return leftValue != rightValue
1002+ case " < " : return leftValue < rightValue
1003+ case " <= " : return leftValue <= rightValue
1004+ case " > " : return leftValue > rightValue
1005+ case " >= " : return leftValue >= rightValue
1006+ default : throw SimulationError . invalidExpression ( " \( lhs ) \( op ) \( rhs ) " )
1007+ }
1008+ }
1009+
1010+ /// String-Literal oder String-Variable (auch Element eines String-Arrays); sonst `nil`.
1011+ private func stringOperand ( _ text : String ) throws -> String ? {
1012+ let trimmed = text . trimmingCharacters ( in : . whitespacesAndNewlines )
1013+ if let literal = Self . unquotedString ( trimmed ) {
1014+ return literal
1015+ }
1016+ if let value = try evaluateReference ( trimmed ) , case . string ( let string ) = value {
1017+ return string
1018+ }
1019+ return nil
1020+ }
1021+
9741022 private enum SwitchSelectorValue : Equatable {
9751023 case int ( Int )
9761024 case text ( String )
@@ -1016,11 +1064,132 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
10161064 return nil
10171065 }
10181066
1019- private func splitBoolExpression ( _ expression : String , separator : String ) -> [ String ] {
1020- expression
1021- . components ( separatedBy : separator )
1022- . map { $0 . trimmingCharacters ( in : . whitespacesAndNewlines ) }
1023- . filter { ! $0 . isEmpty }
1067+ /// Für jedes Zeichen: liegt es außerhalb von Klammern und String-Literalen?
1068+ private static func topLevelMask ( _ chars : [ Character ] ) -> [ Bool ] {
1069+ var mask = [ Bool ] ( repeating : false , count : chars . count )
1070+ var depth = 0
1071+ var inString = false
1072+ var escaped = false
1073+ for ( index , char ) in chars . enumerated ( ) {
1074+ if inString {
1075+ if escaped {
1076+ escaped = false
1077+ } else if char == " \\ " {
1078+ escaped = true
1079+ } else if char == " \" " {
1080+ inString = false
1081+ }
1082+ continue
1083+ }
1084+ switch char {
1085+ case " \" " : inString = true
1086+ case " ( " , " [ " : depth += 1
1087+ case " ) " , " ] " : depth -= 1
1088+ default : mask [ index ] = depth == 0
1089+ }
1090+ }
1091+ return mask
1092+ }
1093+
1094+ private static func splitTopLevel ( _ expression : String , separator : String ) -> [ String ] {
1095+ let chars = Array ( expression )
1096+ let separatorChars = Array ( separator )
1097+ let mask = topLevelMask ( chars )
1098+ var parts : [ String ] = [ ]
1099+ var current : [ Character ] = [ ]
1100+ var index = 0
1101+ while index < chars . count {
1102+ let end = index + separatorChars . count
1103+ if end <= chars . count , Array ( chars [ index ..< end ] ) == separatorChars , mask [ index ..< end ] . allSatisfy ( { $0 } ) {
1104+ parts . append ( String ( current ) . trimmingCharacters ( in : . whitespacesAndNewlines ) )
1105+ current = [ ]
1106+ index = end
1107+ continue
1108+ }
1109+ current . append ( chars [ index ] )
1110+ index += 1
1111+ }
1112+ parts . append ( String ( current ) . trimmingCharacters ( in : . whitespacesAndNewlines ) )
1113+ return parts
1114+ }
1115+
1116+ /// Erster Vergleichsoperator außerhalb von Klammern und Strings.
1117+ private static func splitComparison ( _ expression : String ) -> ( lhs : String , op : String , rhs : String ) ? {
1118+ let chars = Array ( expression )
1119+ let mask = topLevelMask ( chars )
1120+ for index in chars . indices where mask [ index ] {
1121+ var op : String ?
1122+ if index + 1 < chars . count , mask [ index + 1 ] {
1123+ let pair = String ( chars [ index ... index + 1 ] )
1124+ if [ " >= " , " <= " , " == " , " != " ] . contains ( pair ) {
1125+ op = pair
1126+ }
1127+ }
1128+ if op == nil , chars [ index ] == " < " || chars [ index ] == " > " {
1129+ op = String ( chars [ index ] )
1130+ }
1131+ guard let op else { continue }
1132+ let lhs = String ( chars [ ..< index ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1133+ let rhs = String ( chars [ ( index + op . count ) ... ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1134+ return ( lhs , op , rhs )
1135+ }
1136+ return nil
1137+ }
1138+
1139+ /// Index der `)`, die die `(` bei `openIndex` schließt.
1140+ private static func matchingCloseParenthesis ( in chars : [ Character ] , from openIndex : Int ) -> Int ? {
1141+ var depth = 0
1142+ var inString = false
1143+ var escaped = false
1144+ for index in openIndex ..< chars . count {
1145+ let char = chars [ index ]
1146+ if inString {
1147+ if escaped {
1148+ escaped = false
1149+ } else if char == " \\ " {
1150+ escaped = true
1151+ } else if char == " \" " {
1152+ inString = false
1153+ }
1154+ continue
1155+ }
1156+ switch char {
1157+ case " \" " : inString = true
1158+ case " ( " : depth += 1
1159+ case " ) " :
1160+ depth -= 1
1161+ if depth == 0 { return index }
1162+ default : break
1163+ }
1164+ }
1165+ return nil
1166+ }
1167+
1168+ /// `(a || b)` → `a || b`; `(a) && (b)` bleibt unverändert.
1169+ private static func innerOfOuterParentheses ( _ text : String ) -> String ? {
1170+ let chars = Array ( text )
1171+ guard chars . first == " ( " , chars . last == " ) " ,
1172+ matchingCloseParenthesis ( in : chars , from : 0 ) == chars . count - 1 else {
1173+ return nil
1174+ }
1175+ return String ( chars [ 1 ..< ( chars . count - 1 ) ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1176+ }
1177+
1178+ /// Java-Stil `name.equals("Anna")`.
1179+ private static func parseEqualsCall ( _ text : String ) -> ( lhs : String , rhs : String ) ? {
1180+ let chars = Array ( text )
1181+ let marker = Array ( " .equals( " )
1182+ guard chars . last == " ) " else { return nil }
1183+ let mask = topLevelMask ( chars )
1184+ for index in chars . indices where mask [ index ] && index + marker . count <= chars . count {
1185+ guard Array ( chars [ index ..< ( index + marker . count ) ] ) == marker else { continue }
1186+ let openIndex = index + marker . count - 1
1187+ guard matchingCloseParenthesis ( in : chars , from : openIndex ) == chars . count - 1 else { continue }
1188+ let lhs = String ( chars [ ..< index ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1189+ let rhs = String ( chars [ ( openIndex + 1 ) ..< ( chars . count - 1 ) ] ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1190+ return ( lhs , rhs )
1191+ }
1192+ return nil
10241193 }
10251194
10261195 private func renderNumericExpression ( _ expression : String ) throws -> String {
@@ -1160,9 +1329,8 @@ public struct VisuStructSimulationEngine: Equatable, Sendable {
11601329 continue
11611330 }
11621331 var stripped = String ( afterKeyword ) . trimmingCharacters ( in : . whitespacesAndNewlines )
1163- if stripped . first == " ( " , stripped . last == " ) " {
1164- stripped = String ( stripped . dropFirst ( ) . dropLast ( ) )
1165- . trimmingCharacters ( in : . whitespacesAndNewlines )
1332+ if let inner = innerOfOuterParentheses ( stripped ) {
1333+ stripped = inner
11661334 }
11671335 return stripped . isEmpty ? " true " : stripped
11681336 }
0 commit comments