import { useState, useEffect, useRef, useCallback, useMemo } from "react"; import { Play, Pause, SkipForward, SkipBack, RotateCcw, Code2, ChevronDown, Terminal, Layers, Variable, FileCode, Circle, AlertTriangle, Check, Loader2, Sun, Moon, FilePlus2, Trash2, Send, Download, Maximize2, Minimize2 } from "lucide-react"; const JAVA_LOGO = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAIAAgADASIAAhEBAxEB/8QAHAABAAIDAQEBAAAAAAAAAAAAAAUHBAYIAwIB/8QAVBAAAQMCAgUGCQcHCQcEAwAAAAECAwQFBhEHEiExQRNRYXGBoQgUIjJykbHB0RUzNkJSsrMjU2J0dYKSFjdDVYOUosLSFyQ0VnOT8DVEhOFFVGP/xAAbAQEAAgMBAQAAAAAAAAAAAAAABAYDBQcCAf/EAD0RAAIBAgIFCgQFBAEFAQAAAAABAgMEBRESITFBkQYyUWFxgaGxwdETIuHwFBUzNEIWI1JTNUNygpKi8f/aAAwDAQACEQMRAD8A4yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkV9HU0M6Q1UaxvVjZG8zmuRHNci8UVFRTHLUv1jbftCtmxHAzOttcboJlTe6Fsity/d2L1KpVZgoVlVT6U2n3E2+s3azj0SSkuxr02AAGchAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRuhCljrtFSUVQ3WhnfUROTna5cl9qnPNwpn0dfUUcnnwSujd1tVUX2HTuhmjWi0b2hjkydJG6Zf33q5O5UOetIzGx49vrWJknj8q+tyqaTDquldVorZn6l05Q2+hhlpN7UsuKTIAAG7KWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMstvnut3pLbTJnNUzNib0Zrln2bzDLj8HXCzpauXFVXH+Tizho803uXY96dSbO1eYj3VdUKTm/tmwwuxlfXUKMdj29S3l1UFLFRUFPRwJlFBG2JiczWoiJ3IcjYsq0r8U3WtaubZ6yWRq9CvVU7jqLSHeG2LBtzuOsjZGQKyLpkd5Le9c+w5JNPgdN/PUfYW3ltXivg28d2b9F6gAFgKEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACZwfhu54ovDLdbYs1XbLK7zIm8XOX3cTzOcYJyk8kjJSpTrTVOms29iMrR9hSsxbfmUEGtHTsyfVTomyJnxXcifBTqi1UFLbLbT2+iiSKnp40jjYnBE9/SRmDMM27C1ljttvZn9aaVyeVK/i5fcnBDz0g4npsKYcnuUytdOvkU0Sr85Iu5OpN69CFSvbqd7VUIbNy9Tq2DYXSwa1lVrP5ss5PoXQvvW+4qrwjMSpU19PhmlkzZTKk9Vkv8ASKnkt7EXP95OYqA96+rqK+tnrauV0s871kke7e5yrmqngWe1oK3pKmtxzTE7+V/dTry37OpbgACQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWJoy0Z1+JXR3G5pJR2nPNFyyknT9DmT9L1Z8MVavChDTm8kSrOyrXtVUqMc2/vNkBgPBt1xdcORomclSxqnL1T08iNObpdzJ7E2nSuEMNWvC9pZb7ZDqt3ySO2vld9py/wDiJwM60W2htNvioLdTR01NEmTI2JsT4r0rtU9a2qp6Kklq6uZkMETVfJI9cmtRN6qpU72/qXUtFao9B1TBsBoYXDTlrnvfR1LoXmfl0rqS2W+evrp2QU0DFfI9y7ERP/Nxy3pJxfVYvvzqt6Ojo4c2UkKr5jedf0l3r2JwJXSzpAnxXW+JULnw2eB+cbV2LM5PruT2Jw6zQjdYZh/wF8Spzn4FP5S4/wDjZfh6D/tra/8AJ+y3cegAA25UQDKtlur7nUpTW6iqKuZfqQxq5e43+0aIrwtK6uxFcKOx0bE1pHSvR72p05Lqp2qYKtzSo8+WX30E21w+5u/0YNrp3d72FbA2++VeDbQrqTDtDJd502LX16ryaL+hEmSL1uz6lNSke6SR0j8tZy5rkmXch7pzc1nll2mGvRVF6Okm+rWuO/uzXWfIAMhgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9RRySytiiY58j1RrWtTNXKu5EQ97XQVl0r4aC308lRUzO1Y42JtVfh0nRei/RxQ4XhZX16R1d3cm2TLNsGfBnTzu382RDvL2naxzlre5G4wjBa+J1Moaora+j3fUa3ov0Sth5K74qha+TY6KhXa1vTJzr+ju5+YuRrWtajWoiIiZIicD9IPGWKbThW1rW3OfJVzSGFm2SV3M1PfuQqdatWu6mvW9yOqWllaYRbtR+WK2t7+1/fUSN2uVDabfLX3GpjpqaJM3yPXYnxXoTapzhpS0hVmLKlaOk16a0RuzZEq5OlVNzn+5OHWRWPcaXbF1fytY7kaSNVWClYvkM6V+07pXsyNZLDh+GKh89TXLyKBj/KWd7nQt9VPxl7Lq49AB60sE9VUR09NDJNNI7VZHG1XOcvMiJvLjwBobVzY6/Fjlai5ObQxO2/vuT2N9fAn3N1St45zfuaLD8LucQqaFCOfS9y7X9sq7DWG71iOq8XtFBLUqi+W9EyYz0nLsQuHCOhW30yMqMSVi1su9aeBVZEnQrvOd2ZFo08Fus1t5OCOmoaKBqrk1EYxiJvVeCdZTOknS9LMstswo90Ue1slcqZOd/004J+ku3my3ml/G3V9LQoLRXT9fYuX5PhmC0lVvXpz3Lp7F6vUbpirGGE9H1Ctut9LTrWInk0VKiNyXnkVN3bmq8xROMsY3zFVVylzqVSBq5xU0fkxR9ScV6VzUgJHvlkdJI9z3uVXOc5c1VV3qqnybS1w+nb/ADbZdLKzimPXF/8A218tNbIrZ39Pl1AAE40YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMm2UNXcrhBQUMD56md6Mjjam1V/84mMdFaEcENsVpberhCnynWMRWo5NsES7Ub0Ku9exOciXt3G1p6T27jbYNhU8TuFTWqK1t9C93uJnRjgWiwhbUe9GT3SZv+8VGW79BnM1O/fzIm48QhC41xHQ4WsE11rVz1fJiiRclleu5qe/mRFUp0pVLmpm9bZ16nTt8Pt9GPywijB0i40t+D7Vy0+U1bKipTUyLkr1515mpxU5lxJfLniG6yXK61Lpp37ETc1jeDWpwRBiS9V+ILxPdLlMsk8q7vqsbwa1OCIRpbLCwjaxzeuT2s5XjuO1cTqZLVTWxer6/IEjhyyXHEF2itlrp1mnk7Gsbxc5eCJzmNbaKquNfBQUULpqid6RxsbvVVOpNHGDqLCFlbTxo2WtlRHVVRlte7mT9FOCdvE+396rWGrXJ7DxgWCzxOtr1QjtfouvyPDR1gG1YRpWyNa2quT25S1Tm7elrE+q3vXjzGx3q6UNmtk1xuNQynpoW5ve7uRE4qvBOJk1U8NLTSVNRI2KGJivke5cka1EzVVOYdKeN6nF14VsLnx2qncqU0K7NbhruT7S9ybOfOvWtvVv6rlN6t7OgYliFtgVooUorP8AivV+u9nrpM0hXDFtQ6lgV9JaWOzjgz2yZbnPy3r0bk7zRwC2UqUKUVGCyRyq6u613VdWtLOTAAMhHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANv0QWFl/x1R087Nemp86mZFTYrWZZIvQrlah1MUT4MkbFvF5mVE1208bUXoVyqvsQvYqeM1HK40dyX1Oq8j7eNPD/iLbJvPu1I/MjmXTPit+I8VSQQSKtvoHOhgRF2Pd9Z/aqZJ0IhfGku7usmB7pcI3asrYVjiXij3qjUXszz7Dk0k4JbJt1nu1L1Nby0xCUYwtIvbrfp458EAAWM54XX4OOGWKypxRVR5uzWnpM+H23J93+IuogtH9uZasFWihamSspWOf6bk1nd6qTqbikXtd1q8pPu7DteDWUbKyp0ltyzfa9vsVP4RmIZKKyUthpnq19cqvnVF28m1UyTtd90oIs/wkeV/lxSa+ep8ns1P435lYFnwumoW0ct+s5nymuJ1sSqaWyOpd33mAAbA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABZ3g5XFlLjSooXqiJWUrkZ0uYqOTu1joc46w7dJ7LfaK60/wA5SzNkRPtIi7U7UzTtOu7TX01ztlNcKR6Pp6iNskbuhUz9ZWMbouNVVNz80dM5GXqqWsrd7YvPuf1NC8Ih7m6Psm7n1kSO6snL7UQ5xOoNNdvdcNHNyRiZvp0bUJs4Mciu/wAOscvmwwWSdu11mg5ZwlG/UnscV5sAA25Ujsu0Oa+1Uj2ea6Bip1aqGUapolurbvo/tU+trSQwpTyc6Oj8nb2Ii9ptZQa0HCpKL3M7xa1o16EKkdjSfFFS+EfYX1dko79AxXOonLHPkn9G9UyXsd94oQ7NuFJT3ChnoauNJaeeNY5GLuc1UyVDlHH2GarCuI57ZOjnQ569NKqfOxruXr4L0opYsGulOHwXtWzsOe8scLlTrK8ivllqfU/qvIgAAbwpIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALk8HvGKQyrhS4S5Mkcr6Fzl2I7e6Pt3p0586FNn3DJJDMyaJ7o5GORzHNXJWqm1FRecj3VtG4pOnIn4ZiFTD7mNeG7aulb197zs2qgiqaWWmmYj4pWKx7V4tVMlT1Kci4uss+HsR1tonRc6eVUY5U89i7Wu7UVFOhdEeOYsWWjxerc1l2pWok7N3KJu5RE6ePMvWhB+EDhJblaW4jootaqoWatQjU2vhzzz/dXb1KvMV/DqkrS4dGpqz89xfeUNvTxbD43dvrcdfdvXavRlAAAtBzMtnwdcStorvUYdqpNWKt/KU+a7ElRNqfvN+6hfhxdSzzUtTFU08jopono+N7V2tci5oqHU+jLF1Pi7DzKrNra6HJlXEn1X/aT9F29O1OBWsZtHGXxo7HtOj8j8VVSl+CqP5o649a6O7y7DajUdKWD4cXYedBG1jbhT5yUki7PK4sVeZ27ryXgbcDTUqkqU1OO1FyubendUpUaqzi9pxdUwy01RJTzxujlicrHscmStci5KinmWx4RmHo6K90t+po0ayuRWT5Js5VqbF7W/dUqcu9tXVekqi3nFMSsZWN1OhLd4rc+AABnIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnWO611lusFzts7oamB2s1ybl50VOKLuVDp3R7jC3YzsiysRjKpjdWrpXbdRV47d7V4L2HKhI4cvVxw/d4bpbJ1iniXra9vFrk4opr7+xjdRzWqS2M3+BY5UwyrlLXTe1eq6/M2XS9g52FcQq+mY75MrFV9M7gxeMa9XDoy6TSTpShuNi0r4JqKF+UFWjUWSJVzfTS/VenO3PjxTNFOd73bKuz3aptlfHydTTvVj28OtOdFTanQosLmVSLpVdU47fc+49h1OhUVzba6VTWstz3r24bjDJvBWJa/Ct9iudC7NE8maJVybKzi1fcvBSEBOnCM4uMlmmaOjVnRmqlN5NbGdjWC60d7s9NdaCTXp6hiPavFOdF6UXNF6jPKX8Gi8SPjudikcrmR6tTCme7PyX/5V9ZdBSLuh8CtKn0HasJvvx1pCvve3tWpla+EYyN2AYnvRNZldGrF6dV6ew50Lq8Ja9RqltsEb0V6KtVMiLu2K1n+ZfUUqWbCYONss95zXlZWjUxKSjuST7QADZlbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnUNouddktLRTSNX62rk31rsPcISm8orNnmc4wWcnkjBBtNJge6y7Z5aeBOZXK5e7Z3n1U4cstvRUuN+brpvZExFd6s1UmfltylpSjkutpeZC/M7bS0YyzfUm/I1QEtVuw9EitpYrhUO+1JI1iepEVSLerVeqtbqpwTPPIiVKeg8s0+wl06mms8mu0+QAYzISGH7xcLFdYrnbKh0FREuxU3OTi1U4ovMb9jystuPcNNxNb42096t0aNuNKm90WeSSN+01FXfwRdu5M6xPajqqijnSemldFIiK3NvFFTJUXnRUVUVCPVoKU1UWqS+8mT7a+lSpSt566ct3Q9zXWvFajxABIIBbHg0U73YnudUiLycdEka9bntVPuqXNi7EFBhqxz3W4PyjjTJjEXypH8GN6V7tqmhaP223Rto9+U7/JyFXXryywp847Z5EbU4rltXm1lzKjx/jC5YvuvjVWvJU0WaU1M1c2xt97l4r7iuTtXfXcp/wAFqz6cug6HSxKOB4XCk9daSzy6M9eb7t29kViO71d+vdVdq5+tPUP1lRNzU4NToRMk7CPALFGKiklsOfTnKpJzk829bAAPp5ABlW1lC6fWuEsrIWpmrYm5vf0JnsTrU9RjpPI8ylorMxT7bFK5M2xvVOdGqbNBiO0UOSUGH4tn15X5vXuX2ktQY6pHuRtXRSQJ9qN2uidmw2NK0tZPKddJ9j83ka6rd3UVnCg2u1Z8FmaC5FauTkVF5lPwuOJ1su9KkrEp6uF3O1HevPcpAXzBdHUMdLbV8Wm36irmx3vQlV8Bqxjp0pKSIlHHqUpaFaLiyuwe9bS1FFUvpqqJ0crFyVqngaOUXF5Pab2MlJZrYAAfD6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAetLTz1UyQ08L5ZHbmsbmptVpwPVyo2S4zJTtX+jZ5T/AF7k7zbMJ0tup7PA6g1HNkYivkTe53HPt4cCXLbY4HRUVOs9Jvctn1Kne45VcnCitFLfv+hD2zDtot+Sw0jHvT68nlu793YR+IMXUdukdTUrEqqhuxclyYxeZV4r0IbMaLd8DSrM+S3VTFaq5pHNmip0ZpvJt7CvQpJWcF15biBYyoV6rleTb6M8/M1+6Yiu1wVUmqnMjX+ji8lvdv7SJJuowrfYV20Lnpzse13vMOSzXaPz7bVp/Yu+BUK9O6lLSqqTfWmXChVtYxypSjl1NGADIdQ1rfOo6hOuJ3wPlaapTfTyp+4pGcJLcSVOL3niD18WqPzEv8Cn0lJVLuppl/s1GhLoGnHpPAGS2317vNoqleqJ3wPVlnuz/NttYv8AYu+B6VGo9kXwPLrU1tkuJgkthuvobVVfKVRSJXVMK500EnzWvwe/i5E+ym9d65bF+WYevb91sqO1uXtPePCl/f8A+wVvpSNT3np2VeostB8GfIX9GjJS045rpaMXEF7ul/uL7hdquSpndxcuxqczU3InQhHGyR4LvbvObTx+lL8MzLiwHcFy5Wspmeijne5DPTwu5yyjTa8DBVxa2lJznVTb355moA3uHAMabZrk93QyJE9qmbBgi0M+ckqpV6XoidyEqGCXctqS7yJPHLOOxt93vkVuC0X2PDNuZrz09MxETPOaRVz9amvXbFVJAqwWGhp4UTZy6xIn8KfH1CthcbeOdaol1LW/QUMUdy8qFNvrepepqb4JmRpI+KRrF3OVqoinme1XVVFXMs1VPJNIv1nuzPE1UtHP5dhtY6WXzbQADyeiQsd1qrTWtqKdyq3dJGq7HpzL8S2rfVRVtJFVQLnHK1HN+BSpY2jOd0lllhcuaRTKjehFRF9uZYcAupKq6L2PzK7j9rGVJV0ta29hk44szLlbH1Ebf96p2q5ipvc1Nqt96FYF4LtKYukTYLnVQN2Njme1OpFU9cobeMZxqrfqZ85PXEpQlSe7WjGABXCxgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGXbrlXW6RX0VTJCq70Rdi9ablNjosd3CNESqpYJ0525sX3p3GoglUL24oaqc2l4cCLXsqFfXUim/HiWLTY7tr/n6WpiXoycntQz4cXWGXfWLGvM+Jye4qsGwhj11Hbk+72NdPALWWzNd/uW7Hf7NJ5typf3n5e0yGXK3P8yvpXdUzfiU0CRHlFV3wRHfJylumy6m1VM7zaiJep6H2ksS7pGfxIUkDJ/Ub/wBfj9Dw+Ti/2eH1Lt5WP843+I/FmhTfKz+JCkwP6jf+vx+h8/pxf7PD6l0uq6RvnVUKdcifE8n3S2M864UidczfiU2Dy+Uc91NcT2uTkN9R8C3JL/ZY99ypl9F+fsMeXFlhj/8Ae66/oxuX3FVgxS5Q3D2RXj7mWPJ23W2T8PYsibHFoZ5kdVKvQxETvUwajHzETKC3OXmV8uXsQ0UEeeN3ctjS7iRDA7OO2LfebTU44usmyGKmhTnRquXvUi6vEV6qs0luMyIvBi6id2RFAh1L65qc6b4kynYW1PmwXA+nvfI5XPc5zl3qq5qfIBEJewAAAAAAFl6OaVYLByr0yWeVXp1JsT2KaHYLZNdrlHSRIqNVc5H/AGG8VLfpoYqenjgiajY42o1qcyIWPk/auVR1nsWpdpW+UN1GNNUFtet9gle2ON0j1ya1FVV5kQpetm8YrJ6j87I5/rXMsDSFeG0tAtuhenL1CeXkvms4+vd6yuTzyguYzqRpR/jt7We+T9s4U5VZfy2diAAK8WEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHvQ0lRW1LaelidLK7c1v/mxDwLC0ZRUqWueVmqtQsurIvFEyTJOreTbC0V1XVNvJELELt2tB1Es2eNowRBExJrrMsrkTNYo1yanWu9ezI0661UdVVOdBAyCBq5RRsTLJvTzrzqpcpqd4wTR1Uz56Od1K5y5qzV1mZ9HFDf4hhD+FGNtHZt6X3lfw/F18WUrqXZ0LuK7BtU2Brq1fyc9JInpKi+wxpcHX5nm00cnoyt96mglht3HbTfAsEcStJbKi4mvAl34ZvrN9ul7FRfYp5LYb0m+2VXZGqmF2tdbYPgzMrqg9k1xRGgkfkO8f1XV/wDaU/W2C9O3Wyq7Y1Q+fh63+D4M+/iaP+a4ojQTEeGL6/LK3SJ6Tmp7VMqLBt8f50UMfpSp7szJGxuZbKb4MxyvraO2ouKNdBt8GA652XLVtOz0Uc74EhT4DpG5eMV88noMRvtzJMMHvJ/wy7WiNPGLOH88+xM0AFo02ELHCqK6mfMqcZJFXuTJD2ueGbTWUXi7KWOmcm1kkTURUXp506FJf9P3Gi22s+giPlBb6SWTy6SqAS18sFxtL1WaJZIM9kzEzb283aRJpatKdKWjNZM3VKrCrHSg80AAYzIDIt1FU3CrZTUsavkd6kTnXmQzrBYK68SJyLOTgRfKmcnkp1c6liW6gtmH6BVR7Im/0k0ioiuXpX3G1sMLnc/PP5YdPt7mqv8AFIW3yQ+afR7+x+4assFmouSZk+Z+2WTLzl+CHlifENNZ4FYitlq3J5EWe7pdzJ7SBxDjbNHQWhqpwWd6fdT3r6jSppJJpXSyvc97lzc5y5qqm0u8WpW8Pg2u7fuXuaq0wircVPjXe/dvfsfdZUzVlVJU1EiySyLm5yniAVdtyebLSkorJAAHw+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy7Vcau2VKVFHKrH7lTejk5lTiYgPUJyhJSi8mjzOEZpxks0ywrVjmjlajLhA+B/F7PKb6t6d5sVHdbbWoni1bBIq/VR6IvqXaU2Dd0MfuIappS8H99xpK+AW83nTbj4r77y8AU1TXK4U2XIVtREicGyKiGfDim/Rbrg9yfpsa72obGHKKi+dBrg/Y10+TtZc2afFe5a2XQMismY0vbd74H+lF8D1bji8Jvio1/s1+JmWPWj6eBgeA3S6OJZOR+ZFcrjm7fmKP8Agd8T4dje8ruZSJ1Rr8T68dtOvgfFgN31cSycugFYSYyvjvNniZ1RJ7zGlxNfZPOuMqeiiN9iGOXKC2WyLfD3MkeT9y9rXj7FspvPCoqqan2z1EUSfpvRvtKgnudxnz5avqZM+DpXKntMVVVVzVc1I0+USXMp8X9CTDk4/wCdTgvqWtVYosdOi51zJF5o0V/s2EPW48pW5pSUU0q88jkandmaACDVx66nzco9i9yfSwG1hzs33+xslfjO8VCK2J0NMxdmTGZrl1rma69znvV7lzc5c1U+Qaytc1a7zqSbNnRtqVBZU4pHvRUtRW1DaelidLI7cie1eZDYaG2WK2ZTXqviqJk3U0C66IvSqb+7tNYRzkRURVRFTJcl3n4faNaFLXoZvr2cPvsPlajOrq08l1beJuVfjd7I+QtNGyCNEya6REXJOhqbE7zVq+vrK+Xlayokmdw1l2J1JuQxgeri9r3H6ktXRu4Hm3sqFvrpx19O/iAARSUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMttsr7i/Uo6aSXLeqJ5Kda7kJHBdniu9yclQq8hC3Xe1FyV23YhaFPDDTwthgiZHG1MmtamSIbrDcId1H4k3lHxZpcSxdWkvhwWcvBFN3GkWiqXUz5Y5JWbHoxc0avNnxUxjer9gqSaqlqbfUMTlHK5Y5c0yVdq5KhAz4UvsS/wDBconOx7V95FuMNuKU2tB5dWsk2+JW1SCbms+vUQYM+ay3eLz7bVp/ZKvsMZ9LUx+fTyt62KhDlSnHamibGrCXNkmeIP1WuTe1U7AjXLuRV7DHkZMz8B7R0lVJ83TTP9FiqZkFivE3mW2q63Rq32mSNGpPmxb7jHKtThzpJd5Gg2GnwdfJcteCKFF/OSp7syTpcBTu21NwjZzpHGru9ciXTwy7nsg+/V5kSpilpT21F3a/I0sG2XrBVZTN5W3vWqYibWKmT06uCmqyMfG90cjHMe1cla5MlTsMFxa1reWVSORnt7qjcRzpyzPkAEckAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+mMc96MY1XOXciJmqgHyCYo8M3uqRHMoZI2rxlVGe3aSbMD17Wa9TW0kLU3rmq5dyEynh9zUWcYPLh5kOpf21N5Sms+PkaoCfq7DQwZp/KG3ucnDavszISojSKV0aSxyon1mKqovrMNWhOlzvNPyM1KvCrzfJrzM/Dd3ls9xSpY3XjcmrIzPzm/EtC03WhukCS0c7X7PKauxzetCnD7hllgkSWGR8b03Oa7JU7Sfh+K1LRaDWcej2IGIYVTvHpp5S6fcu0FZ27Gd3pkRs6x1TE/OJk71p7yeo8d0D0RKqknhXnaqPT3KWSjjVpU2yyfX95FarYLd0tkdJdX3mbcCFp8U2KbdXtYvM9rm+1DNiu1rlTOO40juqZvxJ8LmjPmzT70QJ2taHOg13MzFa1d6J6gjUTcieo8m1dK7zamFeqRAtVTJtWohT99DIpx6THoy6D2BiPuVujTN9fSt65m/ExZsR2SLzrlAvoKrvYeJXFKHOkl3o9xt6subFvuZKg1qpxrZokXk/GJ1/RjyTvyImsx7KuaUlAxvM6V+fcmXtIdXFrSntnn2ayZSwm7qbIZduo3shMTNw8+Jflh0CPRNi5/lE6stpoVfiW81iK19a+Ni/Vi8hO7aRDlVzlc5VVV3qpqbrHqc46EKefbs4G3tcAqQkpzqZdm3iZNzSgbVOS3PndBwWZER3cYoBWpS0m3lkWWMdFJZ5gAHk9AAAAH6iKqKqIqom9eY/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZNuoKu4VCQUcD5X8ctydKrwPuy0D7ldIKJjtVZHbXcyJtVfUW3arfSW2kbT0kSMYm9eLl51XiptsMwyV43KTyijU4nikbNKMVnJmq2fA0TdWS5zrI781EuTe1d69mRN1Utlw3Sa/JRU6Lsa2Nub3+9etSYNVxrhuqus7KyjlasjGaixPXJFTNV2Lz7SyVLaNlRbtaacvH76itU7qV7WUbqplHw++shLtjavqFcyhjZSx8HL5T19yGt1dZV1j9eqqZZnc73quR6V1tr6FypV0k0XS5uxe3cYhULm5uKssqzfZ9C4Wttb0o50Uu36gAEQlgAAAAAAAAAAAAAAAAAAAAAAAAA+4opZXasUb5HczWqqn1LPYfG8tp8AmKLDN7qslZQyRtX60vkJ37SfoMDJGnK3Sta1rdrmxbE7XL8CbRw65rc2Dy6Xq8yFWxK2o86az6Fr8iFwNTzVF/iRkevCjXcuipm1WKmWS9ew8cX0NPbr7NT0q5RZI5G556maZ5GzV2IrRY6R1FYoY5JOL02sRedV3uU0apmlqZ3zzvWSSR2s5y71UzXao0KCoJ6Us821sXUjBaOtXruvJaMcskntfWzzABqzagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRbqyagrYqynVEkjdmme5edFLDtOMbXVMa2qctJLxR+1ufQ5PfkVoCfZYjWs81DY9zIF7h1G8yc9q3ouqnqqapbrU9RFKnOx6O9h7FINc5qorVVFTihm093ulP8zcKlqc3KKqeo3VPlHH+dPgzS1OTj/hU4ouFURyKioiovAjquw2eqzWa3wKq71a3VX1pkV/Bi6+x5I6qbKifbjavuM+DHdybkktLSv6tZq+1SQ8asqyyqLis/cj/kt7RedN8Hl7E5UYIs8maxuqYV4ar8070MCfALF2w3JydD4s/Yp+Q4+bultqp0tmz9qGZFju1u+cpqtnUjV95ibwir0eK9j2ljFLpfB+5ES4DuCfN1lM70tZPcpjSYKvTd3iz/Rl+KG1R4zsb/OlmZ6US+7MyI8U2F+64MT0mOT3Hn8Dhk9k1/7e56/H4pDbBv8A8fY0Z+EL83dRtd1St+J5Owvfm77dJ2PavvLEbiCyO3XKm7X5Hq28Wl3m3KjX+2b8R+T2MubUfFew/Ob6O2n4P3Kzdhu+Jvts3d8T5/k7e/6sqP4S0kuVtXdXUi/2zfifvyhQf/uU3/db8T7+RWz2VH4D89ut9NcGVZ/J69/1ZUfwn6mHL4v/AOMn9RaS3Cg41lN/3W/E/FuVuTfXUif2zfiPyK2/2PwH57df614lYJhm+rutsvaqJ7z0bhW/u3W9ydcjU95Y7rxaW+dcqJP7ZvxPJ+IbK3fcqbsfn7D5+S2UedU8V7H1YzfPZS8H7mhNwffV300beuVvxPZmCby7etKzrkX3IbjJiuws317V9Fjl9xiy40sjfNfPJ6MS+/I+PD8Mhtqf/SPqxHFJ7Kf/AMsgI8B3FfPrKVvVrL7jJiwC5fnbmiejD8VMybHlvbnyNHUv9JWt96mDPj6Zc0htrG9L5VX2IhjcMIhtefE9qeMVNiy4GbFgOgTLla2pf6KNb8TNgwbY418qKWXL7cq+7I1WoxrepM+TWnh9CPP2qpGVN+vFSipLcajJd6Ndqp3ZHl32GU+ZSz7vcyKxxOpz6uXf7Isf5Kw9b268lJRRInGXL/MY1TimwULVZDKkmX1YGbPXsQrF73PcrnuVzl3qq5qfJiljso6qNNR++4yxwKMtdao5ffebncMeTuRW0FGyPP68q6y+pNntNauV2uNxdnWVckqfZzyanYmwwmtVyo1qKqruRCVocOXmsyWOhkY1frSeQneQKl1d3jybb6l7In07azs1pJKPW/dkSfTGPkejGNc5zlyRETNVN3tuA9z7jWbOLIU/zL8CbcywYZp+U1IoHKmz60j+rj7iVRwWs1p1moR6yLWxqinoUU5y6jVLVhhaemdc77nBSxN1+Rz8t/QvNnuy39RrVTJy1RJLqo3XcrtVNyZruQmMUYiqLzIkaIsNKxc2R57153c6+wgyHeToZqnQXyre9rf3sJlnCu06ld/M925L36QACETQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADMoLbUVyZUroXyfm1la1/Yi5Z9h6z2O8Q/OW2qy50jVU7iOJChvd1okRKevma1NzVdrN9S7CRTdBrKafdl5fUj1FXTzg135+f0MSWnni+dhkZ6TVQ8jaabHF1jTVnipp045sVq9y+4y2YzoZf8Ai7JGvOqK13tQkxt7SWytl2xfoRncXcdtHPskvVGlg3luIMJTfPWdGL007F9ino2twNL51PEzrhensMiw6nLm1o+RjeI1I86hLu1mhAsBG4Ek406fvSNPtKHAz90lKn/yXJ7z1+USeyrDj9Dz+bxW2lPh9SvAWK21YJdulpP72v8AqPttnwZ9ujX/AOYv+o9LBar/AOpHj9Dy8bpL/py4fUrcFltteDW8aDtqs/8AMfSUWDmcbZ2zIvvPSwSe+pHj9Dy8cp7qcuH1KyBaCfyPi3Lac/3FPtLnhaHaya3t9Bie5D0sFiudWijy8ak+bRk/vsKuZG965MY5y9CZmVDarnMv5K31T+lInZewsh2KMPRNybWty5mRO+B4SYzsjfNfPJ6MS+89LC7SPPuF3Ze7PLxS8lzLd9+fsjTIMLX2VdlA9ic73Nb7VJCmwNdpNss1NEnpK5e5CZmx3bk+ao6p/parfephTY+lXPkLcxvS+VV9iHpW+E0+dUb++pHl3GLVObTS++tntTYCiTJam4SO50jjRveqqSlJhCxwZK6nfOqcZJFXuTJDVajG15kz5NKeH0Y8171UjKq/3mpzSW41GS70a7VTuyPv43DKP6dLN9f1Z5/BYnW/Uq5Lq+iLPRtptUeerSUbct/ksIuvxjZqbNInyVT04Rt2etSs3uc9yue5XKu9VXNT5PFTH6uWVKCiuPsZKeAU89KrNyfD3NpumNrlUIrKNjKRi8U8p/rXZ3GtTzSzyulnkfJI7e57s1U8waivdVrh51JZm3oWtG3WVOOQABHJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJfBUMVRjCzU88TJYpK6Fj2PTNrmq9EVFTih5lLRi30HulD4k1Bb3kRAOuP5H4U/wCW7R/c2fAwr9hLC8Vjr5I8O2pj200itclIxFRUYuSpsNMscpt5aLLnPkTcRi5fFWrqZyoADdlJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABN4D+m9j/AGhB+IhCE3gP6b2P9oQfiIY636cuxki0/cQ7V5nXRH4k+j9x/VZfuKSBH4k+j9x/VZfuKUSHOR3Os/7cuxnHQAL+cEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABN4D+m9j/aEH4iEITeA/pvY/wBoQfiIY636cuxki0/cQ7V5nXRH4k+j9x/VZfuKSBH4k+j9x/VZfuKUSHOR3Os/7cuxnHQBIYcrGW7EFvrpGteyCpjke1yZorUciqmS9Bfm2lmjg0IqUkm8kSmHMDYpv6Nkt9pm5B26eX8nHlzorss+zM360aDK16Nddr5BDzspolk/xOy9hebFarUVqoqKmxU5iKuuJ8PWtVbcL1QU70+o+dut/DvKvVxa5qvKmsuxZnTqHJPDbWOlcS0u15Lw9zRKPQlheNqeMVt0nd/1GNT1I33mWuhvByplq3BOlKn/AOiRn0o4GidqrfWvX9CCVyfdMm26RMGXCRsUF/pWvcuSJMjou96IhhlWxDa9LgyZC0wDPQj8NvtT9TT7poOs8jXLbbxW0z+CTtbK3u1VK0xro7xFhZjqmpgbVUKL/wATT5ua30k3t7dnSdSRua9iOa5HNcmaKi5oqCWNksbo5GNex6K1zXJmiou9FTmPtDF7im/mea6zxe8lLC4i/hR0JdK2cP8A8OLD0pmxPqI2TSLHE56I96JmrUz2rlxN500YOjwviBk9BGrLbXIr4W8InJ5zOraip0L0GhFppVY1oKcdjOYXdpUs68qNVa4v74l2M0FQPYj2Yoc5rkzRUo02p/GfX+wiL/mZ/wDck/1m9aIrv8s4Atk7na00Efi0vPrM2J601V7TbSrVcRu6c3By2dSOoW3J/CLijGrGlqkk9st/ecZ3Wimt1zqrfUJlLTTOifs4tVUX2GMWHp/tHydjt9YxmUVwibMmW7XTyXJ3IvaV4Wi3q/FpRn0o5jiFq7S5qUH/ABbXdu8AASGG6NbhiG3UKJn4xVRx5dCuRDLJqKbZFhBzkoray2KTQa2akhlkxG+N72Nc5vieeqqpmqeeev8AsIi/5mf/AHJP9ZdCbjAxHXJbLBcLi5yJ4tTSS9rWqqd5UFid3J5KXgvY63Lk1hVODlKls65e5yFdKeOkudVSxS8tHDM+NkmWWuiOVEXLhnkedNTz1U7YKaGSeV65NZG1XOXqRDzcqucrnLmqrmqlueDRXRsvV1tz0ZrywNmY5UTPyXZKiL+8nqLRc1nQouaWbRzLDrSF9dxoOWipPty+9hAWDRLjC6NbJNSw22J23Wqn5Oy9FM19eRult0F0bWotyv1RIvFtPCjETtcq+wt+aaKCNZJpGRRpvc9yIidqmvV+PMHULlbUYhoM03pHJyip/DmVuWJ3ld5Q8EdEp8m8Is0nW19cpZeyNbh0MYQjaiOfc5V53VCJ7GoJ9DGEJGqjH3KFeCtqEX2tUk00qYE19X5b7fFpcvuk9YsT4fvjlbabtSVT0TPk2Pyflz6q5L3GKVe+gtKTkuJKpWWB1XoU1Bvqab8yqL9oNlZG6SyXlsjkTZFVx6uf77fgVViCyXWwV7qG7UUlLOm1Ecmxyc7VTYqdKHYnYQWN8MW/FVjlt1bG1H5K6CbLyoX8HJ0c6cUJFpjFWMkq2teJrcU5I29WDlaLRl0Z6nx2HMODrRSXy8sttTWTUjpUXknxwJImaIqrnm5MkyReclaXBjK+10Nyt9xc+Gqq3xPSWDVdDC1fnnZOVMti5pns2bdpBZ3LDWIJGtXkK+ilfGq6qLk7a1d+/Yqn1S4gu9Nb/EIKtY6dYJadWo1Nscjkc9ueWe1WoWGcasnnTlq1ev0KJQnbQjoV4PSWfbuy39ueroPjEttSz3+utjZ+XbTTOjSTV1ddEXflw6iOMm51tTca+eurJOUqJ3q+R2SJmq79ibDGM0M1FaW0hVXB1JOCyWersAAPRjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABN4D+m9j/aEH4iEITeA/pvY/2hB+Ihjrfpy7GSLT9xDtXmddEfiT6P3H9Vl+4pIEfiT6P3H9Vl+4pRIc5Hc6z/ty7GcdAAv5wQnbrjDE10po6WsvNW6njYjGxMfqMyRMkzRuWfWuZBAHmMIwWUVkZKtapVelUk2+t5gAHoxls+D9i2sp723DNXO6WjqWuWmRy58lIiZ5JzIqIuzny6S/EOUNFjlbpDsaouS+NtT1nVybkKrjNKMK6a3o6nyOuqlaxcJvPReS7MlqK88IKhZVaPZqlWor6SoilavFM11F+8c3HUOm3+bG79UX4rDl42eCyboNdD9EVrlnBRv4tb4rzZc3g0XfVnulikd57W1USZ8U8l/tb6i8Tk7Rfd/kTHVrrXO1YlmSGXm1H+SufVnn2HWBqsYpfDuNL/Is3JC7+NYfCb1weXc9a9eBV3hF2fxzCEF0YzOS3zprLlujf5K/4tQ56OxMS2yO82CutcmWrVQPjzXgqpsXsXJTj6eKSCeSGVqtkjcrXtXgqLkqGzwWtp0nB7vUrnLO0+HdxrrZNeK+mR8G7aEKDx7SPblVM2U2vUO/dauX+JUNJLj8Ga3K64Xa7ObsjiZTsXpcus77rfWTcQqfDtpvq89RpcBt/j4jSh158NfoXkm40PTxcfENHdXEjkR9ZIynb2rrO7mqb4Uf4TNz1qu02djk8hj6mRM+ddVvsd6yr4dS+JcxXfwOncobn8Ph1WW9rLjqKaJCwXm5WG4JX2qpWnqUY5iPRqO2OTJdioqEeC5yipLJrUcchOVOSlB5NbzNut2ul1mWa5XCqrH555zSq7Lqz3GEAFFRWSE5ym9KTzYPSnnmpp2T08r4pY3I5j2OVrmqnFFTceYPu0+JtPNHUmiHE8uKMIRVNW5HVtO9YKh27XciIqOy6UVO3M3Ep/wZHKtpvLc9iTxqn8K/AuApF9TjSuJRjsO0YHczubClUqPNteTyObfCCoWUmkKSZjUTxumjmdl9ra1fuoV4Wn4Sv0yoP2e38R5Vha8Pk5W0G+g5bj0FDEqyXT56wACYagAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE3gP6b2P9oQfiIQhN4D+m9j/aEH4iGOt+nLsZItP3EO1eZ10R+JPo/cf1WX7ikgR+JPo/cf1WX7ilEhzkdzrP+3LsZx0AZtjtdbebtT2y3xcrU1D0YxvDpVeZETaq9Bfm1FZs4NCEpyUYrNsxqeGaonZBTxSTSvXJjGNVznLzIibywsOaH8UXNjZq9YLVE7blMutJl6Cbu1ULj0e4GtWEaFvIxtqLg9v5arc3ynLxRv2W9HrzNpmljghdLNIyONiK5znKiI1E3qq8Cu3WMy0tGitXSdCw3kdSjBTvZZvoT1Lte/uy7yqrdoOsUSItfdrhUuTfySNjavrRy95LxaH8FMTJ1JVydLqp3uyF+0u4RtsjoqeaouUjVyXxWPNmfpOVEXszNdm07UiKvI4dnenBX1SN9jVMKWJVdevy9iZKXJy1ei9Dg5e5uNo0Z4RtVzp7jRUEzKinekkblqXuRFToVdpuRVOGNMTL3iGhtKWB0HjcyRcp41raufHLV2lqptTMg3cLiEl8fPPreZusKr2FWnJ2OWjnryWWvgjTdNv82N36ovxWHLp1Fpt/mxu/VF+Kw5dN/gn6Eu30RReWv76H/avOR+ouS5psU65wBd0vuDrZc1drPlgakq5/0jfJd3opyKXx4NV45az3GySOzdTSpPGi/ZemS5dSp/iGNUdOhpr+PqeOR138K9dF7JrxWteGZbpy/prtHyRpBrtRmrDWZVUez7fnf4kcdPoVF4Stn5a0W69xszdTyrBKqfZftRV6lRf4jU4RW+HcJPfqLZyss/j4fKa2wafo/DX3FEHS2gW1/J+j2nmc3KStlfUO58s9Vvc1F7Tm+ip5aysgpIW60s8jY2JzucuSe07Es9FFbbVSW+H5umhZE3qaiJ7jZY3Vypxp9L8iucirXTuKld/xWXe/ovEyzlbS5dflfSBdJ2P1ooZPF48t2TPJ71RV7TpTF11bZMM3G6uVM6anc9ufF2WTU7VVEOQZHukkc97lc5yqrlXipgwOlm5VH2E3ltd5Qp263633al6nyAb1omwJLi64OqatXxWqmciTPbsdK7fqNXq3rwTrN/WrQowc5vUii2lpVu60aNJZyZrWHcO3rENT4vZ7fNVOTznNTJjPScuxO1SybHoPuErWyXi8QU2e1Y6eNZF6tZck9pddqt1Da6GOit9LFTU8aZNjjbkifFeldpjYhv8AZ8P0iVN3r4aSNfN1lzc9eZrU2r2IVyri9erLRorLxZ0S15I2VrT07uWk1t15RX31vuNIotCuFIWpy9Rc6l3HWma1O5vvM5uiLBCJtt9Q7rqn/Eibnpuw9C9WUNuuFXl9ZUbG1fWqr3EY/TvDn5OGpFTprE/0Hz4eJT16+OR9dxybpfLlH/1b8cmWXhPCtlwvDPFZqd8LahyOkR0rn5qiZJv6ycNP0Z42bjSmrZ225aLxV7WZLNr62sirzJluNwNXcRqRqNVedvLNYzt528ZW2WhuyWS29Grec++Er9MqD9nt/EeVYWp4S30xt/7Pb+I8qst+HftYdhyTlD/ydbt9AACaaYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE3gP6b2P9oQfiIQhN4D+m9j/aEH4iGOt+nLsZItP3EO1eZ10R+JPo/cf1WX7ikgR+JPo/cf1WX7ilEhzkdzrP+3LsZx0XT4NNljd8pX+ViK9qpSwqqebsRz1+6nrKWOjPB11f5AP1cs/Hpdbr1WFsxao4Wzy35HKeSdGNXEouX8U33/bLJKJ8IrEtVJdosM08ro6WGNstQ1q5co921qLzoiZL1r0IXqc2eEBSS0+kaone1UZUwRSRqvFEbqr3tU0uDxjO5+bci58rqtSnhz0HtaT7Nf0K+ABbTk5sui7+cKx/rjDq9NyHLGiKhq6zSBan0tPJK2nqGyzOamyNib1VeCHVCbkKxjjTrRXUdM5Epq0qNrbL0Rpmm3+bG79UX4rDl06i02/zY3fqi/FYcuk/BP0JdvojR8tf30P+1ecgbtoSu/yTpBoUe/VhrM6WTb9rzf8AEjTST0p5pKeojnhcrZI3o9jk4Ki5ops61NVacoPeisWdw7avCtH+LTO0TXtI9sS74Hu9Dqo57qZz40y+uzym97SSw7co7vYqG6RZatVAyXJOCqm1Oxc0M5yI5qtVEVF2KilGi5Upp70/I7fOMLmg47YyXg0cz6CrP8q4/ppnszhoGLUv5s02N/xKi9h0yV7oawquHo73NLGrZJq+SGLNP6GNyo1e1VVfUWETcUuFWr5rYtRpeTNg7OxSmvmk235LwRU3hI3pKbD9HZI3/lKyXlZET82zdn1uVP4SgzcdMV8+XceV0sb9anpV8Vh27Mmb17XaymnFkw6h8G3intevic75QXv4y/qTT1LUuxe7zYTauR1xgKyx2DCNutjGI18cLXSrl50jtrl9aqcnW/V8fp9fzeVbn1Zodmmtx2bUYR3ayx8h6MXOtVe1ZLjnn5I8a6pjo6Oeql2RwxukevQiZr7DkbFd9rsR3youlfI5z5XLqMz2Rs4MbzIn/wBnWOI6aSsw9caOFM5J6WWJnW5iontOOnNVrla5FRUXJUXgecChH55b9R75b1ai+FTXNeb7Xq8vU/AAWE5+Xp4Mf/pd6/68X3XFwlTeDZQ1dPYblVT08kUNTMxYXuTJJEaioqpzpmu8tkpmJtO6nl96jsfJuLWGUk1ufmzn7wlvpjb/ANnt/EeVWWn4S30yoP2e38R5VhZsO/aw7DmvKH/k63b6AAE00wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ3R81X47sTU/rCD76EEbVokp1qdI9kjamerUcovU1Fd7jDcPKlJ9TJdhFyuqcVvkvM6rIzFLtTDN1eu5tHMv+BSSTMgdItR4rgS+TZ5f7jK1OtWqie0o9JaU4rrR226loUJye5PyOSS6/BpvMaR3OwyPRJFclVCir5yZI1+XVk0pQzbHdK2y3anudvmWKpp36zHcOlFTiipsVC63dv+IoumcZwi//AAF3CvtS29j2nZBqukXBVvxjbWQzvWnq4M1p6lrc1ZnvRU4tXZsI7A2k7D+IaeOKrqI7bcckR8E79Vrl/QcuxepdvtN6a5rmo5qoqLtRUKc41rapm/laOvxqWmKW7SanB7fvan4nOdZoZxfDOrIXW+oZnse2fV2dSoik1hzQhVvlbJiC6RRRJtWKkRXOd0azkRE9Sl5kJiPFmHsPxOfdbpTwvRNkSO1pHdTU2k/80u6nyR29S1mj/pfCrV/Fq81dL1eh74csNpw9b0orTRx00KbXZbXPXncq7VXrJNjmvYj2ORzXJmiouaKhzrpF0rXG/wAclus7ZLfbnZte7P8ALTN5lVPNToTtXgWxozv1ukwFZvGLjSRyspWxOa+dqOTU8nair0GC4sa1KmqtTa395kvD8bs7iu7a31RituxbdiQ02/zY3fqi/FYcunS2mW6W2o0b3aGC4Uksjkj1WMma5V/KM4IpzSbvBU1Qln0+iKbyzlGV7BxefyrzYABtyonRHg63fx3Bstse7OS3zq1Ez/o3+UnfrFmnN2gK+RWnGjqapmZFT10Do3Oe7Vaj2+U1VXsVO06B+W7P/WtD/eWfEqGJ27hcSyWp6zrfJq/hXw+ClLXH5eGzwyJA1vSVfkw5g2vuLX6s+pyVP0yO2N9W1ewlPluz/wBa0P8AeWfEo3whMTxXS80tloZ2S0tG3lJXMcjmulcnOmxcm/eUxWNrKtXjFrVtZJxvE4WdlOpGXzPUu1+20q1VVVVVVVVd6qfgBdDjYTYuaHXWBrzFfsJ265xvRzpYWpIiL5sibHJ60U5FN40WY+qMH1j6eoY+ptU7kWWJq+Ux27XZnxy3pxNZilpK4pfJtRZOTOLQw+5aq8yep9T3M6cVCpdIuiJLvcprth+pgpZ53K+anmzSNzl3uaqIuWfNllnzFiYdxLYsQU7ZrTcqeozTNY0dlI3rau1PUS2ZWKNataTzjqZ0q7tLTE6KjUylHc0/Jo5xp9DWMZJkY9LfC3Pz3VGadyKpv2DdDdntkrKu+T/Ks7dqRaurCi9Kb3duSdBZ8j2RsV8j2sa1M1c5ckQ0TGOlPDVhjfFS1DbpWpsSKmdmxF/SfuTszXoJ3469uvkh4GlWCYPhn96tu/yefBb+DN6byULY4m6kaeaxqZImxNyJ1IfZzfh7HV1vWlGzXO8VbY4GVHJsiRdWKFr0Vq5Z9e1V2nQHy1Z8v/VaH+8s+JGurKpbtKWttGzwzGqGIxnKGpReSz3rJayjfCV+mVB+z2/iPKsLO8IuqpqvF1DJS1EM7EoGoro3o5EXlH7NhWJaMO1W0Ow5jygkpYlWa6fQAAmmmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABb/g4YellulViSeNUggYsFOqp5z3Zayp1Js/eNT0c4AueLapkzmvpbUx35WpVPOy3tZnvXp3Jx5l6Xs9uo7RbILdQQthpoGakbE4J71Xeq8TSYtfRjB0Ya29vUXTkrglSrWjd1VlGOtdb9l5mWVz4QV2bQYEdQo/KW4TNianHVaus5e5E7SwaiaOngfPPI2OKNqve9y5I1E2qqrzHL+ljFv8rMTungVyW+mRYqVq7M0z2v63L3Ihq8Lt3WrqW6Ov2LPynxGNpZShn809S7N74GngAt5yQGbRXe60TdWiudbTNThFO5idymED40nqZ6jOUHnF5EpUYixBUMVk98ucrV2K19U9U9pGOVXKqqqqq71U/AfIxUdiPs6k585tgAHo8AAAAAAAAAAAAAAAAAAH0x7mPR7HK1yblRclQk4cSYhhbqw326Rt5m1b0T2kUDzKMZbUe4VZ0+Y2uwzK66XKuTKtuFXVJ//aZz/aphgH1JLUj5KUpPOTzAAPp5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB60k3i9VFOkcUvJvR+pK3WY7Jc8nJxReY8gGsz6nk80dY6O8S23E2HYaq3xx07okSOalbknIORNyJ9nmXm7SVvd4ttkoH110rIqWBv1nrvXmRN6r0IcoYWxHd8M17620VPIyvjWN6K1HNci86LsXJdqGPe7xdL3VrV3WunrJuDpHZ6vQibkToQr7wTOq2pfL4l9p8tNC1ScM6uzq7fp4m66UtJdXihX2y2tkpLQjtqKuT6jLcruZOZvr6K7AN3RoQoQ0ILJFLvL2te1XVrSzb+8kAAZSKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z'; // ============================================================ // JAVA INTERPRETER ENGINE // ============================================================ class JavaInterpreter { constructor() { this.reset(); } reset() { this.states = []; this.output = []; this.errors = []; this.callStack = []; this.globalVars = {}; this.methods = {}; this.currentLine = 0; this.iterationCounters = {}; this.maxIterations = 10000; this.totalSteps = 0; this.executedLines = new Set(); this.inputQueue = []; this.inputIndex = 0; } tokenize(code) { const tokens = []; let i = 0; const lines = code.split('\n'); for (let lineNum = 0; lineNum < lines.length; lineNum++) { let line = lines[lineNum]; let col = 0; while (col < line.length) { // Skip whitespace if (/\s/.test(line[col])) { col++; continue; } // Single-line comment if (line[col] === '/' && line[col + 1] === '/') { break; // skip rest of line } // Multi-line comment start (simplified - single line only for now) if (line[col] === '/' && line[col + 1] === '*') { const end = line.indexOf('*/', col + 2); if (end !== -1) { col = end + 2; continue; } break; } // String literal if (line[col] === '"') { let str = '"'; col++; while (col < line.length && line[col] !== '"') { if (line[col] === '\\') { str += line[col++]; } str += line[col++]; } str += '"'; col++; tokens.push({ type: 'STRING', value: str, line: lineNum + 1 }); continue; } // Char literal if (line[col] === "'") { let ch = "'"; col++; while (col < line.length && line[col] !== "'") { if (line[col] === '\\') { ch += line[col++]; } ch += line[col++]; } ch += "'"; col++; tokens.push({ type: 'CHAR', value: ch, line: lineNum + 1 }); continue; } // Numbers if (/[0-9]/.test(line[col]) || (line[col] === '.' && /[0-9]/.test(line[col + 1]))) { let num = ''; let hasDecimal = false; while (col < line.length && (/[0-9]/.test(line[col]) || line[col] === '.')) { if (line[col] === '.') { if (hasDecimal) break; hasDecimal = true; } num += line[col++]; } tokens.push({ type: 'NUMBER', value: num, line: lineNum + 1 }); continue; } // Identifiers and keywords if (/[a-zA-Z_$]/.test(line[col])) { let id = ''; while (col < line.length && /[a-zA-Z0-9_$]/.test(line[col])) { id += line[col++]; } const keywords = ['public', 'private', 'protected', 'static', 'void', 'class', 'int', 'double', 'boolean', 'char', 'String', 'float', 'long', 'short', 'byte', 'if', 'else', 'for', 'while', 'do', 'return', 'new', 'true', 'false', 'null', 'this', 'System', 'out', 'println', 'print', 'length', 'charAt', 'Math', 'abs', 'max', 'min', 'sqrt', 'pow', 'random']; if (keywords.includes(id)) { tokens.push({ type: 'KEYWORD', value: id, line: lineNum + 1 }); } else { tokens.push({ type: 'IDENTIFIER', value: id, line: lineNum + 1 }); } continue; } // Multi-char operators const multiOps = ['==', '!=', '<=', '>=', '&&', '||', '++', '--', '+=', '-=', '*=', '/=', '%=']; let matched = false; for (const op of multiOps) { if (line.substring(col, col + op.length) === op) { tokens.push({ type: 'OPERATOR', value: op, line: lineNum + 1 }); col += op.length; matched = true; break; } } if (matched) continue; // Single char operators and punctuation const singles = '+-*/%=<>!&|^~?:;,.(){}[]'; if (singles.includes(line[col])) { tokens.push({ type: 'OPERATOR', value: line[col], line: lineNum + 1 }); col++; continue; } col++; } tokens.push({ type: 'NEWLINE', value: '\n', line: lineNum + 1 }); } return tokens.filter(t => t.type !== 'NEWLINE'); } parse(code) { this.reset(); const tokens = this.tokenize(code); const statements = this.parseStatements(tokens, code); return statements; } parseStatements(tokens, code) { const stmts = []; let i = 0; const lines = code.split('\n'); // Extract methods and main logic const methodBodies = this.extractMethods(lines); for (const method of methodBodies) { if (method.name === 'main') { const mainStmts = this.parseMethodBody(method.bodyLines, method.startLine); stmts.push(...mainStmts); } else { this.methods[method.name] = { params: method.params, returnType: method.returnType, bodyLines: method.bodyLines, startLine: method.startLine }; } } return stmts; } extractMethods(lines) { const methods = []; let i = 0; while (i < lines.length) { const line = lines[i].trim(); // Match method declarations const methodMatch = line.match(/(?:public\s+|private\s+|protected\s+)?(?:static\s+)?(\w+(?:\[\])?)\s+(\w+)\s*\(([^)]*)\)\s*\{?\s*$/); if (methodMatch && !line.startsWith('//') && !line.includes('class ') && !line.includes('new ')) { const returnType = methodMatch[1]; const name = methodMatch[2]; const paramsStr = methodMatch[3].trim(); const params = paramsStr ? paramsStr.split(',').map(p => { const parts = p.trim().split(/\s+/); return { type: parts.slice(0, -1).join(' '), name: parts[parts.length - 1] }; }) : []; // Find matching closing brace let braceCount = line.includes('{') ? 1 : 0; let bodyStart = i + 1; if (!line.includes('{')) { while (bodyStart < lines.length && !lines[bodyStart].includes('{')) bodyStart++; braceCount = 1; bodyStart++; } let bodyEnd = bodyStart; while (bodyEnd < lines.length && braceCount > 0) { for (const ch of lines[bodyEnd]) { if (ch === '{') braceCount++; if (ch === '}') braceCount--; } if (braceCount > 0) bodyEnd++; } methods.push({ name, returnType, params, bodyLines: lines.slice(bodyStart, bodyEnd), startLine: bodyStart + 1 }); i = bodyEnd + 1; continue; } i++; } return methods; } parseMethodBody(bodyLines, startLine) { const stmts = []; let i = 0; while (i < bodyLines.length) { const rawLine = bodyLines[i].trim(); // Strip inline // comments (but not inside string literals) const line = this.stripInlineComment(rawLine); const lineNum = startLine + i; if (!line || line.startsWith('//') || line.startsWith('/*') || line === '{' || line === '}') { i++; continue; } // For loop const forMatch = line.match(/^for\s*\((.+)\)\s*\{?\s*$/); if (forMatch) { const parts = forMatch[1].split(';').map(s => s.trim()); let bodyStmts = []; let braceCount = line.includes('{') ? 1 : 0; let j = i + 1; if (!line.includes('{')) { // Single statement for body if (j < bodyLines.length) { bodyStmts = this.parseMethodBody([bodyLines[j]], startLine + j); } j++; } else { const bodyStart = j; while (j < bodyLines.length && braceCount > 0) { for (const ch of bodyLines[j]) { if (ch === '{') braceCount++; if (ch === '}') braceCount--; } if (braceCount > 0) j++; } bodyStmts = this.parseMethodBody(bodyLines.slice(bodyStart, j), startLine + bodyStart); j++; } stmts.push({ type: 'for', init: parts[0], condition: parts[1], update: parts[2], body: bodyStmts, line: lineNum }); i = j; continue; } // While loop const whileMatch = line.match(/^while\s*\((.+)\)\s*\{?\s*$/); if (whileMatch) { let bodyStmts = []; let braceCount = line.includes('{') ? 1 : 0; let j = i + 1; if (!line.includes('{')) { if (j < bodyLines.length) { bodyStmts = this.parseMethodBody([bodyLines[j]], startLine + j); } j++; } else { const bodyStart = j; while (j < bodyLines.length && braceCount > 0) { for (const ch of bodyLines[j]) { if (ch === '{') braceCount++; if (ch === '}') braceCount--; } if (braceCount > 0) j++; } bodyStmts = this.parseMethodBody(bodyLines.slice(bodyStart, j), startLine + bodyStart); j++; } stmts.push({ type: 'while', condition: whileMatch[1], body: bodyStmts, line: lineNum }); i = j; continue; } // Do-while loop const doMatch = line.match(/^do\s*\{?\s*$/); if (doMatch) { let bodyStmts = []; let braceCount = line.includes('{') ? 1 : 0; let j = i + 1; const bodyStart = j; while (j < bodyLines.length && braceCount > 0) { for (const ch of bodyLines[j]) { if (ch === '{') braceCount++; if (ch === '}') braceCount--; } if (braceCount > 0) j++; } // j is now on the "} while(...)" line const whileCondMatch = bodyLines[j]?.trim().match(/\}\s*while\s*\((.+)\)\s*;/); const cond = whileCondMatch ? whileCondMatch[1] : 'true'; bodyStmts = this.parseMethodBody(bodyLines.slice(bodyStart, j), startLine + bodyStart); stmts.push({ type: 'dowhile', condition: cond, body: bodyStmts, line: lineNum, condLine: startLine + j }); i = j + 1; continue; } // If / else if / else const ifMatch = line.match(/^if\s*\((.+)\)\s*\{?\s*$/); if (ifMatch) { const branches = []; let condition = ifMatch[1]; let braceCount = line.includes('{') ? 1 : 0; let j = i + 1; // Parse if body - stop at the line where closing } is found const bodyStart = j; while (j < bodyLines.length && braceCount > 0) { const bLine = bodyLines[j]; // Check each char but stop if we hit } that closes the if-body for (let ci = 0; ci < bLine.length; ci++) { if (bLine[ci] === '{') braceCount++; if (bLine[ci] === '}') { braceCount--; if (braceCount === 0) break; } } if (braceCount > 0) j++; } branches.push({ condition, body: this.parseMethodBody(bodyLines.slice(bodyStart, j), startLine + bodyStart), line: lineNum }); // Check for else if / else - could be on the same line as closing } const closingLine = bodyLines[j]?.trim() || ''; const elseOnSameLine = closingLine.match(/^}\s*else\s+if\s*\((.+)\)\s*\{?\s*$/) || closingLine.match(/^}\s*else\s*\{?\s*$/); if (elseOnSameLine) { // Process else/else-if from the same line const processElseChain = (startJ) => { let ej = startJ; while (ej < bodyLines.length) { const eLine = bodyLines[ej]?.trim() || ''; const eifMatch = eLine.match(/^}\s*else\s+if\s*\((.+)\)\s*\{?\s*$/) || eLine.match(/^else\s+if\s*\((.+)\)\s*\{?\s*$/); const eMatch = eLine.match(/^}\s*else\s*\{?\s*$/) || eLine.match(/^else\s*\{?\s*$/); if (eifMatch) { const cond = eifMatch[1]; braceCount = eLine.includes('{') ? 1 : 0; ej++; const eBodyStart = ej; while (ej < bodyLines.length && braceCount > 0) { const bl = bodyLines[ej]; for (let ci = 0; ci < bl.length; ci++) { if (bl[ci] === '{') braceCount++; if (bl[ci] === '}') { braceCount--; if (braceCount === 0) break; } } if (braceCount > 0) ej++; } branches.push({ condition: cond, body: this.parseMethodBody(bodyLines.slice(eBodyStart, ej), startLine + eBodyStart), line: startLine + eBodyStart - 1 }); // Check if this closing line also has else const nextClosing = bodyLines[ej]?.trim() || ''; if (!nextClosing.match(/else/)) { ej++; break; } } else if (eMatch) { braceCount = eLine.includes('{') ? 1 : 0; ej++; const eBodyStart = ej; while (ej < bodyLines.length && braceCount > 0) { const bl = bodyLines[ej]; for (let ci = 0; ci < bl.length; ci++) { if (bl[ci] === '{') braceCount++; if (bl[ci] === '}') { braceCount--; if (braceCount === 0) break; } } if (braceCount > 0) ej++; } branches.push({ condition: null, body: this.parseMethodBody(bodyLines.slice(eBodyStart, ej), startLine + eBodyStart), line: startLine + eBodyStart - 1 }); ej++; break; } else { break; } } return ej; }; j = processElseChain(j); } else { j++; // Check next line for standalone else while (j < bodyLines.length) { const nextLine = bodyLines[j]?.trim() || ''; const elseIfMatch2 = nextLine.match(/^else\s+if\s*\((.+)\)\s*\{?\s*$/); const elseMatch2 = nextLine.match(/^else\s*\{?\s*$/); if (elseIfMatch2) { const cond = elseIfMatch2[1]; braceCount = nextLine.includes('{') ? 1 : 0; j++; const eBodyStart = j; while (j < bodyLines.length && braceCount > 0) { const bl = bodyLines[j]; for (let ci = 0; ci < bl.length; ci++) { if (bl[ci] === '{') braceCount++; if (bl[ci] === '}') { braceCount--; if (braceCount === 0) break; } } if (braceCount > 0) j++; } branches.push({ condition: cond, body: this.parseMethodBody(bodyLines.slice(eBodyStart, j), startLine + eBodyStart), line: startLine + eBodyStart - 1 }); j++; } else if (elseMatch2) { braceCount = nextLine.includes('{') ? 1 : 0; j++; const eBodyStart = j; while (j < bodyLines.length && braceCount > 0) { const bl = bodyLines[j]; for (let ci = 0; ci < bl.length; ci++) { if (bl[ci] === '{') braceCount++; if (bl[ci] === '}') { braceCount--; if (braceCount === 0) break; } } if (braceCount > 0) j++; } branches.push({ condition: null, body: this.parseMethodBody(bodyLines.slice(eBodyStart, j), startLine + eBodyStart), line: startLine + eBodyStart - 1 }); j++; break; } else { break; } } } stmts.push({ type: 'if', branches, line: lineNum }); i = j; continue; } // Handle "} else if" or "} else" at start (already consumed by if parser above) if (line.startsWith('} else') || line === '}') { i++; continue; } // Return statement const returnMatch = line.match(/^return\s*(.*?)\s*;$/); if (returnMatch) { stmts.push({ type: 'return', expr: returnMatch[1], line: lineNum }); i++; continue; } // Regular statement (declaration, assignment, method call, etc.) const cleanLine = line.endsWith(';') ? line.slice(0, -1).trim() : line.replace(/[{}]/g, '').trim(); if (cleanLine) { stmts.push({ type: 'statement', code: cleanLine, line: lineNum }); } i++; } return stmts; } evaluateExpression(expr, vars) { if (expr === undefined || expr === null) return undefined; expr = expr.trim(); if (expr === '') return undefined; if (expr === 'true') return true; if (expr === 'false') return false; if (expr === 'null') return null; // String literal — must be a single quoted string, not "str" + expr + "str" if (expr.startsWith('"') && expr.endsWith('"')) { // Find the actual end of the first string literal let endIdx = -1; for (let si = 1; si < expr.length; si++) { if (expr[si] === '\\') { si++; continue; } if (expr[si] === '"') { endIdx = si; break; } } if (endIdx === expr.length - 1) { // The entire expression is one string literal return expr.slice(1, -1).replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"'); } // Otherwise it's something like "str" + expr + "str" — fall through to concat handler } // Char literal if (expr.startsWith("'") && expr.endsWith("'")) { return expr.slice(1, -1); } // Number (including L/l for long, f/F for float, d/D for double suffixes, underscore separators) if (/^-?\d[\d_]*(\.\d[\d_]*)?[lLfFdD]?$/.test(expr)) { const cleanNum = expr.replace(/[lLfFdD]$/, '').replace(/_/g, ''); return cleanNum.includes('.') ? parseFloat(cleanNum) : parseInt(cleanNum); } // Parenthesized expression if (expr.startsWith('(') && this.findMatchingParen(expr, 0) === expr.length - 1) { // Check for cast const inner = expr.slice(1, -1).trim(); const castMatch = inner.match(/^(int|double|float|long|short|byte|char)\)\s*(.+)$/); if (castMatch) { // Actually it's (type) value } return this.evaluateExpression(inner, vars); } // Type cast: (double) expr, (int) expr // Only capture the next token (variable or number), not the whole remaining expression const castMatch = expr.match(/^\((\w+)\)\s*(.+)$/); if (castMatch) { const castType = castMatch[1]; const restExpr = castMatch[2]; // Check if there's an operator after the cast target — e.g. "(double) sum / 5" // In that case, cast only applies to the first operand const opMatch = restExpr.match(/^(\w+(?:\[.+?\])?)\s*([+\-*/%])\s*(.+)$/); if (opMatch) { let castVal = this.evaluateExpression(opMatch[1], vars); if (castType === 'int') castVal = Math.floor(Number(castVal)); else if (castType === 'double' || castType === 'float') castVal = Number(castVal); else if (castType === 'char') castVal = String.fromCharCode(Number(castVal)); const right = this.evaluateExpression(opMatch[3], vars); const op = opMatch[2]; if (op === '+') return castVal + Number(right); if (op === '-') return castVal - Number(right); if (op === '*') return castVal * Number(right); if (op === '/') { if (Number(right) === 0 && Number.isInteger(castVal) && (castType === 'int' || castType === 'long' || castType === 'short' || castType === 'byte')) throw new Error('ArithmeticException: / by zero'); return castVal / Number(right); } if (op === '%') return castVal % Number(right); } const castVal = this.evaluateExpression(restExpr, vars); if (castType === 'int') return Math.floor(Number(castVal)); if (castType === 'double' || castType === 'float') return Number(castVal); if (castType === 'char') return String.fromCharCode(Number(castVal)); return castVal; } // String concatenation with + (handle carefully) // We need to split on + but not inside strings or parens const concatParts = this.splitOnOperator(expr, '+'); if (concatParts.length > 1) { const values = concatParts.map(p => this.evaluateExpression(p.trim(), vars)); if (values.some(v => typeof v === 'string' || (v && typeof v === 'object' && v.__type__))) { return values.map(v => { if (v === null) return 'null'; if (v === undefined) return 'undefined'; if (v && typeof v === 'object' && v.__type__ === 'ArrayList') return '[' + v.items.join(', ') + ']'; if (v && typeof v === 'object' && v.__type__ === 'HashMap') { const entries = [...v.map.entries()].map(([k, val]) => k + '=' + val); return '{' + entries.join(', ') + '}'; } if (v && typeof v === 'object' && v.__type__ === 'StringBuilder') return v.value; if (Array.isArray(v)) return '[' + v.join(', ') + ']'; return String(v); }).join(''); } // Otherwise it's numeric addition - but only if exactly 2 parts or all numeric // For more complex expressions, try to evaluate as math } // Ternary operator const ternaryIdx = this.findTernary(expr); if (ternaryIdx !== -1) { const condition = expr.slice(0, ternaryIdx).trim(); const rest = expr.slice(ternaryIdx + 1); const colonIdx = this.findColon(rest); if (colonIdx !== -1) { const trueExpr = rest.slice(0, colonIdx).trim(); const falseExpr = rest.slice(colonIdx + 1).trim(); return this.evaluateExpression(condition, vars) ? this.evaluateExpression(trueExpr, vars) : this.evaluateExpression(falseExpr, vars); } } // Logical OR const orParts = this.splitOnOperator(expr, '||'); if (orParts.length > 1) { return orParts.some(p => this.evaluateExpression(p.trim(), vars)); } // Logical AND const andParts = this.splitOnOperator(expr, '&&'); if (andParts.length > 1) { return andParts.every(p => this.evaluateExpression(p.trim(), vars)); } // Comparison operators for (const op of ['==', '!=', '<=', '>=', '<', '>']) { const compParts = this.splitOnOperator(expr, op); if (compParts.length === 2) { const left = this.evaluateExpression(compParts[0].trim(), vars); const right = this.evaluateExpression(compParts[1].trim(), vars); switch (op) { case '==': return left === right || left == right; case '!=': return left !== right; case '<': return left < right; case '>': return left > right; case '<=': return left <= right; case '>=': return left >= right; } } } // Addition and subtraction (only if no string concat was detected) for (const op of ['-']) { const parts = this.splitOnOperator(expr, op); if (parts.length >= 2 && parts[0].trim() !== '') { let result = Number(this.evaluateExpression(parts[0].trim(), vars)); for (let pi = 1; pi < parts.length; pi++) { result = result - Number(this.evaluateExpression(parts[pi].trim(), vars)); } return result; } } // Handle + for numbers (if we got here, no string was involved) if (concatParts.length > 1) { const values = concatParts.map(p => this.evaluateExpression(p.trim(), vars)); if (values.every(v => typeof v === 'number')) { return values.reduce((a, b) => a + b, 0); } // Mixed types with + means string concat return values.map(v => v === null ? 'null' : String(v)).join(''); } // Multiplication, division, modulo for (const op of ['*', '/', '%']) { const parts = this.splitOnOperator(expr, op); if (parts.length === 2) { const left = this.evaluateExpression(parts[0].trim(), vars); const right = this.evaluateExpression(parts[1].trim(), vars); if (op === '*') return Number(left) * Number(right); if (op === '/') { // Only integer division by zero throws ArithmeticException // Float division by zero produces Infinity/NaN (IEEE 754) const leftText = parts[0].trim(); const rightText = parts[1].trim(); const hasFloatLiteral = /\d+\.\d*/.test(leftText) || /\d+\.\d*/.test(rightText); if (Number(right) === 0) { if (!hasFloatLiteral && Number.isInteger(Number(left))) { throw new Error('ArithmeticException: / by zero'); } return Number(left) / Number(right); // Infinity or NaN } // Integer division (truncates toward zero) if (!hasFloatLiteral && Number.isInteger(left) && Number.isInteger(right)) return Math.floor(left / right); return Number(left) / Number(right); } if (op === '%') return Number(left) % Number(right); } } // Unary negation if (expr.startsWith('-') && !expr.startsWith('--')) { return -this.evaluateExpression(expr.slice(1), vars); } // Logical NOT if (expr.startsWith('!')) { return !this.evaluateExpression(expr.slice(1), vars); } // new int[] {...} const newArrayMatch = expr.match(/^new\s+(\w+)\s*\[\]\s*\{(.+)\}$/); if (newArrayMatch) { const elements = this.splitOnOperator(newArrayMatch[2], ','); return elements.map(e => this.evaluateExpression(e.trim(), vars)); } // new int[size] const newArraySizeMatch = expr.match(/^new\s+(\w+)\s*\[(.+)\]$/); if (newArraySizeMatch) { const size = this.evaluateExpression(newArraySizeMatch[2], vars); return new Array(size).fill(0); } // Array literal {1, 2, 3} if (expr.startsWith('{') && expr.endsWith('}')) { const elements = this.splitOnOperator(expr.slice(1, -1), ','); return elements.map(e => this.evaluateExpression(e.trim(), vars)); } // Array access: name[index] const arrayAccessMatch = expr.match(/^(\w+)\[(.+)\]$/); if (arrayAccessMatch) { const arrName = arrayAccessMatch[1]; const idx = this.evaluateExpression(arrayAccessMatch[2], vars); const arr = this.getVar(arrName, vars); if (!Array.isArray(arr)) throw new Error(`${arrName} is not an array`); if (idx < 0 || idx >= arr.length) throw new Error(`ArrayIndexOutOfBoundsException: Index ${idx} out of bounds for length ${arr.length}`); return arr[idx]; } // Property access: name.length, name.length() const propMatch = expr.match(/^(\w+(?:\.\w+)?)\.(\w+)(\(\))?$/); if (propMatch && !propMatch[3]) { const objName = propMatch[1]; const prop = propMatch[2]; // Static constants if (objName === 'Math' && prop === 'PI') return Math.PI; if (objName === 'Math' && prop === 'E') return Math.E; if (objName === 'Integer' && prop === 'MAX_VALUE') return 2147483647; if (objName === 'Integer' && prop === 'MIN_VALUE') return -2147483648; if (objName === 'Integer' && prop === 'SIZE') return 32; if (objName === 'Integer' && prop === 'BYTES') return 4; if (objName === 'Long' && prop === 'MAX_VALUE') return 9223372036854775807; if (objName === 'Long' && prop === 'MIN_VALUE') return -9223372036854775808; if (objName === 'Double' && prop === 'MAX_VALUE') return Number.MAX_VALUE; if (objName === 'Double' && prop === 'MIN_VALUE') return Number.MIN_VALUE; if (objName === 'Double' && prop === 'POSITIVE_INFINITY') return Infinity; if (objName === 'Double' && prop === 'NEGATIVE_INFINITY') return -Infinity; if (objName === 'Double' && prop === 'NaN') return NaN; if (objName === 'Float' && prop === 'MAX_VALUE') return 3.4028235e+38; if (objName === 'Float' && prop === 'MIN_VALUE') return 1.4e-45; if (objName === 'Float' && prop === 'SIZE') return 32; if (objName === 'Float' && prop === 'BYTES') return 4; if (objName === 'Byte' && prop === 'MAX_VALUE') return 127; if (objName === 'Byte' && prop === 'MIN_VALUE') return -128; if (objName === 'Byte' && prop === 'SIZE') return 8; if (objName === 'Byte' && prop === 'BYTES') return 1; if (objName === 'Short' && prop === 'MAX_VALUE') return 32767; if (objName === 'Short' && prop === 'MIN_VALUE') return -32768; if (objName === 'Short' && prop === 'SIZE') return 16; if (objName === 'Short' && prop === 'BYTES') return 2; if (objName === 'Long' && prop === 'SIZE') return 64; if (objName === 'Long' && prop === 'BYTES') return 8; if (objName === 'Double' && prop === 'SIZE') return 64; if (objName === 'Double' && prop === 'BYTES') return 8; if (objName === 'Boolean' && prop === 'TRUE') return true; if (objName === 'Boolean' && prop === 'FALSE') return false; // Instance property access const obj = this.evaluateExpression(objName, vars); if (prop === 'length') { if (Array.isArray(obj)) return obj.length; if (typeof obj === 'string') return obj.length; if (obj && typeof obj === 'object' && obj.__type__ === 'StringBuilder') return obj.value.length; } if (prop === 'size' && obj && typeof obj === 'object') { if (obj.__type__ === 'ArrayList') return obj.items.length; if (obj.__type__ === 'HashMap') return obj.map.size; } return undefined; } // Method calls: name.charAt(expr), Math.abs(expr), etc. const methodCallMatch = expr.match(/^(\w+(?:\.\w+)*)\.(\w+)\((.*)?\)$/); if (methodCallMatch) { const obj = methodCallMatch[1]; const method = methodCallMatch[2]; const argsStr = methodCallMatch[3] || ''; const args = argsStr ? this.splitOnOperator(argsStr, ',').map(a => this.evaluateExpression(a.trim(), vars)) : []; // Scanner methods const scannerVal = this.getVar(obj, vars); if (scannerVal === '__scanner__') { if (this.inputIndex >= this.inputQueue.length) { const err = new Error('__INPUT_REQUIRED__'); err.type = 'input_required'; err.inputType = method; err.line = this.currentLine; throw err; } const rawInput = this.inputQueue[this.inputIndex++]; // Append the user's input to the current output line (the prompt) if (this.output.length > 0) { this.output[this.output.length - 1] += rawInput; } else { this.output.push(rawInput); } // Start a new line (simulates user pressing Enter) this.output.push(''); if (method === 'nextInt') return parseInt(rawInput, 10); if (method === 'nextDouble' || method === 'nextFloat') return parseFloat(rawInput); if (method === 'nextLine') return rawInput; if (method === 'next') return rawInput.split(/\s+/)[0] || rawInput; if (method === 'nextBoolean') return rawInput.toLowerCase() === 'true'; return rawInput; } // java.util.Arrays methods if (obj === 'java.util.Arrays' || obj === 'Arrays') { if (method === 'toString') { const arrVal = args[0]; if (Array.isArray(arrVal)) return '[' + arrVal.join(', ') + ']'; return String(arrVal); } if (method === 'sort') { const arrVal = args[0]; if (Array.isArray(arrVal)) { if (args.length === 3) { const sub = arrVal.slice(args[1], args[2]).sort((a,b) => a-b); for (let ci=0;ci typeof a === 'string' ? a.localeCompare(b) : a - b); } return; } if (method === 'fill') { const arrVal = args[0]; if (Array.isArray(arrVal)) { if (args.length === 4) { for (let ci=args[2];ci v === b[i]); return a === b; } if (method === 'deepEquals') { return JSON.stringify(args[0]) === JSON.stringify(args[1]); } if (method === 'binarySearch') { const arrVal = args[0], target = args[1]; if (Array.isArray(arrVal)) { let lo = 0, hi = arrVal.length - 1; while (lo <= hi) { const mid = Math.floor((lo + hi) / 2); if (arrVal[mid] === target) return mid; if (arrVal[mid] < target) lo = mid + 1; else hi = mid - 1; } return -(lo + 1); } return -1; } if (method === 'asList') { return { __type__: 'ArrayList', items: Array.isArray(args[0]) ? [...args[0]] : [...args] }; } } if (obj === 'Math') { // Math methods if (method === 'abs') return Math.abs(args[0]); if (method === 'max') return Math.max(args[0], args[1]); if (method === 'min') return Math.min(args[0], args[1]); if (method === 'sqrt') return Math.sqrt(args[0]); if (method === 'pow') return Math.pow(args[0], args[1]); if (method === 'random') return Math.random(); if (method === 'floor') return Math.floor(args[0]); if (method === 'ceil') return Math.ceil(args[0]); if (method === 'round') return Math.round(args[0]); if (method === 'log') return Math.log(args[0]); if (method === 'log10') return Math.log10(args[0]); if (method === 'sin') return Math.sin(args[0]); if (method === 'cos') return Math.cos(args[0]); if (method === 'tan') return Math.tan(args[0]); if (method === 'asin') return Math.asin(args[0]); if (method === 'acos') return Math.acos(args[0]); if (method === 'atan') return Math.atan(args[0]); if (method === 'atan2') return Math.atan2(args[0], args[1]); if (method === 'toRadians') return args[0] * (Math.PI / 180); if (method === 'toDegrees') return args[0] * (180 / Math.PI); if (method === 'signum') return Math.sign(args[0]); if (method === 'cbrt') return Math.cbrt(args[0]); if (method === 'exp') return Math.exp(args[0]); if (method === 'hypot') return Math.hypot(args[0], args[1]); } // Integer static methods if (obj === 'Integer') { if (method === 'parseInt') return parseInt(args[0], args.length > 1 ? args[1] : 10); if (method === 'valueOf') return parseInt(args[0]); if (method === 'toString') return String(args[0]); if (method === 'toBinaryString') return (args[0] >>> 0).toString(2); if (method === 'toHexString') return (args[0] >>> 0).toString(16); if (method === 'toOctalString') return (args[0] >>> 0).toString(8); if (method === 'compare') return args[0] < args[1] ? -1 : args[0] > args[1] ? 1 : 0; if (method === 'max') return Math.max(args[0], args[1]); if (method === 'min') return Math.min(args[0], args[1]); if (method === 'sum') return args[0] + args[1]; } // Long static methods if (obj === 'Long') { if (method === 'parseLong') return parseInt(args[0]); if (method === 'valueOf') return parseInt(args[0]); if (method === 'toString') return String(args[0]); if (method === 'compare') return args[0] < args[1] ? -1 : args[0] > args[1] ? 1 : 0; } // Double static methods if (obj === 'Double') { if (method === 'parseDouble') return parseFloat(args[0]); if (method === 'valueOf') return parseFloat(args[0]); if (method === 'toString') return String(args[0]); if (method === 'isNaN') return isNaN(args[0]); if (method === 'isInfinite') return !isFinite(args[0]); if (method === 'compare') return args[0] < args[1] ? -1 : args[0] > args[1] ? 1 : 0; } // Character static methods if (obj === 'Character') { if (method === 'isLetter') return /[a-zA-Z]/.test(args[0]); if (method === 'isDigit') return /[0-9]/.test(args[0]); if (method === 'isLetterOrDigit') return /[a-zA-Z0-9]/.test(args[0]); if (method === 'isUpperCase') return args[0] === String(args[0]).toUpperCase() && /[a-zA-Z]/.test(args[0]); if (method === 'isLowerCase') return args[0] === String(args[0]).toLowerCase() && /[a-zA-Z]/.test(args[0]); if (method === 'toUpperCase') return String(args[0]).toUpperCase(); if (method === 'toLowerCase') return String(args[0]).toLowerCase(); if (method === 'isWhitespace') return /\s/.test(args[0]); if (method === 'isAlphabetic') return /[a-zA-Z]/.test(args[0]); if (method === 'compare') return String(args[0]).charCodeAt(0) - String(args[1]).charCodeAt(0); if (method === 'getNumericValue') return parseInt(args[0]); } // String static methods if (obj === 'String') { if (method === 'valueOf') return String(args[0]); if (method === 'join') { const delim = args[0]; const parts = args.slice(1); if (parts.length === 1 && Array.isArray(parts[0])) return parts[0].join(delim); return parts.join(delim); } if (method === 'format') { let fmt = args[0]; let ai = 1; return fmt.replace(/%[dfs%]/g, (m) => { if (m === '%%') return '%'; if (ai < args.length) return String(args[ai++]); return m; }); } } // Collections static methods if (obj === 'Collections') { if (method === 'sort') { const list = args[0]; if (list && list.__type__ === 'ArrayList') list.items.sort((a, b) => typeof a === 'string' ? a.localeCompare(b) : a - b); return; } if (method === 'reverse') { const list = args[0]; if (list && list.__type__ === 'ArrayList') list.items.reverse(); return; } if (method === 'max') { const list = args[0]; if (list && list.__type__ === 'ArrayList') return Math.max(...list.items); return; } if (method === 'min') { const list = args[0]; if (list && list.__type__ === 'ArrayList') return Math.min(...list.items); return; } if (method === 'frequency') { const list = args[0]; const val = args[1]; if (list && list.__type__ === 'ArrayList') return list.items.filter(x => x === val).length; return 0; } if (method === 'swap') { const list = args[0]; if (list && list.__type__ === 'ArrayList') { const temp = list.items[args[1]]; list.items[args[1]] = list.items[args[2]]; list.items[args[2]] = temp; } return; } if (method === 'fill') { const list = args[0]; if (list && list.__type__ === 'ArrayList') list.items.fill(args[1]); return; } if (method === 'unmodifiableList') return args[0]; } // System static methods if (obj === 'System') { if (method === 'currentTimeMillis') return Date.now(); if (method === 'nanoTime') return Date.now() * 1000000; if (method === 'arraycopy') { const src = args[0], srcPos = args[1], dest = args[2], destPos = args[3], len = args[4]; if (Array.isArray(src) && Array.isArray(dest)) { for (let ci = 0; ci < len; ci++) dest[destPos + ci] = src[srcPos + ci]; } return; } if (method === 'exit') return; } // String instance methods const strVal = this.getVar(obj, vars); if (typeof strVal === 'string') { if (method === 'length') return strVal.length; if (method === 'charAt') return strVal.charAt(args[0]); if (method === 'substring') return args.length === 1 ? strVal.substring(args[0]) : strVal.substring(args[0], args[1]); if (method === 'indexOf') return args.length === 1 ? strVal.indexOf(args[0]) : strVal.indexOf(args[0], args[1]); if (method === 'lastIndexOf') return args.length === 1 ? strVal.lastIndexOf(args[0]) : strVal.lastIndexOf(args[0], args[1]); if (method === 'equals') return strVal === args[0]; if (method === 'equalsIgnoreCase') return strVal.toLowerCase() === String(args[0]).toLowerCase(); if (method === 'compareTo') return strVal < args[0] ? -1 : strVal > args[0] ? 1 : 0; if (method === 'compareToIgnoreCase') { const a = strVal.toLowerCase(), b = String(args[0]).toLowerCase(); return a < b ? -1 : a > b ? 1 : 0; } if (method === 'toUpperCase') return strVal.toUpperCase(); if (method === 'toLowerCase') return strVal.toLowerCase(); if (method === 'trim') return strVal.trim(); if (method === 'contains') return strVal.includes(args[0]); if (method === 'replace') return strVal.split(args[0]).join(args[1]); if (method === 'replaceAll') return strVal.replace(new RegExp(args[0], 'g'), args[1]); if (method === 'replaceFirst') return strVal.replace(new RegExp(args[0]), args[1]); if (method === 'startsWith') return strVal.startsWith(args[0]); if (method === 'endsWith') return strVal.endsWith(args[0]); if (method === 'isEmpty') return strVal.length === 0; if (method === 'isBlank') return strVal.trim().length === 0; if (method === 'toCharArray') return [...strVal]; if (method === 'split') return strVal.split(args[0]); if (method === 'concat') return strVal + args[0]; if (method === 'matches') return new RegExp('^' + args[0] + '$').test(strVal); if (method === 'codePointAt') return strVal.codePointAt(args[0]); if (method === 'hashCode') { let h = 0; for (let ci = 0; ci < strVal.length; ci++) h = (Math.imul(31, h) + strVal.charCodeAt(ci)) | 0; return h; } if (method === 'toString') return strVal; if (method === 'valueOf') return strVal; if (method === 'intern') return strVal; if (method === 'getBytes') return [...strVal].map(c => c.charCodeAt(0)); if (method === 'repeat') return strVal.repeat(args[0]); if (method === 'strip') return strVal.trim(); if (method === 'stripLeading') return strVal.replace(/^\s+/, ''); if (method === 'stripTrailing') return strVal.replace(/\s+$/, ''); } // ArrayList instance methods const listVal = this.getVar(obj, vars); if (listVal && typeof listVal === 'object' && listVal.__type__ === 'ArrayList') { if (method === 'add') { if (args.length === 2) { listVal.items.splice(args[0], 0, args[1]); } else { listVal.items.push(args[0]); } this.setVar(obj, listVal, vars, 'ArrayList'); return true; } if (method === 'get') return listVal.items[args[0]]; if (method === 'set') { const old = listVal.items[args[0]]; listVal.items[args[0]] = args[1]; this.setVar(obj, listVal, vars, 'ArrayList'); return old; } if (method === 'remove') { if (typeof args[0] === 'number') { const old = listVal.items.splice(args[0], 1)[0]; this.setVar(obj, listVal, vars, 'ArrayList'); return old; } const idx = listVal.items.indexOf(args[0]); if (idx >= 0) { listVal.items.splice(idx, 1); this.setVar(obj, listVal, vars, 'ArrayList'); return true; } return false; } if (method === 'size') return listVal.items.length; if (method === 'isEmpty') return listVal.items.length === 0; if (method === 'contains') return listVal.items.includes(args[0]); if (method === 'indexOf') return listVal.items.indexOf(args[0]); if (method === 'lastIndexOf') return listVal.items.lastIndexOf(args[0]); if (method === 'clear') { listVal.items = []; this.setVar(obj, listVal, vars, 'ArrayList'); return; } if (method === 'toArray') return [...listVal.items]; if (method === 'subList') return { __type__: 'ArrayList', items: listVal.items.slice(args[0], args[1]) }; if (method === 'addAll') { const other = args[0]; if (other && other.__type__ === 'ArrayList') listVal.items.push(...other.items); else if (Array.isArray(other)) listVal.items.push(...other); this.setVar(obj, listVal, vars, 'ArrayList'); return true; } if (method === 'sort') { listVal.items.sort((a, b) => typeof a === 'string' ? a.localeCompare(b) : a - b); this.setVar(obj, listVal, vars, 'ArrayList'); return; } if (method === 'toString') return '[' + listVal.items.join(', ') + ']'; if (method === 'forEach') return; // no-op for now } // HashMap instance methods const mapVal = this.getVar(obj, vars); if (mapVal && typeof mapVal === 'object' && mapVal.__type__ === 'HashMap') { if (method === 'put') { const old = mapVal.map.get(args[0]); mapVal.map.set(args[0], args[1]); this.setVar(obj, mapVal, vars, 'HashMap'); return old !== undefined ? old : null; } if (method === 'get') { const v = mapVal.map.get(args[0]); return v !== undefined ? v : null; } if (method === 'getOrDefault') { const v = mapVal.map.get(args[0]); return v !== undefined ? v : args[1]; } if (method === 'remove') { const old = mapVal.map.get(args[0]); mapVal.map.delete(args[0]); this.setVar(obj, mapVal, vars, 'HashMap'); return old !== undefined ? old : null; } if (method === 'containsKey') return mapVal.map.has(args[0]); if (method === 'containsValue') { for (const v of mapVal.map.values()) { if (v === args[0]) return true; } return false; } if (method === 'size') return mapVal.map.size; if (method === 'isEmpty') return mapVal.map.size === 0; if (method === 'clear') { mapVal.map.clear(); this.setVar(obj, mapVal, vars, 'HashMap'); return; } if (method === 'keySet') { return { __type__: 'ArrayList', items: [...mapVal.map.keys()] }; } if (method === 'values') { return { __type__: 'ArrayList', items: [...mapVal.map.values()] }; } if (method === 'entrySet') { return { __type__: 'ArrayList', items: [...mapVal.map.entries()].map(([k, v]) => k + '=' + v) }; } if (method === 'putIfAbsent') { if (!mapVal.map.has(args[0])) { mapVal.map.set(args[0], args[1]); this.setVar(obj, mapVal, vars, 'HashMap'); } return mapVal.map.get(args[0]); } if (method === 'replace') { if (mapVal.map.has(args[0])) { const old = mapVal.map.get(args[0]); mapVal.map.set(args[0], args[1]); this.setVar(obj, mapVal, vars, 'HashMap'); return old; } return null; } if (method === 'toString') { const entries = [...mapVal.map.entries()].map(([k, v]) => k + '=' + v); return '{' + entries.join(', ') + '}'; } } // StringBuilder instance methods const sbVal = this.getVar(obj, vars); if (sbVal && typeof sbVal === 'object' && sbVal.__type__ === 'StringBuilder') { if (method === 'append') { sbVal.value += String(args[0]); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'insert') { sbVal.value = sbVal.value.slice(0, args[0]) + String(args[1]) + sbVal.value.slice(args[0]); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'delete') { sbVal.value = sbVal.value.slice(0, args[0]) + sbVal.value.slice(args[1]); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'deleteCharAt') { sbVal.value = sbVal.value.slice(0, args[0]) + sbVal.value.slice(args[0] + 1); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'replace') { sbVal.value = sbVal.value.slice(0, args[0]) + String(args[2]) + sbVal.value.slice(args[1]); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'reverse') { sbVal.value = [...sbVal.value].reverse().join(''); this.setVar(obj, sbVal, vars, 'StringBuilder'); return sbVal; } if (method === 'toString') return sbVal.value; if (method === 'length') return sbVal.value.length; if (method === 'charAt') return sbVal.value.charAt(args[0]); if (method === 'substring') return args.length === 1 ? sbVal.value.substring(args[0]) : sbVal.value.substring(args[0], args[1]); if (method === 'indexOf') return sbVal.value.indexOf(args[0]); if (method === 'capacity') return sbVal.value.length + 16; if (method === 'setCharAt') { const arr = [...sbVal.value]; arr[args[0]] = args[1]; sbVal.value = arr.join(''); this.setVar(obj, sbVal, vars, 'StringBuilder'); return; } } // Number wrapper instance methods (Integer, Double, Long, Float, etc.) const numVal = this.getVar(obj, vars); if (typeof numVal === 'number') { if (method === 'intValue') return Math.floor(numVal); if (method === 'doubleValue') return numVal; if (method === 'floatValue') return numVal; if (method === 'longValue') return Math.floor(numVal); if (method === 'shortValue') return numVal & 0xFFFF; if (method === 'byteValue') return numVal & 0xFF; if (method === 'toString') return String(numVal); if (method === 'compareTo') return numVal < args[0] ? -1 : numVal > args[0] ? 1 : 0; if (method === 'equals') return numVal === args[0]; if (method === 'hashCode') return numVal | 0; } // Boolean wrapper instance methods if (typeof numVal === 'boolean') { if (method === 'booleanValue') return numVal; if (method === 'toString') return String(numVal); if (method === 'compareTo') return numVal === args[0] ? 0 : numVal ? 1 : -1; if (method === 'equals') return numVal === args[0]; } // Array instance methods const arrVal = this.getVar(obj, vars); if (Array.isArray(arrVal)) { if (method === 'length') return arrVal.length; if (method === 'clone') return [...arrVal]; if (method === 'toString') return '[' + arrVal.join(', ') + ']'; } // User-defined method call if (this.methods[method]) { return this.callMethod(method, args, vars); } return undefined; } // Simple function call: methodName(args) const funcCallMatch = expr.match(/^(\w+)\((.*)?\)$/); if (funcCallMatch) { const funcName = funcCallMatch[1]; const argsStr = funcCallMatch[2] || ''; const args = argsStr ? this.splitOnOperator(argsStr, ',').map(a => this.evaluateExpression(a.trim(), vars)) : []; if (this.methods[funcName]) { return this.callMethod(funcName, args, vars); } // Integer.parseInt etc if (funcName === 'Integer' || funcName === 'Double' || funcName === 'Long') { return undefined; } } // Variable reference const varVal = this.getVar(expr, vars); if (varVal !== undefined) return varVal; // If nothing matched, return the expr as string (fallback) return undefined; } callMethod(name, args, callerVars) { const method = this.methods[name]; if (!method) throw new Error(`Method ${name} not found`); const methodVars = {}; method.params.forEach((p, i) => { methodVars[p.name] = { type: p.type, value: args[i] !== undefined ? args[i] : 0 }; }); this.callStack.push({ name, line: this.currentLine, vars: { ...callerVars } }); const methodStmts = this.parseMethodBody(method.bodyLines, method.startLine); let result = undefined; try { result = this.executeBlock(methodStmts, methodVars, true); } catch (e) { if (e.type === 'return') { result = e.value; } else { throw e; } } this.callStack.pop(); return result; } getVar(name, vars) { if (vars[name] !== undefined) { return typeof vars[name] === 'object' && vars[name] !== null && 'value' in vars[name] ? vars[name].value : vars[name]; } return undefined; } setVar(name, value, vars, type) { if (vars[name] !== undefined && typeof vars[name] === 'object' && vars[name] !== null && 'value' in vars[name]) { const prevVal = vars[name].value; vars[name].value = value; vars[name].changed = true; vars[name].prevValue = prevVal; } else { vars[name] = { type: type || 'var', value, changed: true }; } } toJavaString(v) { if (v === null || v === undefined) return 'null'; if (v && typeof v === 'object' && v.__type__ === 'ArrayList') return '[' + v.items.join(', ') + ']'; if (v && typeof v === 'object' && v.__type__ === 'HashMap') { const entries = [...v.map.entries()].map(([k, val]) => k + '=' + val); return '{' + entries.join(', ') + '}'; } if (v && typeof v === 'object' && v.__type__ === 'StringBuilder') return v.value; if (Array.isArray(v)) return '[' + v.join(', ') + ']'; return String(v); } stripInlineComment(line) { // Remove // comments that are NOT inside string literals let inString = false; let stringChar = ''; for (let ci = 0; ci < line.length - 1; ci++) { const ch = line[ci]; if (inString) { if (ch === '\\') { ci++; continue; } if (ch === stringChar) inString = false; } else { if (ch === '"' || ch === "'") { inString = true; stringChar = ch; } else if (ch === '/' && line[ci + 1] === '/') { return line.slice(0, ci).trim(); } } } return line; } splitOnOperator(expr, op) { const parts = []; let depth = 0; let inString = false; let stringChar = ''; let current = ''; for (let i = 0; i < expr.length; i++) { const ch = expr[i]; if (inString) { current += ch; if (ch === '\\') { current += expr[++i] || ''; continue; } if (ch === stringChar) inString = false; continue; } if (ch === '"' || ch === "'") { inString = true; stringChar = ch; current += ch; continue; } if (ch === '(' || ch === '[' || ch === '{') { depth++; current += ch; continue; } if (ch === ')' || ch === ']' || ch === '}') { depth--; current += ch; continue; } if (depth === 0 && expr.substring(i, i + op.length) === op) { // For < and >, don't match <= or >= if ((op === '<' || op === '>') && expr[i + 1] === '=') { current += ch; continue; } // For = don't match == if (op === '=' && expr[i + 1] === '=') { current += ch; continue; } // For ! don't match != if (op === '!' && expr[i + 1] === '=') { current += ch; continue; } // For + don't match +=, for - don't match -= if ((op === '+' || op === '-') && expr[i + 1] === '=') { current += ch; continue; } // For + don't match ++, for - don't match -- if ((op === '+' && expr[i + 1] === '+') || (op === '-' && expr[i + 1] === '-')) { current += ch; continue; } parts.push(current); current = ''; i += op.length - 1; continue; } current += ch; } parts.push(current); return parts; } findMatchingParen(expr, start) { let depth = 0; for (let i = start; i < expr.length; i++) { if (expr[i] === '(') depth++; if (expr[i] === ')') { depth--; if (depth === 0) return i; } } return -1; } findTernary(expr) { let depth = 0; let inString = false; for (let i = 0; i < expr.length; i++) { if (expr[i] === '"' || expr[i] === "'") { inString = !inString; continue; } if (inString) continue; if (expr[i] === '(' || expr[i] === '[') depth++; if (expr[i] === ')' || expr[i] === ']') depth--; if (depth === 0 && expr[i] === '?') return i; } return -1; } findColon(expr) { let depth = 0; let inString = false; for (let i = 0; i < expr.length; i++) { if (expr[i] === '"' || expr[i] === "'") { inString = !inString; continue; } if (inString) continue; if (expr[i] === '(' || expr[i] === '[') depth++; if (expr[i] === ')' || expr[i] === ']') depth--; if (depth === 0 && expr[i] === ':') return i; } return -1; } executeStatement(stmt, vars) { const code = stmt.code; // Skip import statements if (code.match(/^import\s+/)) return; // Scanner declaration: Scanner scanner = new Scanner(System.in) — treat as no-op if (code.match(/^Scanner\s+\w+\s*=\s*new\s+Scanner\s*\(\s*System\.in\s*\)/)) { const nameMatch = code.match(/^Scanner\s+(\w+)/); if (nameMatch) this.setVar(nameMatch[1], '__scanner__', vars, 'Scanner'); return; } // ArrayList declaration: ArrayList name = new ArrayList<>() or new ArrayList<>(initialCollection) const alDeclMatch = code.match(/^(?:ArrayList|List)\s*<[^>]*>\s+(\w+)\s*=\s*new\s+ArrayList\s*<[^>]*>\s*\((.*)?\)\s*$/); if (alDeclMatch) { let items = []; const initExpr = (alDeclMatch[2] || '').trim(); if (initExpr) { const initVal = this.evaluateExpression(initExpr, vars); if (initVal && typeof initVal === 'object' && initVal.__type__ === 'ArrayList') { items = [...initVal.items]; } else if (Array.isArray(initVal)) { items = [...initVal]; } } this.setVar(alDeclMatch[1], { __type__: 'ArrayList', items }, vars, 'ArrayList'); return; } // List declaration via Arrays.asList directly: List name = Arrays.asList(...) const listAsListMatch = code.match(/^(?:List)\s*<[^>]*>\s+(\w+)\s*=\s*(Arrays\.asList\(.+\))$/); if (listAsListMatch) { const initVal = this.evaluateExpression(listAsListMatch[2], vars); if (initVal && typeof initVal === 'object' && initVal.__type__ === 'ArrayList') { this.setVar(listAsListMatch[1], initVal, vars, 'ArrayList'); } return; } // HashMap declaration: HashMap name = new HashMap<>() const hmMatch = code.match(/^(?:HashMap|Map)\s*<[^>]*>\s+(\w+)\s*=\s*new\s+HashMap\s*<[^>]*>\s*\(\s*\)/); if (hmMatch) { this.setVar(hmMatch[1], { __type__: 'HashMap', map: new Map() }, vars, 'HashMap'); return; } // Set/Collection/List declaration from method result: Set name = expr; Collection name = expr; const collectionDeclMatch = code.match(/^(?:Set|Collection|List)\s*<[^>]*>\s+(\w+)\s*=\s*(.+)$/); if (collectionDeclMatch) { const val = this.evaluateExpression(collectionDeclMatch[2], vars); this.setVar(collectionDeclMatch[1], val, vars, 'Collection'); return; } // StringBuilder declaration const sbMatch = code.match(/^StringBuilder\s+(\w+)\s*=\s*new\s+StringBuilder\s*\(\s*(.*?)\s*\)/); if (sbMatch) { const initVal = sbMatch[2] ? this.evaluateExpression(sbMatch[2], vars) : ''; this.setVar(sbMatch[1], { __type__: 'StringBuilder', value: String(initVal || '') }, vars, 'StringBuilder'); return; } // scanner.close() and other no-op close calls if (code.match(/^\w+\.close\(\)$/)) return; // System.out.printf — formatted print const printfMatch = code.match(/^System\.out\.printf\((.+)\)$/); if (printfMatch) { const argsStr = printfMatch[1]; const args = this.splitOnOperator(argsStr, ',').map(a => this.evaluateExpression(a.trim(), vars)); let fmt = String(args[0]); let ai = 1; const result = fmt.replace(/%[dfsn%]/g, (m) => { if (m === '%%') return '%'; if (m === '%n') return '\n'; if (ai < args.length) return String(args[ai++]); return m; }); // printf doesn't add newline, split on \n for multi-line const lines = result.split('\n'); lines.forEach((line, idx) => { if (idx === 0) { if (this.output.length > 0) { this.output[this.output.length - 1] += line; } else { this.output.push(line); } } else { this.output.push(line); } }); return; } // Standalone method calls on objects: list.add(x), map.put(k,v), sb.append(x), Collections.sort(list) const stmtMethodMatch = code.match(/^(\w+(?:\.\w+)*)\.(\w+)\((.*)?\)$/); if (stmtMethodMatch) { const objName = stmtMethodMatch[1]; const methodName = stmtMethodMatch[2]; // Check if it's a known object method (not System.out.println which is handled elsewhere) if (objName !== 'System.out') { const val = this.getVar(objName, vars); if (val && typeof val === 'object' && (val.__type__ === 'ArrayList' || val.__type__ === 'HashMap' || val.__type__ === 'StringBuilder')) { this.evaluateExpression(code, vars); return; } // Static method calls: Collections.sort, Arrays.fill, etc. if (['Collections', 'Arrays', 'System'].includes(objName)) { this.evaluateExpression(code, vars); return; } } } // Variable declaration with initialization // int x = 5; or int[] arr = {1,2,3}; or String s = "hello"; const declMatch = code.match(/^(int|double|boolean|char|String|float|long|short|byte|Integer|Double|Long|Float|Short|Byte|Boolean|Character|Object|var)(\[\])?\s+(\w+)\s*=\s*(.+)$/); if (declMatch) { const type = declMatch[1] + (declMatch[2] || ''); const name = declMatch[3]; const valueExpr = declMatch[4]; const value = this.evaluateExpression(valueExpr, vars); this.setVar(name, value, vars, type); return; } // Variable declaration without initialization const declOnlyMatch = code.match(/^(int|double|boolean|char|String|float|long|short|byte|Integer|Double|Long|Float|Short|Byte|Boolean|Character|Object|var)(\[\])?\s+(\w+)\s*$/); if (declOnlyMatch) { const type = declOnlyMatch[1] + (declOnlyMatch[2] || ''); const name = declOnlyMatch[3]; const defaults = { 'int': 0, 'double': 0.0, 'boolean': false, 'char': '\0', 'String': null, 'float': 0.0, 'long': 0, 'short': 0, 'byte': 0, 'Integer': null, 'Double': null, 'Long': null, 'Float': null, 'Short': null, 'Byte': null, 'Boolean': null, 'Character': null, 'Object': null, 'var': null }; this.setVar(name, defaults[declOnlyMatch[1]] ?? null, vars, type); return; } // Array access assignment: arr[i] = value const arrAssignMatch = code.match(/^(\w+)\[(.+)\]\s*=\s*(.+)$/); if (arrAssignMatch) { const arrName = arrAssignMatch[1]; const idx = this.evaluateExpression(arrAssignMatch[2], vars); const value = this.evaluateExpression(arrAssignMatch[3], vars); const arr = this.getVar(arrName, vars); if (!Array.isArray(arr)) throw new Error(`${arrName} is not an array`); if (idx < 0 || idx >= arr.length) throw new Error(`ArrayIndexOutOfBoundsException: Index ${idx} out of bounds for length ${arr.length}`); const oldArr = [...arr]; arr[idx] = value; if (vars[arrName] && typeof vars[arrName] === 'object' && 'value' in vars[arrName]) { vars[arrName].changed = true; vars[arrName].changedIndex = idx; vars[arrName].prevValue = oldArr; } return; } // Compound assignment: x += 5, x -= 3, etc. const compoundMatch = code.match(/^(\w+)\s*(\+=|-=|\*=|\/=|%=)\s*(.+)$/); if (compoundMatch) { const name = compoundMatch[1]; const op = compoundMatch[2]; const right = this.evaluateExpression(compoundMatch[3], vars); const current = this.getVar(name, vars); let newVal; switch (op) { case '+=': newVal = typeof current === 'string' ? current + String(right) : current + right; break; case '-=': newVal = current - right; break; case '*=': newVal = current * right; break; case '/=': newVal = current / right; break; case '%=': newVal = current % right; break; } this.setVar(name, newVal, vars); return; } // Increment/decrement: i++ or i-- const incDecMatch = code.match(/^(\w+)(\+\+|--)$/); if (incDecMatch) { const name = incDecMatch[1]; const current = this.getVar(name, vars); this.setVar(name, incDecMatch[2] === '++' ? current + 1 : current - 1, vars); return; } // Pre increment/decrement const preIncDecMatch = code.match(/^(\+\+|--)(\w+)$/); if (preIncDecMatch) { const name = preIncDecMatch[2]; const current = this.getVar(name, vars); this.setVar(name, preIncDecMatch[1] === '++' ? current + 1 : current - 1, vars); return; } // Simple assignment: x = value const assignMatch = code.match(/^(\w+)\s*=\s*(.+)$/); if (assignMatch) { const name = assignMatch[1]; const value = this.evaluateExpression(assignMatch[2], vars); this.setVar(name, value, vars); return; } // System.out.println / System.out.print const printlnMatch = code.match(/^System\.out\.println\((.*)?\)$/); if (printlnMatch) { const arg = printlnMatch[1]; const value = arg ? this.evaluateExpression(arg, vars) : ''; this.output.push(this.toJavaString(value)); return; } const printMatch = code.match(/^System\.out\.print\((.*)?\)$/); if (printMatch) { const arg = printMatch[1]; const value = arg ? this.evaluateExpression(arg, vars) : ''; if (this.output.length > 0) { this.output[this.output.length - 1] += this.toJavaString(value); } else { this.output.push(this.toJavaString(value)); } return; } // Method call as statement const methodStmtMatch = code.match(/^(\w+)\((.*)?\)$/); if (methodStmtMatch) { const funcName = methodStmtMatch[1]; const argsStr = methodStmtMatch[2] || ''; const args = argsStr ? this.splitOnOperator(argsStr, ',').map(a => this.evaluateExpression(a.trim(), vars)) : []; if (this.methods[funcName]) { this.callMethod(funcName, args, vars); return; } } // Object method call as statement: obj.method(args) const objMethodMatch = code.match(/^(\w+(?:\.\w+)*)\.(\w+)\((.*)?\)$/); if (objMethodMatch) { this.evaluateExpression(code, vars); return; } } executeBlock(stmts, vars, isMethod = false) { for (const stmt of stmts) { if (this.totalSteps >= this.maxIterations) { throw new Error('Execution limit reached (possible infinite loop)'); } const result = this.executeNode(stmt, vars); if (result && result.type === 'return') { if (isMethod) { const err = new Error('return'); err.type = 'return'; err.value = result.value; throw err; } return result; } } return undefined; } executeNode(node, vars) { this.totalSteps++; if (this.totalSteps >= this.maxIterations) { throw new Error('Execution limit reached (possible infinite loop)'); } switch (node.type) { case 'statement': { this.currentLine = node.line; this.executedLines.add(node.line); // Clear change flags Object.values(vars).forEach(v => { if (typeof v === 'object' && v !== null && 'changed' in v) { v.changed = false; delete v.changedIndex; } }); this.executeStatement(node, vars); this.saveState(vars, node.line); break; } case 'for': { // Execute init this.currentLine = node.line; this.executedLines.add(node.line); Object.values(vars).forEach(v => { if (typeof v === 'object' && v !== null && 'changed' in v) { v.changed = false; delete v.changedIndex; } }); this.executeStatement({ code: node.init }, vars); this.saveState(vars, node.line, { loopType: 'for', iteration: 0 }); let iteration = 0; while (this.evaluateExpression(node.condition, vars)) { iteration++; if (iteration > this.maxIterations) throw new Error('Execution limit reached (possible infinite loop)'); for (const bodyStmt of node.body) { const result = this.executeNode(bodyStmt, vars); if (result && result.type === 'return') return result; } // Execute update this.currentLine = node.line; this.executedLines.add(node.line); Object.values(vars).forEach(v => { if (typeof v === 'object' && v !== null && 'changed' in v) { v.changed = false; delete v.changedIndex; } }); this.executeStatement({ code: node.update }, vars); this.saveState(vars, node.line, { loopType: 'for', iteration }); } break; } case 'while': { let iteration = 0; while (this.evaluateExpression(node.condition, vars)) { iteration++; this.currentLine = node.line; this.executedLines.add(node.line); if (iteration > this.maxIterations) throw new Error('Execution limit reached (possible infinite loop)'); this.saveState(vars, node.line, { loopType: 'while', iteration }); for (const bodyStmt of node.body) { const result = this.executeNode(bodyStmt, vars); if (result && result.type === 'return') return result; } } // Record the failed condition check this.currentLine = node.line; this.executedLines.add(node.line); this.saveState(vars, node.line, { loopType: 'while', iteration, done: true }); break; } case 'dowhile': { let iteration = 0; do { iteration++; this.currentLine = node.line; this.executedLines.add(node.line); if (iteration > this.maxIterations) throw new Error('Execution limit reached (possible infinite loop)'); this.saveState(vars, node.line, { loopType: 'do-while', iteration }); for (const bodyStmt of node.body) { const result = this.executeNode(bodyStmt, vars); if (result && result.type === 'return') return result; } } while (this.evaluateExpression(node.condition, vars)); break; } case 'if': { for (const branch of node.branches) { this.currentLine = branch.line; this.executedLines.add(branch.line); if (branch.condition === null || this.evaluateExpression(branch.condition, vars)) { this.saveState(vars, branch.line, { branch: branch.condition ? 'true' : 'else' }); for (const bodyStmt of branch.body) { const result = this.executeNode(bodyStmt, vars); if (result && result.type === 'return') return result; } break; } else { this.saveState(vars, branch.line, { branch: 'false' }); } } break; } case 'return': { this.currentLine = node.line; this.executedLines.add(node.line); const value = node.expr ? this.evaluateExpression(node.expr, vars) : undefined; this.saveState(vars, node.line); return { type: 'return', value }; } } return undefined; } saveState(vars, line, meta = {}) { const varSnapshot = {}; for (const [name, val] of Object.entries(vars)) { if (typeof val === 'object' && val !== null && 'value' in val) { let clonedValue = val.value; if (Array.isArray(val.value)) { clonedValue = [...val.value]; } else if (val.value && typeof val.value === 'object') { if (val.value.__type__ === 'ArrayList') { clonedValue = { __type__: 'ArrayList', items: [...val.value.items] }; } else if (val.value.__type__ === 'HashMap') { clonedValue = { __type__: 'HashMap', map: new Map(val.value.map) }; } else if (val.value.__type__ === 'StringBuilder') { clonedValue = { __type__: 'StringBuilder', value: val.value.value }; } } varSnapshot[name] = { type: val.type, value: clonedValue, changed: val.changed || false, prevValue: val.prevValue, changedIndex: val.changedIndex }; } } this.states.push({ line, vars: varSnapshot, output: [...this.output], callStack: this.callStack.map(f => ({ ...f })), executedLines: new Set(this.executedLines), meta }); } compile(code) { try { // Basic syntax checks const lines = code.split('\n'); let braceCount = 0; let parenCount = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const ch of line) { if (ch === '{') braceCount++; if (ch === '}') braceCount--; if (ch === '(') parenCount++; if (ch === ')') parenCount--; } } if (braceCount !== 0) { return { success: false, error: `Mismatched braces: ${braceCount > 0 ? 'missing closing }' : 'extra closing }'}`, line: null }; } if (parenCount !== 0) { return { success: false, error: `Mismatched parentheses`, line: null }; } // Try to parse this.parse(code); return { success: true }; } catch (e) { return { success: false, error: e.message, line: null }; } } run(code, inputQueue = []) { this.reset(); const compileResult = this.compile(code); if (!compileResult.success) { return { success: false, error: compileResult.error, states: [], output: [] }; } this.reset(); // Set input AFTER all resets (parse() inside compile also calls reset) this.inputQueue = inputQueue; this.inputIndex = 0; try { const stmts = this.parse(code); // Restore input after parse's reset this.inputQueue = inputQueue; this.inputIndex = 0; const vars = {}; this.executeBlock(stmts, vars); return { success: true, states: this.states, output: this.output, finalVars: this.states.length > 0 ? this.states[this.states.length - 1].vars : {} }; } catch (e) { if (e.type === 'input_required') { return { success: false, waiting: true, inputType: e.inputType, inputLine: e.line, states: this.states, output: this.output, error: null }; } return { success: false, error: e.message, states: this.states, output: this.output, errorLine: this.currentLine }; } } } // ============================================================ // EXAMPLE PROGRAMS // ============================================================ const EXAMPLES = { 'Array Sum & Average': `public class ArraySumAverage { public static void main(String[] args) { // Variable declarations int sum = 0; int[] numbers = {3, 7, 2, 8, 1}; // Loop through array for (int i = 0; i < numbers.length; i++) { sum += numbers[i]; System.out.println("Added " + numbers[i] + ", sum = " + sum); } // Calculate average double average = (double) sum / numbers.length; System.out.println("Average: " + average); // Conditional check if (average > 4.0) { System.out.println("Above threshold!"); } else { System.out.println("Below threshold."); } } }`, 'Bubble Sort': `public class BubbleSort { public static void main(String[] args) { int[] arr = {64, 34, 25, 12, 22, 11, 90}; int n = arr.length; System.out.println("Unsorted array:"); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // Swap int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } System.out.println("Sorted!"); for (int i = 0; i < n; i++) { System.out.println("arr[" + i + "] = " + arr[i]); } } }`, 'Fibonacci (Recursive)': `public class Fibonacci { public static int fibonacci(int n) { if (n <= 1) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); } public static void main(String[] args) { int count = 3; System.out.println("Fibonacci sequence:"); for (int i = 0; i < count; i++) { int result = fibonacci(i); System.out.println("fibonacci(" + i + ") = " + result); } } }`, 'Factorial (Recursive)': `public class Factorial { public static int factorial(int n) { if (n <= 1) { return 1; } return n * factorial(n - 1); } public static void main(String[] args) { for (int i = 1; i <= 7; i++) { int result = factorial(i); System.out.println(i + "! = " + result); } } }`, 'Binary Search': `public class BinarySearch { public static void main(String[] args) { int[] arr = {2, 5, 8, 12, 16, 23, 38, 45, 67, 91}; int target = 23; int low = 0; int high = arr.length - 1; int result = -1; System.out.println("Searching for " + target); while (low <= high) { int mid = (low + high) / 2; System.out.println("Checking index " + mid + " = " + arr[mid]); if (arr[mid] == target) { result = mid; System.out.println("Found at index " + mid + "!"); low = high + 1; } else if (arr[mid] < target) { low = mid + 1; } else { high = mid - 1; } } if (result == -1) { System.out.println("Not found."); } } }`, 'String Reversal': `public class StringReversal { public static void main(String[] args) { String original = "Hello, World!"; String reversed = ""; int len = original.length(); System.out.println("Original: " + original); for (int i = len - 1; i >= 0; i--) { char c = original.charAt(i); reversed = reversed + c; System.out.println("Step: " + reversed); } System.out.println("Reversed: " + reversed); } }`, 'FizzBuzz': `public class FizzBuzz { public static void main(String[] args) { for (int i = 1; i <= 20; i++) { if (i % 3 == 0 && i % 5 == 0) { System.out.println(i + ": FizzBuzz"); } else if (i % 3 == 0) { System.out.println(i + ": Fizz"); } else if (i % 5 == 0) { System.out.println(i + ": Buzz"); } else { System.out.println(i + ": " + i); } } } }`, 'Selection Sort': `public class SelectionSort { public static void main(String[] args) { int[] arr = {29, 10, 14, 37, 13}; int n = arr.length; for (int i = 0; i < n - 1; i++) { int minIdx = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIdx]) { minIdx = j; } } // Swap int temp = arr[minIdx]; arr[minIdx] = arr[i]; arr[i] = temp; System.out.println("After pass " + i + ": placed " + arr[i]); } System.out.println("Sorted!"); for (int i = 0; i < n; i++) { System.out.print(arr[i] + " "); } System.out.println(""); } }`, 'Grade Calculator (Scanner)': `import java.util.Scanner; public class GradeCalculator { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("Enter student name: "); String name = scanner.nextLine(); System.out.print("Enter number of subjects: "); int subjects = scanner.nextInt(); int total = 0; for (int i = 1; i <= subjects; i++) { System.out.print("Enter marks for subject " + i + ": "); int marks = scanner.nextInt(); total += marks; } System.out.println("--- Report Card ---"); System.out.println("Student: " + name); System.out.println("Total: " + total); System.out.println("Average: " + (double) total / subjects); double average = (double) total / subjects; if (average >= 90) { System.out.println("Grade: A+"); } else if (average >= 80) { System.out.println("Grade: A"); } else if (average >= 70) { System.out.println("Grade: B"); } else if (average >= 60) { System.out.println("Grade: C"); } else if (average >= 50) { System.out.println("Grade: D"); } else { System.out.println("Grade: F"); } scanner.close(); } }` }; // ============================================================ // THEME DEFINITIONS // ============================================================ const THEMES = { dark: { // Backgrounds bg: '#0d1117', bgDeep: '#010409', bgCard: '#161b22', bgHover: '#1f2937', // Borders border: '#21262d', borderHover: '#484f58', borderActive: '#30363d', // Text text: '#c9d1d9', textBright: '#f0f6fc', textMuted: '#8b949e', textDim: '#484f58', // Accent accent: '#58a6ff', accentBg: '#1f6feb', accentHover: '#388bfd', accentGradient: 'linear-gradient(135deg, #1f6feb 0%, #8b5cf6 100%)', progressGradient: 'linear-gradient(90deg, #1f6feb, #8b5cf6)', // Semantic green: '#3fb950', red: '#f85149', orange: '#f0883e', purple: '#d2a8ff', caret: '#58a6ff', // Syntax highlighting tokKeyword: '#ff7b72', tokType: '#79c0ff', tokBuiltin: '#d2a8ff', tokString: '#a5d6ff', tokNumber: '#79c0ff', tokComment: '#8b949e', // Opacity overlays currentLineBg: 'rgba(56,139,253,0.12)', errorLineBg: 'rgba(248,81,73,0.1)', greenGlow: 'rgba(63,185,80,0.15)', greenFlash: 'rgba(63,185,80,0.25)', orangeOverlay: 'rgba(240,136,62,0.15)', orangeBorder: 'rgba(240,136,62,0.3)', errorOverlayBg: 'rgba(248,81,73,0.08)', errorOverlayBorder: 'rgba(248,81,73,0.2)', successOverlayBg: 'rgba(63,185,80,0.08)', successOverlayBorder: 'rgba(63,185,80,0.2)', consoleBg: '#010409', dropShadow: '0 8px 24px rgba(0,0,0,0.4)', // Toggle toggleBg: '#161b22', toggleBorder: '#30363d', toggleIcon: '#f0c75e', }, light: { // Backgrounds bg: '#ffffff', bgDeep: '#f6f8fa', bgCard: '#f0f3f6', bgHover: '#e8ecf0', // Borders border: '#d0d7de', borderHover: '#afb8c1', borderActive: '#d0d7de', // Text text: '#24292f', textBright: '#1b1f24', textMuted: '#57606a', textDim: '#8b949e', // Accent accent: '#0969da', accentBg: '#0969da', accentHover: '#0550ae', accentGradient: 'linear-gradient(135deg, #0969da 0%, #8250df 100%)', progressGradient: 'linear-gradient(90deg, #0969da, #8250df)', // Semantic green: '#1a7f37', red: '#cf222e', orange: '#bc4c00', purple: '#8250df', caret: '#0969da', // Syntax highlighting tokKeyword: '#cf222e', tokType: '#0550ae', tokBuiltin: '#8250df', tokString: '#0a3069', tokNumber: '#0550ae', tokComment: '#6e7781', // Opacity overlays currentLineBg: 'rgba(9,105,218,0.08)', errorLineBg: 'rgba(207,34,46,0.06)', greenGlow: 'rgba(26,127,55,0.12)', greenFlash: 'rgba(26,127,55,0.2)', orangeOverlay: 'rgba(188,76,0,0.08)', orangeBorder: 'rgba(188,76,0,0.25)', errorOverlayBg: 'rgba(207,34,46,0.06)', errorOverlayBorder: 'rgba(207,34,46,0.15)', successOverlayBg: 'rgba(26,127,55,0.06)', successOverlayBorder: 'rgba(26,127,55,0.15)', consoleBg: '#f6f8fa', dropShadow: '0 8px 24px rgba(0,0,0,0.12)', // Toggle toggleBg: '#f0f3f6', toggleBorder: '#d0d7de', toggleIcon: '#0969da', } }; // ============================================================ // SYNTAX HIGHLIGHTER // ============================================================ function highlightJava(code) { const keywords = /\b(public|private|protected|static|void|class|interface|extends|implements|new|return|if|else|for|while|do|switch|case|break|continue|default|try|catch|finally|throw|throws|import|package|this|super|final|abstract|native|synchronized|volatile|transient|strictfp|assert|enum|instanceof)\b/g; const types = /\b(int|double|boolean|char|String|float|long|short|byte|Integer|Double|Boolean|Character|Object|void)\b/g; const builtins = /\b(System|Math|out|println|print|length|charAt|null|true|false)\b/g; const numbers = /\b(\d+\.?\d*)\b/g; const strings = /("(?:[^"\\]|\\.)*")/g; const chars = /('(?:[^'\\]|\\.)*')/g; const comments = /(\/\/.*$|\/\*[\s\S]*?\*\/)/gm; // Process in correct order to avoid double-highlighting let result = code; const tokens = []; let id = 0; // Replace strings first result = result.replace(strings, (m) => { const key = `__STR${id++}__`; tokens.push({ key, html: `${escapeHtml(m)}` }); return key; }); result = result.replace(chars, (m) => { const key = `__CHR${id++}__`; tokens.push({ key, html: `${escapeHtml(m)}` }); return key; }); result = result.replace(comments, (m) => { const key = `__CMT${id++}__`; tokens.push({ key, html: `${escapeHtml(m)}` }); return key; }); result = escapeHtml(result); result = result.replace(keywords, '$1'); result = result.replace(types, '$1'); result = result.replace(builtins, '$1'); result = result.replace(numbers, '$1'); // Restore tokens for (const t of tokens) { result = result.replace(t.key, t.html); } return result; } function escapeHtml(s) { return s.replace(/&/g, '&').replace(//g, '>'); } // ============================================================ // MAIN COMPONENT // ============================================================ export default function JavaCodeVisualizer() { const [code, setCode] = useState(EXAMPLES['Array Sum & Average']); const codeHistoryRef = useRef([EXAMPLES['Array Sum & Average']]); const historyIndexRef = useRef(0); const isUndoRedoRef = useRef(false); // Wrapper to track code changes for undo/redo const updateCode = useCallback((newCode) => { if (isUndoRedoRef.current) { isUndoRedoRef.current = false; setCode(newCode); return; } // Trim future history if we're not at the end const history = codeHistoryRef.current; const idx = historyIndexRef.current; codeHistoryRef.current = history.slice(0, idx + 1); codeHistoryRef.current.push(newCode); // Cap history at 100 entries if (codeHistoryRef.current.length > 100) { codeHistoryRef.current = codeHistoryRef.current.slice(-100); } historyIndexRef.current = codeHistoryRef.current.length - 1; setCode(newCode); }, []); const handleUndo = useCallback(() => { if (historyIndexRef.current > 0) { historyIndexRef.current--; isUndoRedoRef.current = true; setCode(codeHistoryRef.current[historyIndexRef.current]); } }, []); const handleRedo = useCallback(() => { if (historyIndexRef.current < codeHistoryRef.current.length - 1) { historyIndexRef.current++; isUndoRedoRef.current = true; setCode(codeHistoryRef.current[historyIndexRef.current]); } }, []); const [states, setStates] = useState([]); const [currentStep, setCurrentStep] = useState(-1); const [isRunning, setIsRunning] = useState(false); const [isAutoPlaying, setIsAutoPlaying] = useState(false); const [speed, setSpeed] = useState(500); const [activeTab, setActiveTab] = useState('variables'); const [compileLog, setCompileLog] = useState([]); const [errorLine, setErrorLine] = useState(null); const [breakpoints, setBreakpoints] = useState(new Set()); const [showExamples, setShowExamples] = useState(false); const [compiledSuccess, setCompiledSuccess] = useState(null); const [isDark, setIsDark] = useState(true); const [localFiles, setLocalFiles] = useState({}); const [activeFileName, setActiveFileName] = useState(null); const [userInputs, setUserInputs] = useState([]); const [waitingForInput, setWaitingForInput] = useState(false); const [inputPromptType, setInputPromptType] = useState(''); const [currentInput, setCurrentInput] = useState(''); const [cursorPos, setCursorPos] = useState({ line: 1, col: 1 }); const [isFullscreen, setIsFullscreen] = useState(false); const inputRef = useRef(null); const appRef = useRef(null); const t = isDark ? THEMES.dark : THEMES.light; const handleReset = useCallback(() => { setStates([]); setCurrentStep(-1); setIsRunning(false); setIsAutoPlaying(false); setCompileLog([]); setCompiledSuccess(null); setErrorLine(null); setUserInputs([]); setWaitingForInput(false); setInputPromptType(''); setCurrentInput(''); }, []); // Load individual .java files const handleLoadFiles = useCallback(async () => { setShowExamples(false); try { if (window.showOpenFilePicker) { const handles = await window.showOpenFilePicker({ multiple: true, types: [{ description: 'Java Files', accept: { 'text/java': ['.java'] } }] }); const files = { ...localFiles }; for (const handle of handles) { const file = await handle.getFile(); if (file.name.endsWith('.java')) { files[file.name] = await file.text(); } } setLocalFiles(files); const lastName = handles[handles.length - 1] ? (await handles[handles.length - 1].getFile()).name : Object.keys(files)[0]; setActiveFileName(lastName); setCode(files[lastName]); handleReset(); setCompileLog([{ type: 'success', message: `Loaded ${Object.keys(files).length} Java file(s).` }]); } else { const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.accept = '.java'; input.onchange = async (e) => { const files = { ...localFiles }; for (const file of e.target.files) { if (file.name.endsWith('.java')) { files[file.name] = await file.text(); } } setLocalFiles(files); const lastName = e.target.files[e.target.files.length - 1]?.name || Object.keys(files)[0]; setActiveFileName(lastName); setCode(files[lastName]); handleReset(); setCompileLog([{ type: 'success', message: `Loaded ${Object.keys(files).length} Java file(s).` }]); }; input.click(); } } catch (err) { if (err.name !== 'AbortError') { setCompileLog([{ type: 'error', message: `Failed to load files: ${err.message}` }]); setActiveTab('compilelog'); } } }, [handleReset, localFiles]); const loadLocalFile = useCallback((name) => { setCode(localFiles[name]); codeHistoryRef.current = [localFiles[name]]; historyIndexRef.current = 0; setActiveFileName(name); handleReset(); setShowExamples(false); }, [localFiles, handleReset]); const editorRef = useRef(null); const textareaRef = useRef(null); const overlayScrollRef = useRef(null); const autoPlayRef = useRef(null); const interpreterRef = useRef(new JavaInterpreter()); const exampleMenuRef = useRef(null); // Close example dropdown on outside click useEffect(() => { const handler = (e) => { if (exampleMenuRef.current && !exampleMenuRef.current.contains(e.target)) { setShowExamples(false); } }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, []); // Auto-play logic useEffect(() => { if (isAutoPlaying && currentStep < states.length - 1) { autoPlayRef.current = setTimeout(() => { const nextStep = currentStep + 1; // Check breakpoints if (breakpoints.has(states[nextStep]?.line) && nextStep !== currentStep + 1) { setIsAutoPlaying(false); return; } setCurrentStep(nextStep); }, speed); } else if (isAutoPlaying && currentStep >= states.length - 1) { setIsAutoPlaying(false); } return () => { if (autoPlayRef.current) clearTimeout(autoPlayRef.current); }; }, [isAutoPlaying, currentStep, states, speed, breakpoints]); // Shared run function that handles input waiting const runProgram = useCallback((inputs = []) => { const interpreter = interpreterRef.current; const result = interpreter.run(code, inputs); if (result.waiting) { // Program needs input — show what we have so far and prompt setStates(result.states || []); setCurrentStep(result.states?.length ? result.states.length - 1 : -1); setIsRunning(result.states?.length > 0); setWaitingForInput(true); setInputPromptType(result.inputType); setCompileLog([{ type: 'success', message: 'Program waiting for user input...' }]); setCompiledSuccess(true); setErrorLine(null); setActiveTab('output'); setTimeout(() => inputRef.current?.focus(), 100); return result; } setWaitingForInput(false); setInputPromptType(''); if (result.success) { setStates(result.states); setCurrentStep(result.states.length - 1); setIsRunning(true); setCompileLog([{ type: 'success', message: 'Compilation successful. Program executed.' }]); setCompiledSuccess(true); setErrorLine(null); } else { setStates(result.states || []); setCurrentStep(result.states?.length ? result.states.length - 1 : -1); setIsRunning(result.states?.length > 0); setCompileLog([{ type: 'error', message: result.error, line: result.errorLine }]); setCompiledSuccess(false); setErrorLine(result.errorLine); setActiveTab('compilelog'); } return result; }, [code]); const handleCompileRun = useCallback(() => { setUserInputs([]); runProgram([]); }, [runProgram]); const handleSubmitInput = useCallback(() => { if (currentInput.trim() === '' && inputPromptType !== 'nextLine') return; const newInputs = [...userInputs, currentInput]; setUserInputs(newInputs); setCurrentInput(''); setWaitingForInput(false); runProgram(newInputs); }, [currentInput, userInputs, inputPromptType, runProgram]); const handleStepForward = useCallback(() => { if (!isRunning) { const interpreter = interpreterRef.current; const result = interpreter.run(code, userInputs); if (result.waiting) { setStates(result.states || []); setCurrentStep(result.states?.length ? result.states.length - 1 : -1); setIsRunning(result.states?.length > 0); setWaitingForInput(true); setInputPromptType(result.inputType); setActiveTab('output'); setTimeout(() => inputRef.current?.focus(), 100); return; } if (result.success) { setStates(result.states); setCurrentStep(0); setIsRunning(true); setCompileLog([{ type: 'success', message: 'Compilation successful.' }]); setCompiledSuccess(true); setErrorLine(null); } else { setStates(result.states || []); setCompileLog([{ type: 'error', message: result.error }]); setCompiledSuccess(false); setActiveTab('compilelog'); } return; } if (currentStep < states.length - 1) { setCurrentStep(prev => prev + 1); } }, [isRunning, currentStep, states, code, userInputs]); const handleStepBack = useCallback(() => { if (currentStep > 0) { setCurrentStep(prev => prev - 1); } }, [currentStep]); const toggleAutoPlay = useCallback(() => { if (!isRunning) { const interpreter = interpreterRef.current; const result = interpreter.run(code, userInputs); if (result.waiting) { setStates(result.states || []); setCurrentStep(result.states?.length ? result.states.length - 1 : -1); setIsRunning(result.states?.length > 0); setWaitingForInput(true); setInputPromptType(result.inputType); setActiveTab('output'); return; } if (result.success) { setStates(result.states); setCurrentStep(0); setIsRunning(true); setCompileLog([{ type: 'success', message: 'Compilation successful.' }]); setCompiledSuccess(true); setErrorLine(null); setIsAutoPlaying(true); } return; } setIsAutoPlaying(prev => !prev); }, [isRunning, code, userInputs]); const toggleBreakpoint = useCallback((lineNum) => { setBreakpoints(prev => { const next = new Set(prev); if (next.has(lineNum)) next.delete(lineNum); else next.add(lineNum); return next; }); }, []); const loadExample = useCallback((name) => { setCode(EXAMPLES[name]); codeHistoryRef.current = [EXAMPLES[name]]; historyIndexRef.current = 0; setActiveFileName(null); handleReset(); setShowExamples(false); }, [handleReset]); const currentState = currentStep >= 0 && currentStep < states.length ? states[currentStep] : null; const currentLineNum = currentState?.line; const executedLines = currentState?.executedLines || new Set(); const currentOutput = currentState?.output || []; const currentVars = currentState?.vars || {}; const currentCallStack = currentState?.callStack || []; // Compute variable changes for diff display const prevState = currentStep > 0 && currentStep < states.length ? states[currentStep - 1] : null; const prevVars = prevState?.vars || {}; const varChanges = useMemo(() => { const changes = []; for (const [name, info] of Object.entries(currentVars)) { const prev = prevVars[name]; const isNew = !prev; const isChanged = info.changed; if (isNew) { changes.push({ name, type: 'new', value: info.value, varType: info.type }); } else if (isChanged) { const prevVal = info.prevValue; const newVal = info.value; let delta = null; if (typeof prevVal === 'number' && typeof newVal === 'number') { delta = newVal - prevVal; } changes.push({ name, type: 'changed', prevValue: prevVal, newValue: newVal, delta, varType: info.type }); } } return changes; }, [currentVars, prevVars]); // Execution path visualization const allExecutedLines = useMemo(() => { if (states.length === 0) return new Set(); return states[states.length - 1].executedLines || new Set(); }, [states]); const execCounts = useMemo(() => { const counts = {}; for (let i = 0; i <= currentStep && i < states.length; i++) { const line = states[i].line; if (line) counts[line] = (counts[line] || 0) + 1; } return counts; }, [states, currentStep]); const lines = code.split('\n'); const codeLineCount = lines.filter(l => l.trim() && !l.trim().startsWith('//') && !l.trim().startsWith('import') && l.trim() !== '{' && l.trim() !== '}').length; const executedLineCount = allExecutedLines.size; const coveragePercent = codeLineCount > 0 ? Math.round((executedLineCount / codeLineCount) * 100) : 0; // Derive file name from class declaration const derivedFileName = useMemo(() => { const classMatch = code.match(/public\s+class\s+(\w+)/); return classMatch ? `${classMatch[1]}.java` : 'Main.java'; }, [code]); const displayFileName = activeFileName || derivedFileName; // Download current code as .java file const handleDownload = useCallback(() => { const blob = new Blob([code], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = displayFileName || 'Main.java'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }, [code, displayFileName]); // Toggle fullscreen mode const handleToggleFullscreen = useCallback(() => { if (!document.fullscreenElement) { appRef.current?.requestFullscreen?.().then(() => setIsFullscreen(true)).catch(() => {}); } else { document.exitFullscreen?.().then(() => setIsFullscreen(false)).catch(() => {}); } }, []); // Listen for fullscreen changes (e.g., user presses Escape) useEffect(() => { const handler = () => setIsFullscreen(!!document.fullscreenElement); document.addEventListener('fullscreenchange', handler); return () => document.removeEventListener('fullscreenchange', handler); }, []); // Toggle line comment (// prefix) const handleToggleComment = useCallback(() => { const ta = textareaRef.current; if (!ta) return; const start = ta.selectionStart; const end = ta.selectionEnd; const allLines = code.split('\n'); // Find which lines are selected let charCount = 0; let startLine = 0, endLine = 0; for (let i = 0; i < allLines.length; i++) { if (charCount + allLines[i].length >= start && startLine === 0 && charCount <= start) startLine = i; if (charCount + allLines[i].length >= end - 1 || i === allLines.length - 1) { endLine = i; break; } charCount += allLines[i].length + 1; } // Check if all selected lines are already commented const selectedLines = allLines.slice(startLine, endLine + 1); const allCommented = selectedLines.every(l => l.trimStart().startsWith('//')); // Toggle comments const newLines = [...allLines]; for (let i = startLine; i <= endLine; i++) { if (allCommented) { // Remove comment newLines[i] = newLines[i].replace(/^(\s*)\/\/\s?/, '$1'); } else { // Add comment newLines[i] = newLines[i].replace(/^(\s*)/, '$1// '); } } updateCode(newLines.join('\n')); }, [code, updateCode]); // Global keyboard shortcuts useEffect(() => { const handler = (e) => { const isMod = e.metaKey || e.ctrlKey; // Cmd/Ctrl + Enter → Compile & Run if (isMod && e.key === 'Enter') { e.preventDefault(); handleCompileRun(); return; } // Cmd/Ctrl + S → Download file if (isMod && e.key === 's') { e.preventDefault(); handleDownload(); return; } // Cmd/Ctrl + / → Toggle comment if (isMod && e.key === '/') { e.preventDefault(); handleToggleComment(); return; } // F11 → Toggle fullscreen if (e.key === 'F11') { e.preventDefault(); handleToggleFullscreen(); return; } // Cmd/Ctrl + Z → Undo if (isMod && e.key === 'z' && !e.shiftKey) { e.preventDefault(); handleUndo(); return; } // Cmd/Ctrl + Shift + Z → Redo if (isMod && e.key === 'z' && e.shiftKey) { e.preventDefault(); handleRedo(); return; } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [handleCompileRun, handleDownload, handleToggleComment, handleToggleFullscreen, handleUndo, handleRedo]); // Track cursor position in textarea const updateCursorPos = useCallback(() => { const ta = textareaRef.current; if (!ta) return; const pos = ta.selectionStart; const textBefore = ta.value.substring(0, pos); const lineNum = textBefore.split('\n').length; const lastNewline = textBefore.lastIndexOf('\n'); const colNum = pos - lastNewline; setCursorPos({ line: lineNum, col: colNum }); }, []); // Handle keyboard in textarea const handleKeyDown = (e) => { if (e.key === 'Tab') { e.preventDefault(); const textarea = textareaRef.current; const start = textarea.selectionStart; const end = textarea.selectionEnd; const newCode = code.substring(0, start) + ' ' + code.substring(end); updateCode(newCode); setTimeout(() => { textarea.selectionStart = textarea.selectionEnd = start + 4; }, 0); } }; return (
{/* Header / Toolbar */}
{/* Logo */}
Java
Java Code Visualizer
Compile · Execute · Visualize line-by-line
{/* Theme Toggle */}
setIsDark(d => !d)} title={isDark ? 'Switch to Light Mode' : 'Switch to Dark Mode'} role="button" aria-label="Toggle theme" >
{isDark ? ( ) : ( )}
{/* Controls */} {/* Speed */}
Speed setSpeed(2100 - parseInt(e.target.value))} />
{/* Example dropdown */}
{showExamples && (
{/* Load files action */} {/* Show loaded local files */} {Object.keys(localFiles).length > 0 && ( <>
Your Files
{Object.keys(localFiles).map(name => ( ))} )} {/* Built-in examples */}
Built-in Examples
{Object.keys(EXAMPLES).map(name => ( ))}
)}
{/* Step indicator */} {isRunning && (
Step {currentStep + 1} of {states.length} {currentLineNum && Line {currentLineNum}} {currentState?.meta?.loopType && ( {currentState.meta.loopType} · iteration {currentState.meta.iteration} )} {/* Progress bar */}
0 ? ((currentStep + 1) / states.length) * 100 : 0}%`, height: '100%', background: t.progressGradient, borderRadius: 2, transition: 'width 0.2s' }} />
{/* Coverage indicator */}
{coveragePercent}%
)} {/* Main content */}
{/* Left pane: Code Editor */}
{/* File label */}
{displayFileName}
{/* Editor area */}
{/* Scrollable container */}
{ if (textareaRef.current) { textareaRef.current.scrollTop = e.target.scrollTop; textareaRef.current.scrollLeft = e.target.scrollLeft; } }} ref={overlayScrollRef} > {/* Syntax-highlighted overlay */}
{lines.map((line, idx) => { const lineNum = idx + 1; const isCurrentLine = isRunning && lineNum === currentLineNum; const isExecuted = executedLines.has(lineNum); const isError = lineNum === errorLine; const hasBreakpoint = breakpoints.has(lineNum); const lineExecCount = execCounts[lineNum] || 0; const wasEverExecuted = allExecutedLines.has(lineNum); const trimmedLine = line.trim(); const isCodeLine = trimmedLine && trimmedLine !== '{' && trimmedLine !== '}' && !trimmedLine.startsWith('//') && !trimmedLine.startsWith('import') && !trimmedLine.startsWith('public class'); const isNeverReached = isRunning && isCodeLine && !wasEverExecuted && currentStep === states.length - 1; return (
toggleBreakpoint(lineNum)} style={{ lineHeight: '28px', fontSize: 12, color: isCurrentLine ? t.accent : t.textDim, pointerEvents: 'auto' }} > {hasBreakpoint &&
} {!hasBreakpoint && isExecuted && !isCurrentLine && lineExecCount > 1 && (
{lineExecCount > 99 ? '99+' : lineExecCount}
)} {!hasBreakpoint && isExecuted && !isCurrentLine && lineExecCount <= 1 && (
)} {lineNum}
{isNeverReached && not reached}
); })}
{/* Textarea for editing — sits behind overlay, covers code area only */}