| Line | Branch | Decision | Exec | Source |
|---|---|---|---|---|
| 1 | #include "pch.h" | |||
| 2 | ||||
| 3 | #include "rusefi_lua.h" | |||
| 4 | #include "thread_controller.h" | |||
| 5 | ||||
| 6 | #if EFI_LUA | |||
| 7 | ||||
| 8 | #include "lua.hpp" | |||
| 9 | #include "lua_heap.h" | |||
| 10 | #include "lua_hooks.h" | |||
| 11 | #include "can_filter.h" | |||
| 12 | ||||
| 13 | #define TAG "LUA " | |||
| 14 | ||||
| 15 | static bool withErrorLoading = false; | |||
| 16 | static int luaTickPeriodUs; | |||
| 17 | ||||
| 18 | #if EFI_CAN_SUPPORT | |||
| 19 | static int recentRxCount = 0; | |||
| 20 | static int totalRxCount = 0; | |||
| 21 | static int rxTime; | |||
| 22 | #endif // EFI_CAN_SUPPORT | |||
| 23 | ||||
| 24 | ✗ | static int lua_setTickRate(lua_State* l) { | ||
| 25 | ✗ | float userFreq = luaL_checknumber(l, 1); | ||
| 26 | ||||
| 27 | // For instance BMW does 100 CAN messages per second on some IDs, let's allow at least twice that speed | |||
| 28 | // Limit to 1..200 hz | |||
| 29 | ✗ | float freq = clampF(1, userFreq, 2000); | ||
| 30 | ✗ | if (freq != userFreq) { | ||
| 31 | ✗ | efiPrintf(TAG "clamping tickrate %f", freq); | ||
| 32 | } | |||
| 33 | ||||
| 34 | ✗ | if (freq > 150 && !engineConfiguration->luaCanRxWorkaround) { | ||
| 35 | ✗ | efiPrintf(TAG "luaCanRxWorkaround recommended at high tick rate!"); | ||
| 36 | } | |||
| 37 | ||||
| 38 | ✗ | luaTickPeriodUs = 1000000.0f / freq; | ||
| 39 | ✗ | return 0; | ||
| 40 | } | |||
| 41 | ||||
| 42 | 297 | static void loadLibraries(LuaHandle& ls) { | ||
| 43 | 297 | constexpr luaL_Reg libs[] = { | ||
| 44 | // TODO: do we even need the base lib? | |||
| 45 | //{ LUA_GNAME, luaopen_base }, | |||
| 46 | { LUA_MATHLIBNAME, luaopen_math }, | |||
| 47 | }; | |||
| 48 | ||||
| 49 |
2/2✓ Branch 1 taken 297 times.
✓ Branch 2 taken 297 times.
|
2/2✓ Decision 'true' taken 297 times.
✓ Decision 'false' taken 297 times.
|
594 | for (size_t i = 0; i < efi::size(libs); i++) { |
| 50 |
1/1✓ Branch 4 taken 297 times.
|
297 | luaL_requiref(ls, libs[i].name, libs[i].func, 1); | |
| 51 |
1/1✓ Branch 2 taken 297 times.
|
297 | lua_pop(ls, 1); | |
| 52 | } | |||
| 53 | 297 | } | ||
| 54 | ||||
| 55 | 297 | static LuaHandle setupLuaState(lua_Alloc alloc) { | ||
| 56 |
1/1✓ Branch 2 taken 297 times.
|
297 | LuaHandle ls = lua_newstate(alloc, NULL); | |
| 57 | ||||
| 58 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 297 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 297 times.
|
297 | if (!ls) { |
| 59 | ✗ | criticalError("Failed to start Lua interpreter"); | ||
| 60 | ||||
| 61 | ✗ | return nullptr; | ||
| 62 | } | |||
| 63 | ||||
| 64 |
1/1✓ Branch 4 taken 297 times.
|
297 | lua_atpanic(ls, [](lua_State* l) { | |
| 65 | ✗ | criticalError("Lua panic: %s", lua_tostring(l, -1)); | ||
| 66 | ||||
| 67 | // hang the lua thread | |||
| 68 | ✗ | while (true) ; | ||
| 69 | ||||
| 70 | return 0; | |||
| 71 | }); | |||
| 72 | ||||
| 73 | // Load Lua's own libraries | |||
| 74 |
1/1✓ Branch 1 taken 297 times.
|
297 | loadLibraries(ls); | |
| 75 | ||||
| 76 | // Load rusEFI hooks | |||
| 77 |
2/2✓ Branch 2 taken 297 times.
✓ Branch 6 taken 297 times.
|
297 | lua_register(ls, "setTickRate", lua_setTickRate); | |
| 78 |
1/1✓ Branch 2 taken 297 times.
|
297 | configureRusefiLuaHooks(ls); | |
| 79 | ||||
| 80 | // run a GC cycle | |||
| 81 |
1/1✓ Branch 2 taken 297 times.
|
297 | lua_gc(ls, LUA_GCCOLLECT, 0); | |
| 82 | ||||
| 83 | // set GC settings | |||
| 84 | // see https://www.lua.org/manual/5.4/manual.html#2.5.1 | |||
| 85 |
1/1✓ Branch 2 taken 297 times.
|
297 | lua_gc(ls, LUA_GCINC, 50, 1000, 9); | |
| 86 | ||||
| 87 | 297 | return ls; | ||
| 88 | 297 | } | ||
| 89 | ||||
| 90 | 297 | static bool loadScript(LuaHandle& ls, const char* scriptStr) { | ||
| 91 | 297 | efiPrintf(TAG "loading script length: %u...", std::strlen(scriptStr)); | ||
| 92 | ||||
| 93 |
6/6✓ Branch 2 taken 296 times.
✓ Branch 3 taken 1 time.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 290 times.
✓ Branch 8 taken 7 times.
✓ Branch 9 taken 290 times.
|
2/2✓ Decision 'true' taken 7 times.
✓ Decision 'false' taken 290 times.
|
297 | if (0 != luaL_dostring(ls, scriptStr)) { |
| 94 | 7 | withErrorLoading = true; | ||
| 95 | 7 | efiPrintf(TAG "ERROR loading script: %s", lua_tostring(ls, -1)); | ||
| 96 | 7 | lua_pop(ls, 1); | ||
| 97 | 7 | return false; | ||
| 98 | } | |||
| 99 | ||||
| 100 | 290 | efiPrintf(TAG "script loaded successfully!"); | ||
| 101 | ||||
| 102 | #if EFI_PROD_CODE | |||
| 103 | luaHeapPrintInfo(); | |||
| 104 | #endif // EFI_PROD_CODE | |||
| 105 | ||||
| 106 | 290 | return true; | ||
| 107 | } | |||
| 108 | ||||
| 109 | #if !EFI_UNIT_TEST | |||
| 110 | static bool interactivePending = false; | |||
| 111 | static char interactiveCmd[100]; | |||
| 112 | ||||
| 113 | static void doInteractive(LuaHandle& ls) { | |||
| 114 | if (!interactivePending) { | |||
| 115 | // no cmd pending, return | |||
| 116 | return; | |||
| 117 | } | |||
| 118 | ||||
| 119 | auto status = luaL_dostring(ls, interactiveCmd); | |||
| 120 | ||||
| 121 | if (0 == status) { | |||
| 122 | // Function call was OK, resolve return value and print it | |||
| 123 | if (lua_isinteger(ls, -1)) { | |||
| 124 | efiPrintf(TAG "interactive returned integer: %d", lua_tointeger(ls, -1)); | |||
| 125 | } else if (lua_isnumber(ls, -1)) { | |||
| 126 | efiPrintf(TAG "interactive returned number: %f", lua_tonumber(ls, -1)); | |||
| 127 | } else if (lua_isstring(ls, -1)) { | |||
| 128 | efiPrintf(TAG "interactive returned string: '%s'", lua_tostring(ls, -1)); | |||
| 129 | } else if (lua_isboolean(ls, -1)) { | |||
| 130 | efiPrintf(TAG "interactive returned bool: %s", lua_toboolean(ls, -1) ? "true" : "false"); | |||
| 131 | } else if (lua_isnil(ls, -1)) { | |||
| 132 | efiPrintf(TAG "interactive returned nil."); | |||
| 133 | } else { | |||
| 134 | efiPrintf(TAG "interactive returned nothing."); | |||
| 135 | } | |||
| 136 | } else { | |||
| 137 | // error with interactive command, print it | |||
| 138 | efiPrintf(TAG "interactive error: %s", lua_tostring(ls, -1)); | |||
| 139 | } | |||
| 140 | ||||
| 141 | interactivePending = false; | |||
| 142 | ||||
| 143 | lua_settop(ls, 0); | |||
| 144 | } | |||
| 145 | ||||
| 146 | static uint32_t maxLuaDuration{}; | |||
| 147 | ||||
| 148 | static void invokeTick(LuaHandle& ls) { | |||
| 149 | ScopePerf perf(PE::LuaTickFunction); | |||
| 150 | ||||
| 151 | // run the tick function | |||
| 152 | lua_getglobal(ls, "onTick"); | |||
| 153 | if (lua_isnil(ls, -1)) { | |||
| 154 | // TODO: handle missing tick function | |||
| 155 | lua_settop(ls, 0); | |||
| 156 | return; | |||
| 157 | } | |||
| 158 | #if EFI_PROD_CODE | |||
| 159 | uint32_t before = port_rt_get_counter_value(); | |||
| 160 | #endif // EFI_PROD_CODE | |||
| 161 | ||||
| 162 | int status = lua_pcall(ls, 0, 0, 0); | |||
| 163 | ||||
| 164 | #if EFI_PROD_CODE | |||
| 165 | uint32_t duration = port_rt_get_counter_value() - before; | |||
| 166 | maxLuaDuration = std::max(maxLuaDuration, duration); | |||
| 167 | #endif // EFI_PROD_CODE | |||
| 168 | ||||
| 169 | if (0 != status) { | |||
| 170 | // error calling hook function | |||
| 171 | auto errMsg = lua_tostring(ls, -1); | |||
| 172 | efiPrintf(TAG "error %s", errMsg); | |||
| 173 | lua_pop(ls, 1); | |||
| 174 | } | |||
| 175 | ||||
| 176 | lua_settop(ls, 0); | |||
| 177 | } | |||
| 178 | ||||
| 179 | struct LuaThread : ThreadController<4096> { | |||
| 180 | LuaThread() : ThreadController("lua", PRIO_LUA) { } | |||
| 181 | ||||
| 182 | void ThreadTask() override; | |||
| 183 | }; | |||
| 184 | ||||
| 185 | static void resetLua() { | |||
| 186 | engine->module<AcController>().unmock().isDisabledByLua = false; | |||
| 187 | #if EFI_CAN_SUPPORT | |||
| 188 | resetLuaCanRx(); | |||
| 189 | #endif // EFI_CAN_SUPPORT | |||
| 190 | ||||
| 191 | // De-init pins, they will reinit next start of the script. | |||
| 192 | luaDeInitPins(); | |||
| 193 | } | |||
| 194 | ||||
| 195 | ||||
| 196 | static bool needsReset = false; | |||
| 197 | ||||
| 198 | // Each invocation of runOneLua will: | |||
| 199 | // - create a new Lua instance | |||
| 200 | // - read the script from config | |||
| 201 | // - run the tick function until needsReset is set | |||
| 202 | // Returns true if it should be re-called immediately, | |||
| 203 | // or false if there was a problem setting up the interpreter | |||
| 204 | // or parsing the script. | |||
| 205 | static bool runOneLua(lua_Alloc alloc, const char* script) { | |||
| 206 | needsReset = false; | |||
| 207 | ||||
| 208 | auto ls = setupLuaState(alloc); | |||
| 209 | ||||
| 210 | // couldn't start Lua interpreter, bail out | |||
| 211 | if (!ls) { | |||
| 212 | return false; | |||
| 213 | } | |||
| 214 | ||||
| 215 | // Reset default tick rate | |||
| 216 | luaTickPeriodUs = MS2US(5); | |||
| 217 | ||||
| 218 | if (!loadScript(ls, script)) { | |||
| 219 | return false; | |||
| 220 | } | |||
| 221 | ||||
| 222 | while (!needsReset && !chThdShouldTerminateX()) { | |||
| 223 | efitick_t beforeNt = getTimeNowNt(); | |||
| 224 | #if EFI_CAN_SUPPORT | |||
| 225 | // First, process any pending can RX messages | |||
| 226 | totalRxCount += recentRxCount; | |||
| 227 | recentRxCount = doLuaCanRx(ls); | |||
| 228 | rxTime = getTimeNowNt() - beforeNt; | |||
| 229 | #endif // EFI_CAN_SUPPORT | |||
| 230 | ||||
| 231 | // Next, check if there is a pending interactive command entered by the user | |||
| 232 | doInteractive(ls); | |||
| 233 | ||||
| 234 | invokeTick(ls); | |||
| 235 | ||||
| 236 | engine->outputChannels.luaLastCycleDuration = (getTimeNowNt() - beforeNt); | |||
| 237 | engine->outputChannels.luaInvocationCounter++; | |||
| 238 | chThdSleep(TIME_US2I(luaTickPeriodUs)); | |||
| 239 | ||||
| 240 | engine->engineState.luaDigitalState0 = getAuxDigital(0); | |||
| 241 | engine->engineState.luaDigitalState1 = getAuxDigital(1); | |||
| 242 | engine->engineState.luaDigitalState2 = getAuxDigital(2); | |||
| 243 | engine->engineState.luaDigitalState3 = getAuxDigital(3); | |||
| 244 | } | |||
| 245 | ||||
| 246 | resetLua(); | |||
| 247 | ||||
| 248 | return true; | |||
| 249 | } | |||
| 250 | ||||
| 251 | void LuaThread::ThreadTask() { | |||
| 252 | while (!chThdShouldTerminateX()) { | |||
| 253 | bool wasOk = runOneLua(luaHeapAlloc, config->luaScript); | |||
| 254 | ||||
| 255 | auto usedAfterRun = luaHeapUsed(); | |||
| 256 | if (usedAfterRun != 0) { | |||
| 257 | if (!withErrorLoading) { | |||
| 258 | efiPrintf(TAG "MEMORY LEAK DETECTED: %d bytes used after teardown", usedAfterRun); | |||
| 259 | } | |||
| 260 | ||||
| 261 | // Lua blew up in some terrible way that left memory allocated, reset the heap | |||
| 262 | // so that subsequent runs don't overflow the heap | |||
| 263 | luaHeapReset(); | |||
| 264 | } | |||
| 265 | ||||
| 266 | // Reset any lua adjustments the script made | |||
| 267 | engine->resetLua(); | |||
| 268 | ||||
| 269 | if (!wasOk) { | |||
| 270 | // Something went wrong executing the script, spin | |||
| 271 | // until reset invoked (maybe the user fixed the script) | |||
| 272 | while (!needsReset) { | |||
| 273 | chThdSleepMilliseconds(100); | |||
| 274 | } | |||
| 275 | } | |||
| 276 | } | |||
| 277 | } | |||
| 278 | ||||
| 279 | static LuaThread luaThread; | |||
| 280 | ||||
| 281 | void startLua() { | |||
| 282 | luaHeapInit(); | |||
| 283 | ||||
| 284 | #if EFI_CAN_SUPPORT | |||
| 285 | initLuaCanRx(); | |||
| 286 | #endif // EFI_CAN_SUPPORT | |||
| 287 | ||||
| 288 | addConsoleActionII("set_lua_setting", [](int index, int value) { | |||
| 289 | engineConfiguration->scriptSetting[index] = value; | |||
| 290 | }); | |||
| 291 | ||||
| 292 | luaThread.start(); | |||
| 293 | ||||
| 294 | addConsoleActionS("lua", [](const char* str){ | |||
| 295 | if (interactivePending) { | |||
| 296 | return; | |||
| 297 | } | |||
| 298 | ||||
| 299 | strncpy(interactiveCmd, str, sizeof(interactiveCmd) - 1); | |||
| 300 | interactiveCmd[sizeof(interactiveCmd) - 1] = '\0'; | |||
| 301 | ||||
| 302 | interactivePending = true; | |||
| 303 | }); | |||
| 304 | ||||
| 305 | addConsoleAction("luareset", [](){ | |||
| 306 | needsReset = true; | |||
| 307 | }); | |||
| 308 | ||||
| 309 | addConsoleAction("luamemory", [](){ | |||
| 310 | efiPrintf("maxLuaDuration %lu", maxLuaDuration); | |||
| 311 | maxLuaDuration = 0; | |||
| 312 | efiPrintf("rx total/recent/dropped %d %d %d", totalRxCount, | |||
| 313 | recentRxCount, getLuaCanRxDropped()); | |||
| 314 | efiPrintf("luaCycle %luus including luaRxTime %dus", NT2US(engine->outputChannels.luaLastCycleDuration), | |||
| 315 | NT2US(rxTime)); | |||
| 316 | ||||
| 317 | luaHeapPrintInfo(); | |||
| 318 | }); | |||
| 319 | } | |||
| 320 | ||||
| 321 | #else // not EFI_UNIT_TEST | |||
| 322 | ||||
| 323 | 1 | void startLua() { } | ||
| 324 | ||||
| 325 | #include <stdexcept> | |||
| 326 | #include <string> | |||
| 327 | ||||
| 328 | 100 | static LuaHandle runScript(const char* script) { | ||
| 329 | 100 | auto ls = setupLuaState(luaHeapAlloc); | ||
| 330 | ||||
| 331 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 100 times.
|
100 | if (!ls) { |
| 332 | ✗ | throw std::logic_error("Call to setupLuaState failed, returned null"); | ||
| 333 | } | |||
| 334 | ||||
| 335 |
3/3✓ Branch 1 taken 100 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 99 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 99 times.
|
100 | if (!loadScript(ls, script)) { |
| 336 |
1/1✓ Branch 2 taken 1 time.
|
1 | throw std::logic_error("Call to loadScript failed"); | |
| 337 | } | |||
| 338 | ||||
| 339 |
1/1✓ Branch 2 taken 99 times.
|
99 | lua_getglobal(ls, "testFunc"); | |
| 340 |
3/3✓ Branch 2 taken 99 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 98 times.
|
2/2✓ Decision 'true' taken 1 time.
✓ Decision 'false' taken 98 times.
|
99 | if (lua_isnil(ls, -1)) { |
| 341 |
1/1✓ Branch 2 taken 1 time.
|
1 | throw std::logic_error("Failed to find function testFunc"); | |
| 342 | } | |||
| 343 | ||||
| 344 |
1/1✓ Branch 2 taken 98 times.
|
98 | int status = lua_pcall(ls, 0, 1, 0); | |
| 345 | ||||
| 346 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 98 times.
|
98 | if (0 != status) { |
| 347 | ✗ | std::string msg = std::string("lua error while running script: ") + lua_tostring(ls, -1); | ||
| 348 | ✗ | throw std::logic_error(msg); | ||
| 349 | ✗ | } | ||
| 350 | ||||
| 351 | 98 | return ls; | ||
| 352 | 2 | } | ||
| 353 | ||||
| 354 | 83 | expected<float> testLuaReturnsNumberOrNil(const char* script) { | ||
| 355 |
1/1✓ Branch 2 taken 83 times.
|
83 | auto ls = runScript(script); | |
| 356 | ||||
| 357 | // check nil return first | |||
| 358 |
3/3✓ Branch 2 taken 83 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 78 times.
|
2/2✓ Decision 'true' taken 5 times.
✓ Decision 'false' taken 78 times.
|
83 | if (lua_isnil(ls, -1)) { |
| 359 | 5 | return unexpected; | ||
| 360 | } | |||
| 361 | ||||
| 362 | // If not nil, it should be a number | |||
| 363 |
2/3✓ Branch 2 taken 78 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 78 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 78 times.
|
78 | if (!lua_isnumber(ls, -1)) { |
| 364 | ✗ | throw std::logic_error("Returned value is not a number"); | ||
| 365 | } | |||
| 366 | ||||
| 367 | // pop the return value | |||
| 368 |
1/1✓ Branch 3 taken 78 times.
|
78 | return lua_tonumber(ls, -1); | |
| 369 | 83 | } | ||
| 370 | ||||
| 371 | 11 | float testLuaReturnsNumber(const char* script) { | ||
| 372 |
1/1✓ Branch 2 taken 11 times.
|
11 | auto ls = runScript(script); | |
| 373 | ||||
| 374 | // check the return value | |||
| 375 |
2/3✓ Branch 2 taken 11 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 11 times.
|
11 | if (!lua_isnumber(ls, -1)) { |
| 376 | ✗ | throw new std::logic_error("Returned value is not a number"); | ||
| 377 | } | |||
| 378 | ||||
| 379 | // pop the return value | |||
| 380 |
1/1✓ Branch 2 taken 11 times.
|
22 | return lua_tonumber(ls, -1); | |
| 381 | 11 | } | ||
| 382 | ||||
| 383 | 6 | int testLuaReturnsInteger(const char* script) { | ||
| 384 |
1/1✓ Branch 2 taken 4 times.
|
6 | auto ls = runScript(script); | |
| 385 | ||||
| 386 | // pop the return value; | |||
| 387 |
3/3✓ Branch 2 taken 4 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
|
2/2✓ Decision 'true' taken 2 times.
✓ Decision 'false' taken 2 times.
|
4 | if (!lua_isinteger(ls, -1)) { |
| 388 |
1/1✓ Branch 2 taken 2 times.
|
2 | throw std::logic_error("Returned value is not an integer"); | |
| 389 | } | |||
| 390 | ||||
| 391 |
1/1✓ Branch 2 taken 2 times.
|
4 | return lua_tointeger(ls, -1); | |
| 392 | 4 | } | ||
| 393 | ||||
| 394 | 197 | void testLuaExecString(const char* script) { | ||
| 395 |
1/1✓ Branch 2 taken 197 times.
|
197 | auto ls = setupLuaState(luaHeapAlloc); | |
| 396 | ||||
| 397 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 197 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 197 times.
|
197 | if (!ls) { |
| 398 | ✗ | throw std::logic_error("Call to setupLuaState failed, returned null"); | ||
| 399 | } | |||
| 400 | ||||
| 401 |
3/3✓ Branch 1 taken 197 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 191 times.
|
2/2✓ Decision 'true' taken 6 times.
✓ Decision 'false' taken 191 times.
|
197 | if (!loadScript(ls, script)) { |
| 402 |
1/1✓ Branch 2 taken 6 times.
|
6 | throw std::logic_error("Call to loadScript failed"); | |
| 403 | } | |||
| 404 | 388 | } | ||
| 405 | ||||
| 406 | #endif // EFI_UNIT_TEST | |||
| 407 | #endif // EFI_LUA | |||
| 408 | ||||
| 409 | // This is technically non-compliant, but it's only used for lua float parsing. | |||
| 410 | // It doesn't properly handle very small and very large numbers, and doesn't | |||
| 411 | // parse numbers in the format 1.3e5 at all. | |||
| 412 | 68 | extern "C" float strtof_rusefi(const char* str, char** endPtr) { | ||
| 413 | 68 | bool afterDecimalPoint = false; | ||
| 414 | 68 | float div = 1; // Divider to place digits after the decimal point | ||
| 415 | ||||
| 416 |
1/2✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 68 times.
✗ Decision 'false' not taken.
|
68 | if (endPtr) { |
| 417 | 68 | *endPtr = const_cast<char*>(str); | ||
| 418 | } | |||
| 419 | ||||
| 420 | 68 | float integerPart = 0; | ||
| 421 | 68 | float fractionalPart = 0; | ||
| 422 | ||||
| 423 |
2/2✓ Branch 0 taken 322 times.
✓ Branch 1 taken 67 times.
|
2/2✓ Decision 'true' taken 322 times.
✓ Decision 'false' taken 67 times.
|
389 | while (*str != '\0') { |
| 424 | 322 | char c = *str; | ||
| 425 | 322 | int digitVal = c - '0'; | ||
| 426 | ||||
| 427 |
4/4✓ Branch 0 taken 255 times.
✓ Branch 1 taken 67 times.
✓ Branch 2 taken 254 times.
✓ Branch 3 taken 1 time.
|
2/2✓ Decision 'true' taken 254 times.
✓ Decision 'false' taken 68 times.
|
322 | if (c >= '0' && c <= '9') { |
| 428 |
2/2✓ Branch 0 taken 93 times.
✓ Branch 1 taken 161 times.
|
2/2✓ Decision 'true' taken 93 times.
✓ Decision 'false' taken 161 times.
|
254 | if (!afterDecimalPoint) { |
| 429 | // Integer part | |||
| 430 | 93 | integerPart = 10 * integerPart + digitVal; | ||
| 431 | } else { | |||
| 432 | // Fractional part | |||
| 433 | 161 | fractionalPart = 10 * fractionalPart + digitVal; | ||
| 434 | 161 | div *= 10; | ||
| 435 | } | |||
| 436 |
2/2✓ Branch 0 taken 67 times.
✓ Branch 1 taken 1 time.
|
2/2✓ Decision 'true' taken 67 times.
✓ Decision 'false' taken 1 time.
|
68 | } else if (c == '.') { |
| 437 | 67 | afterDecimalPoint = true; | ||
| 438 | } else { | |||
| 439 | 1 | break; | ||
| 440 | } | |||
| 441 | ||||
| 442 | 321 | str++; | ||
| 443 | ||||
| 444 |
1/2✓ Branch 0 taken 321 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 321 times.
✗ Decision 'false' not taken.
|
321 | if (endPtr) { |
| 445 | 321 | *endPtr = const_cast<char*>(str); | ||
| 446 | } | |||
| 447 | } | |||
| 448 | ||||
| 449 | 68 | return integerPart + fractionalPart / div; | ||
| 450 | } | |||
| 451 |