Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 | 13x 13x 13x | /**
* Shared Arduino Mock code for Compiler and Runner
* * Differences between Compiler and Runner:
* - Compiler: Only needs type definitions for syntax check (no implementation needed)
* - Runner: Needs working implementations for real execution
* * This file contains the complete Runner version that also works for Compiler.
* * --- UPDATES ---
* 1. String class: Added concat(char c) and a constructor for char to support char appending.
* 2. SerialClass: Added explicit operator bool() to fix 'while (!Serial)' error.
* 3. SerialClass: Implemented readStringUntil(char terminator).
* 4. SerialClass: Added print/println overloads with decimals parameter for float/double.
* 5. SerialClass: Added parseFloat(), readString(), setTimeout(), write(buf,len), readBytes(), readBytesUntil()
* 6. SerialClass: Added print/println with format (DEC, HEX, OCT, BIN)
*/
export const ARDUINO_MOCK_LINES = 427; // NOTE: approximate line count
export const ARDUINO_MOCK_CODE = `
// Simulated Arduino environment
#include <iostream>
#include <string>
#include <cmath>
#include <stdint.h>
#include <thread>
#include <chrono>
#include <random>
#include <cstdlib>
#include <ctime>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <cstring>
#include <algorithm> // For std::tolower/toupper in String
#include <iomanip> // For std::setprecision
#include <sstream> // For std::ostringstream
#include <unistd.h> // For STDIN_FILENO
#include <sys/select.h> // For select()
#include <map> // For I/O Registry
#include <vector> // For I/O Registry operations
using namespace std;
// Arduino specific types
typedef bool boolean;
#define byte uint8_t
// Pin modes and states
#define HIGH 0x1
#define LOW 0x0
#define INPUT 0x0
#define OUTPUT 0x1
#define INPUT_PULLUP 0x2
#define LED_BUILTIN 13
// Analog pins
#define A0 14
#define A1 15
#define A2 16
#define A3 17
#define A4 18
#define A5 19
// Math constants
#define PI 3.1415926535897932384626433832795
#define HALF_PI 1.5707963267948966192313216916398
#define TWO_PI 6.283185307179586476925286766559
#define DEG_TO_RAD 0.017453292519943295769236907684886
#define RAD_TO_DEG 57.295779513082320876798154814105
// Number format constants for print()
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
// Math functions
#define abs(x) ((x)>0?(x):-(x))
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
#define sq(x) ((x)*(x))
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
#define map(value, fromLow, fromHigh, toLow, toHigh) (toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow))
// Random number generator (for runner)
static std::mt19937 rng(std::time(nullptr));
std::atomic<bool> keepReading(true);
// Global mutex for all std::cerr writes.
// The background serialInputReader thread and the main loop thread both write
// protocol messages to stderr. Chained << operations are NOT atomic, so without
// this mutex the two threads can interleave output and corrupt protocol framing.
static std::mutex cerrMutex;
// Pause/Resume timing state
static std::atomic<bool> processIsPaused(false);
static std::atomic<unsigned long> pausedTimeMs(0);
static auto processStartTime = std::chrono::steady_clock::now();
static unsigned long pauseTimeOffset = 0;
// Forward declaration
void checkStdinForPinCommands();
// Arduino String class
class String {
private:
std::string str;
public:
String() {}
String(const char* s) : str(s) {}
String(std::string s) : str(s) {}
String(char c) : str(1, c) {} // New: Constructor from char
String(int i) : str(std::to_string(i)) {}
String(long l) : str(std::to_string(l)) {}
String(float f) : str(std::to_string(f)) {}
String(double d) : str(std::to_string(d)) {}
const char* c_str() const { return str.c_str(); }
int length() const { return str.length(); }
char charAt(int i) const { return (size_t)i < str.length() ? str[i] : 0; }
void concat(String s) { str += s.str; }
void concat(const char* s) { str += s; }
void concat(int i) { str += std::to_string(i); }
void concat(char c) { str += c; } // New: Concat char
int indexOf(char c) const { return str.find(c); }
int indexOf(String s) const { return str.find(s.str); }
String substring(int start) const { return String(str.substr(start).c_str()); }
String substring(int start, int end) const { return String(str.substr(start, end-start).c_str()); }
void replace(String from, String to) {
size_t pos = 0;
while ((pos = str.find(from.str, pos)) != std::string::npos) {
str.replace(pos, from.str.length(), to.str);
pos += to.str.length();
}
}
void toLowerCase() { for(auto& c : str) c = std::tolower(c); }
void toUpperCase() { for(auto& c : str) c = std::toupper(c); }
void trim() {
str.erase(0, str.find_first_not_of(" \\t\\n\\r"));
str.erase(str.find_last_not_of(" \\t\\n\\r") + 1);
}
int toInt() const { return std::stoi(str); }
float toFloat() const { return std::stof(str); }
String operator+(const String& other) const { return String((str + other.str).c_str()); }
String operator+(const char* other) const { return String((str + other).c_str()); }
bool operator==(const String& other) const { return str == other.str; }
friend std::ostream& operator<<(std::ostream& os, const String& s) {
return os << s.str;
}
};
// Pin state tracking for visualization
static int pinModes[20] = {0}; // 0=INPUT, 1=OUTPUT, 2=INPUT_PULLUP
static std::atomic<int> pinValues[20]; // Thread-safe: Digital 0=LOW, 1=HIGH
// Initialize atomic array (called before main)
struct PinValuesInitializer {
PinValuesInitializer() {
for (int i = 0; i < 20; i++) {
pinValues[i].store(0);
}
}
} pinValuesInit;
// Runtime I/O Registry tracking
struct IOOperation {
int line;
std::string operation;
};
struct IOPinRecord {
std::string pin;
bool defined;
int definedLine;
int pinMode; // 0=INPUT, 1=OUTPUT, 2=INPUT_PULLUP
std::vector<IOOperation> operations;
};
static std::map<int, IOPinRecord> ioRegistry;
void initIORegistry() {
ioRegistry.clear();
// Pre-populate all 20 Arduino pins
for (int i = 0; i <= 13; i++) {
IOPinRecord rec;
rec.pin = std::to_string(i);
rec.defined = false;
rec.definedLine = 0;
rec.pinMode = 0;
rec.operations = {};
ioRegistry[i] = rec;
}
for (int i = 14; i <= 19; i++) {
IOPinRecord rec;
rec.pin = "A" + std::to_string(i - 14);
rec.defined = false;
rec.definedLine = 0;
rec.pinMode = 0;
rec.operations = {};
ioRegistry[i] = rec;
}
}
void outputIORegistry() {
std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[IO_REGISTRY_START]]" << std::endl;
std::cerr.flush();
for (const auto& pair : ioRegistry) {
const auto& rec = pair.second;
std::cerr << "[[IO_PIN:" << rec.pin << ":" << (rec.defined ? "1" : "0") << ":" << rec.definedLine << ":" << rec.pinMode;
// Limit to first 5 operations per pin to avoid buffer overflow
int opCount = 0;
for (const auto& op : rec.operations) {
if (opCount >= 5) break; // Only output first 5 operations
std::cerr << ":" << op.operation << "@" << op.line;
opCount++;
}
if (rec.operations.size() > 5) {
std::cerr << ":_count@" << rec.operations.size(); // Append count if more than 5
}
std::cerr << "]]" << std::endl;
}
std::cerr << "[[IO_REGISTRY_END]]" << std::endl;
std::cerr.flush();
}
// GPIO Functions with state tracking
void pinMode(int pin, int mode) {
if (pin >= 0 && pin < 20) {
pinModes[pin] = mode;
// Send pin state update via stderr (special protocol)
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[PIN_MODE:" << pin << ":" << mode << "]]" << std::endl; }
// Track in I/O Registry - add pinMode as an operation with mode info
if (ioRegistry.find(pin) != ioRegistry.end()) {
ioRegistry[pin].defined = true;
ioRegistry[pin].definedLine = 0; // Line number not available at runtime
ioRegistry[pin].pinMode = mode; // Keep the pinMode field updated for backwards compatibility
// Track pinMode in operations (format: "pinMode:MODE" where MODE is 0=INPUT, 1=OUTPUT, 2=INPUT_PULLUP)
std::string pinModeOp = "pinMode:" + std::to_string(mode);
ioRegistry[pin].operations.push_back({0, pinModeOp});
}
}
}
// Helper: Track IO operation (consolidates redundant tracking code)
inline void trackIOOperation(int pin, const std::string& operation) {
if (ioRegistry.find(pin) != ioRegistry.end()) {
bool opExists = false;
for (const auto& op : ioRegistry[pin].operations) {
if (op.operation == operation) {
opExists = true;
break;
}
}
if (!opExists) {
ioRegistry[pin].operations.push_back({0, operation});
}
}
}
void digitalWrite(int pin, int value) {
if (pin >= 0 && pin < 20) {
int oldValue = pinValues[pin].load(std::memory_order_seq_cst);
pinValues[pin].store(value, std::memory_order_seq_cst);
// Only send update if value actually changed (avoid stderr flooding)
if (oldValue != value) {
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[PIN_VALUE:" << pin << ":" << value << "]]" << std::endl;
std::cerr.flush(); }
}
trackIOOperation(pin, "digitalWrite");
}
}
int digitalRead(int pin) {
if (pin >= 0 && pin < 20) {
int val = pinValues[pin].load(std::memory_order_seq_cst);
trackIOOperation(pin, "digitalRead");
return val;
}
return LOW;
}
void analogWrite(int pin, int value) {
if (pin >= 0 && pin < 20) {
int oldValue = pinValues[pin].load(std::memory_order_seq_cst);
pinValues[pin].store(value, std::memory_order_seq_cst);
// Only send update if value actually changed
if (oldValue != value) {
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[PIN_PWM:" << pin << ":" << value << "]]" << std::endl; }
}
trackIOOperation(pin, "analogWrite");
}
}
int analogRead(int pin) {
// Support both analog channel numbers 0..5 and A0..A5 (14..19)
int p = pin;
if (pin >= 0 && pin <= 5) p = 14 + pin; // map channel 0..5 to A0..A5
if (p >= 0 && p < 20) {
trackIOOperation(p, "analogRead");
// Return the externally-set pin value (0..1023 expected for analog inputs)
return pinValues[p].load(std::memory_order_seq_cst);
}
return 0;
}
// Timing Functions - with pause/resume support
void delayMicroseconds(unsigned int us) {
std::this_thread::sleep_for(std::chrono::microseconds(us));
}
unsigned long millis() {
// If paused, return the frozen time value
if (processIsPaused.load()) {
return pausedTimeMs.load();
}
// Normal operation: calculate elapsed time since start
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
now - processStartTime
).count();
// Subtract any pause offsets that have been accumulated
return static_cast<unsigned long>(elapsed) - pauseTimeOffset;
}
unsigned long micros() {
// If paused, return the frozen time value (in microseconds)
if (processIsPaused.load()) {
return pausedTimeMs.load() * 1000UL;
}
// Normal operation: calculate elapsed time since start
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(
now - processStartTime
).count();
// Subtract any pause offsets that have been accumulated
return static_cast<unsigned long>(elapsed) - (pauseTimeOffset * 1000UL);
}
// Random Functions
void randomSeed(unsigned long seed) {
rng.seed(seed);
std::srand(seed);
}
long random(long max) {
if (max <= 0) return 0;
std::uniform_int_distribution<long> dist(0, max - 1);
return dist(rng);
}
long random(long min, long max) {
if (min >= max) return min;
std::uniform_int_distribution<long> dist(min, max - 1);
return dist(rng);
}
// Serial class with working implementation
class SerialClass {
private:
std::mutex mtx;
std::queue<uint8_t> inputBuffer;
unsigned long _timeout = 1000; // Default timeout 1 second
bool initialized = false;
long _baudrate = 9600;
std::string lineBuffer; // Buffer to accumulate output until newline
// TX Buffer (backpressure simulation)
// Real Arduino Uno has 64-byte TX buffer, MEGA has 128-byte
// We use 256 to be generous with modern systems
static const size_t TX_BUFFER_SIZE = 256;
size_t txBufferUsed = 0; // Current bytes in TX buffer
std::chrono::steady_clock::time_point lastTxTime; // Initialized in constructor
// Simulate serial transmission delay for n characters
// 10 bits per char: start + 8 data + stop
// Also checks stdin during the delay for responsiveness
void txDelay(size_t numChars) {
if (_baudrate > 0 && numChars > 0) {
// Milliseconds total = (10 bits * numChars * 1000) / baudrate
long totalMs = (10L * numChars * 1000L) / _baudrate;
// Cap at 2ms so the SerialOutputBatcher is the sole rate-limiter.
// For short messages at standard baudrates (e.g. println("Hello") at 9600),
// txDelay stays realistic (1.2ms uncapped). For large messages or low baudrates,
// the mock runs faster than real UART and the batcher drops excess data.
if (totalMs > 2L) totalMs = 2L;
// Direct sleep (consistent with simplified delay() - no stdin polling during serial tx)
std::this_thread::sleep_for(std::chrono::milliseconds(totalMs));
}
}
// Update TX buffer state - simulates bytes draining at baudrate
void updateTxBuffer() {
if (txBufferUsed > 0 && _baudrate > 0) {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastTxTime).count();
// Calculate how many bytes could drain in the elapsed time
// Drain rate: baudrate / 10 bits per byte = bytes per second
double bytesPerMs = (_baudrate / 10.0) / 1000.0;
size_t bytesDrained = static_cast<size_t>(elapsed * bytesPerMs);
if (bytesDrained > 0) {
txBufferUsed = (bytesDrained >= txBufferUsed) ? 0 : (txBufferUsed - bytesDrained);
lastTxTime = now;
}
}
}
// Block if TX buffer is getting full (backpressure)
void applyBackpressure(size_t newBytes) {
updateTxBuffer();
if (txBufferUsed + newBytes > TX_BUFFER_SIZE) {
// Buffer would overflow - calculate how long to wait
size_t bytesOverflow = (txBufferUsed + newBytes) - TX_BUFFER_SIZE;
double bytesPerMs = (_baudrate / 10.0) / 1000.0;
if (bytesPerMs > 0) {
// How long to wait for overflow bytes to drain?
unsigned long waitMs = static_cast<unsigned long>((bytesOverflow / bytesPerMs) + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(waitMs));
updateTxBuffer();
}
}
// Add new bytes to buffer
txBufferUsed += newBytes;
}
// Base64 encoder helper
static std::string base64_encode(const std::string &in) {
static const std::string b64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
int val=0, valb=-6;
for (unsigned char c : in) {
val = (val<<8) + c;
valb += 8;
while (valb>=0) {
out.push_back(b64_chars[(val>>valb)&0x3F]);
valb-=6;
}
}
if (valb>-6) out.push_back(b64_chars[((val<<8)>>(valb+8))&0x3F]);
while (out.size()%4) out.push_back('=');
return out;
}
// Flush the line buffer as a single SERIAL_EVENT
void flushLineBuffer() {
if (lineBuffer.empty()) return;
unsigned long ts = millis();
std::string enc = base64_encode(lineBuffer);
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[SERIAL_EVENT:" << ts << ":" << enc << "]]" << std::endl;
std::cerr.flush(); }
// Simulate transmit time for the whole buffer
txDelay(lineBuffer.length());
lineBuffer.clear();
}
// Output string - buffer until newline; flush BEFORE backspace/carriage return
// so that the control char stays with its following content
// WITH BACKPRESSURE: blocks if TX buffer would overflow
void serialWrite(const std::string& s) {
// Apply backpressure before adding to output buffer
applyBackpressure(s.length());
for (char c : s) {
if (c == '\\b' || c == '\\r') {
// Flush pending content BEFORE the control character
flushLineBuffer();
// Add backspace to buffer - it will be sent with the next char(s)
lineBuffer += c;
} else if (c == '\\n') {
lineBuffer += c;
flushLineBuffer();
} else {
lineBuffer += c;
}
}
}
void serialWrite(char c) {
// Apply backpressure for single character
applyBackpressure(1);
if (c == '\\b' || c == '\\r') {
flushLineBuffer();
lineBuffer += c;
} else if (c == '\\n') {
lineBuffer += c;
flushLineBuffer();
} else {
lineBuffer += c;
}
}
public:
SerialClass() {
std::cout.setf(std::ios::unitbuf);
std::cerr.setf(std::ios::unitbuf);
lastTxTime = std::chrono::steady_clock::now();
}
// Fix for 'while (!Serial)' error
explicit operator bool() const {
return true; // The serial connection is always considered 'ready' in the mock
}
void begin(long baud) {
_baudrate = baud;
// Reset TX buffer state
txBufferUsed = 0;
lastTxTime = std::chrono::steady_clock::now();
if (!initialized) {
// Disable buffering on stdout and stderr for immediate output
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
initialized = true;
}
}
void begin(long baud, int config) { begin(baud); }
void end() {}
// Set timeout for read operations (in milliseconds)
void setTimeout(unsigned long timeout) {
_timeout = timeout;
}
int available() {
std::lock_guard<std::mutex> lock(mtx);
return static_cast<int>(inputBuffer.size());
}
int read() {
std::lock_guard<std::mutex> lock(mtx);
if (inputBuffer.empty()) return -1;
uint8_t b = inputBuffer.front();
inputBuffer.pop();
return b;
}
int peek() {
std::lock_guard<std::mutex> lock(mtx);
if (inputBuffer.empty()) return -1;
return inputBuffer.front();
}
// Read string until terminator character
String readStringUntil(char terminator) {
String result;
while (available() > 0) {
int c = read();
if (c == -1) break;
if ((char)c == terminator) break;
result.concat((char)c);
}
return result;
}
// Read entire string (until timeout or no more data)
String readString() {
String result;
while (available() > 0) {
int c = read();
if (c == -1) break;
result.concat((char)c);
}
return result;
}
// Read bytes into buffer, returns number of bytes read
size_t readBytes(char* buffer, size_t length) {
size_t count = 0;
while (count < length && available() > 0) {
int c = read();
if (c == -1) break;
buffer[count++] = (char)c;
}
return count;
}
// Read bytes until terminator or length reached
size_t readBytesUntil(char terminator, char* buffer, size_t length) {
size_t count = 0;
while (count < length && available() > 0) {
int c = read();
if (c == -1) break;
if ((char)c == terminator) break;
buffer[count++] = (char)c;
}
return count;
}
void flush() {
// Flush the line buffer immediately (for Serial.print without newline)
flushLineBuffer();
std::cout << std::flush;
}
// Helper for number format conversion - returns string
// Supports any base >= 2, matching Arduino's Print::printNumber() behavior
std::string formatNumber(long n, int base) {
if (base < 2) base = 10; // Arduino defaults to base 10 for invalid bases
std::ostringstream oss;
if (base == DEC) {
oss << n;
} else if (base == HEX) {
oss << std::uppercase << std::hex << n << std::dec;
} else if (base == OCT) {
oss << std::oct << n << std::dec;
} else {
// General base conversion (BIN and any other base >= 2)
if (n == 0) { oss << "0"; }
else {
std::string result;
unsigned long un = (n < 0) ? (unsigned long)n : n;
while (un > 0) {
int digit = un % base;
result = (char)(digit < 10 ? '0' + digit : 'A' + digit - 10) + result;
un /= base;
}
oss << result;
}
}
return oss.str();
}
void printNumber(long n, int base) {
serialWrite(formatNumber(n, base));
}
template<typename T> void print(T v) {
std::ostringstream oss;
oss << v;
serialWrite(oss.str());
}
// Special overload for byte/uint8_t (otherwise printed as char)
void print(byte v) {
std::ostringstream oss;
oss << (int)v;
serialWrite(oss.str());
}
// print with base format (DEC, HEX, OCT, BIN)
void print(int v, int base) { printNumber(v, base); }
void print(long v, int base) { printNumber(v, base); }
void print(unsigned int v, int base) { printNumber(v, base); }
void print(unsigned long v, int base) { printNumber(v, base); }
void print(byte v, int base) { printNumber(v, base); }
// Overload for floating-point with decimal places
void print(float v, int decimals) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(decimals) << v;
serialWrite(oss.str());
}
void print(double v, int decimals) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(decimals) << v;
serialWrite(oss.str());
}
template<typename T> void println(T v) {
std::ostringstream oss;
oss << v << "\\n";
serialWrite(oss.str());
}
// Special overload for byte/uint8_t (otherwise printed as char)
void println(byte v) {
std::ostringstream oss;
oss << (int)v << "\\n";
serialWrite(oss.str());
}
// println with base format (DEC, HEX, OCT, BIN)
void println(int v, int base) {
serialWrite(formatNumber(v, base) + "\\n");
}
void println(long v, int base) {
serialWrite(formatNumber(v, base) + "\\n");
}
void println(unsigned int v, int base) {
serialWrite(formatNumber(v, base) + "\\n");
}
void println(unsigned long v, int base) {
serialWrite(formatNumber(v, base) + "\\n");
}
void println(byte v, int base) {
serialWrite(formatNumber(v, base) + "\\n");
}
// Overload for floating-point with decimal places
void println(float v, int decimals) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(decimals) << v << "\\n";
serialWrite(oss.str());
}
void println(double v, int decimals) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(decimals) << v << "\\n";
serialWrite(oss.str());
}
void println() {
serialWrite("\\n");
}
// parseInt() - Reads next integer from Serial Input
int parseInt() {
int result = 0;
int c;
// Skip non-digit characters
while ((c = read()) != -1) {
if ((c >= '0' && c <= '9') || c == '-') {
break;
}
}
if (c == -1) return 0;
boolean negative = (c == '-');
if (!negative && c >= '0' && c <= '9') {
result = c - '0';
}
while ((c = read()) != -1) {
if (c >= '0' && c <= '9') {
result = result * 10 + (c - '0');
} else {
break;
}
}
return negative ? -result : result;
}
// parseFloat() - Reads next float from Serial Input
float parseFloat() {
float result = 0.0f;
float fraction = 0.0f;
float divisor = 1.0f;
boolean negative = false;
boolean inFraction = false;
int c;
// Skip non-digit characters (except minus and dot)
while ((c = read()) != -1) {
if ((c >= '0' && c <= '9') || c == '-' || c == '.') {
break;
}
}
if (c == -1) return 0.0f;
// Handle negative sign
if (c == '-') {
negative = true;
c = read();
}
// Read integer and fractional parts
while (c != -1) {
if (c == '.') {
inFraction = true;
} else if (c >= '0' && c <= '9') {
if (inFraction) {
divisor *= 10.0f;
fraction += (c - '0') / divisor;
} else {
result = result * 10.0f + (c - '0');
}
} else {
break;
}
c = read();
}
result += fraction;
return negative ? -result : result;
}
void write(uint8_t b) { serialWrite(std::string(1, (char)b)); }
void write(const char* str) { serialWrite(std::string(str)); }
// Write buffer with length
size_t write(const uint8_t* buffer, size_t size) {
std::string s;
s.reserve(size);
for (size_t i = 0; i < size; i++) {
s += (char)buffer[i];
}
serialWrite(s);
return size;
}
size_t write(const char* buffer, size_t size) {
return write((const uint8_t*)buffer, size);
}
void mockInput(const char* data, size_t len) {
std::lock_guard<std::mutex> lock(mtx);
for (size_t i = 0; i < len; i++) {
inputBuffer.push(static_cast<uint8_t>(data[i]));
}
}
void mockInput(const std::string& data) {
mockInput(data.c_str(), data.size());
}
};
SerialClass Serial;
// Implementation of delay() after SerialClass is defined
inline void delay(unsigned long ms) {
// Flush serial buffer FIRST so output appears before the delay
Serial.flush();
// Direct sleep without chunking to avoid overhead from repeated system calls.
// The previous implementation split into 10ms chunks and called checkStdinForPinCommands()
// ~100 times per second, which added ~2ms per iteration (~200ms overhead for 1000ms delay).
// Real Arduino blocks completely during delay, so this matches expected behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
// Global buffer for stdin reading (used by checkStdinForPinCommands)
static char stdinBuffer[256];
static size_t stdinBufPos = 0;
// Helper function to set pin value from external input
void setExternalPinValue(int pin, int value) {
if (pin >= 0 && pin < 20) {
pinValues[pin].store(value, std::memory_order_seq_cst);
// Send pin state update so UI reflects the change
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[PIN_VALUE:" << pin << ":" << value << "]]" << std::endl; }
}
}
// Helper functions for pause/resume timing
void handlePauseTimeCommand() {
processIsPaused.store(true);
unsigned long currentMs = millis(); // Get current time before freezing
pausedTimeMs.store(currentMs);
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[TIME_FROZEN:" << currentMs << "]]" << std::endl; }
}
void handleResumeTimeCommand(unsigned long pauseDurationMs) {
processIsPaused.store(false);
// Adjust offset to account for the pause duration that elapsed in real time
pauseTimeOffset += pauseDurationMs;
{ std::lock_guard<std::mutex> lock(cerrMutex);
std::cerr << "[[TIME_RESUMED:" << pauseTimeOffset << "]]" << std::endl; }
}
// Non-blocking check for stdin pin commands - called from delay() and txDelay()
void checkStdinForPinCommands() {
fd_set readfds;
struct timeval tv;
while (true) {
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 0; // Zero timeout = immediate return
int selectResult = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
if (selectResult <= 0 || !FD_ISSET(STDIN_FILENO, &readfds)) {
break;
}
// Read one byte
char c;
ssize_t n = read(STDIN_FILENO, &c, 1);
if (n <= 0) break;
if (c == '\\n' || c == '\\r') {
// End of line - process buffer
if (stdinBufPos > 0) {
stdinBuffer[stdinBufPos] = '\\0';
// Check for pause/resume commands
if (sscanf(stdinBuffer, "[[PAUSE_TIME]]") == 0 &&
strncmp(stdinBuffer, "[[PAUSE_TIME]]", 14) == 0) {
handlePauseTimeCommand();
} else {
// Check for resume time with duration parameter
unsigned long pauseDurationMs;
if (sscanf(stdinBuffer, "[[RESUME_TIME:%lu]]", &pauseDurationMs) == 1) {
handleResumeTimeCommand(pauseDurationMs);
} else {
// Check for special pin value command: [[SET_PIN:X:Y]]
int pin, value;
if (sscanf(stdinBuffer, "[[SET_PIN:%d:%d]]", &pin, &value) == 2) {
setExternalPinValue(pin, value);
} else {
// Normal serial input (add newline back for serial input)
Serial.mockInput(stdinBuffer, stdinBufPos);
char newline = 10;
Serial.mockInput(&newline, 1);
}
}
}
stdinBufPos = 0;
}
} else if (stdinBufPos < sizeof(stdinBuffer) - 1) {
stdinBuffer[stdinBufPos++] = c;
}
}
}
// Thread-based reader for when main thread is not in delay/serial (legacy, still useful)
void serialInputReader() {
while (keepReading.load()) {
checkStdinForPinCommands();
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
`;
export const ARDUINO_MOCK_CODE_MINIMAL = ARDUINO_MOCK_CODE;
|