前回に引き続き、Arduino環境用に書いた関数をWSL2(Ubuntu)上でGoogle-testしています。多少進捗があったのは「文字列で書いた16進数の計算式」を「評価」して結果を数値で返せるところまでテストが進んだことです。160行ばかりの短いコードだけれども一応インタプリタもどき。今のところ即値の計算のみ。
※「IoT何をいまさら」投稿順Indexはこちら
ArduinoIDEから関数コピーしてテストを続ける
前回WSL2上のUbuntu環境でgoogle-testをCMakeできるようになったので、淡々とテストを続けてます。といってもVScodeで作業しているので、Arduino側のソースをUbuntu側にコピペして走らせるだけですが。まあ、イチイチ実機(ターゲット機はESP32C2番のXiao)のFlashに書き込んで動作を調べるよりかはよっぽど楽。結果、ぽろぽろとエラーを発見し、淡々と修正しとります。
今回のコピペと修正にて、一応以下の機能を果たせそうな関数群ができつつあります(まだ作業中です。)
-
- 文字列で与えた16進数を計算して数値で結果を返せる
- 演算子としては今のところ、+、-、*、&(ビットAND)、|(ビットOR)、^(ビットXOR)、~(ビット反転)が使える
- 掛け算の優先順位が高く、他は同レベル
- マルカッコ”()”で優先順位は変更可能
現時点での「インタプリタ」の被テスト関数ソースが以下に。evalU32exp(char *buf, uint32_t *result) という「仮の」トップ関数あり、bufに文字列へのポインタを渡し、resultに結果格納先のuint32_t型へのポインタを渡して呼び出せば、文字列の内容を評価して、結果の数値をresultに戻します。なお、関数そのものの返り値としてエラーがあればtrueを返します。
#include "undertest.hpp" extern char buf[]; //--- Under test ------------------- int currentIndex; int lastIndex; bool errFlag; bool errNumber; bool nextCh() { while (isSpace(buf[currentIndex])) { currentIndex++; } if (currentIndex >= lastIndex) { return false; } return true; } char toUpper(char arg) { if ((arg >= 'a') && (arg <= 'z')) { return arg - 32; } return arg; } bool toHex(char arg, uint32_t *res) { char work = toUpper(arg); if ((work >= '0') && (work <= '9')) { *res = (uint32_t)(work - '0'); return true; } if ((work >= 'A') && (work <= 'F')) { *res = (uint32_t)(work - 'A' + 10); return true; } return false; } uint32_t numberU32(char *buf) { uint32_t sum, temp; bool bitNotFlag = false; if (buf[currentIndex] == '~') { bitNotFlag = true; currentIndex++; nextCh(); } if (! isdigit(buf[currentIndex])) { //first character must be 0-9 errFlag = true; errNumber = 1; return 0; } sum = (uint32_t)(buf[currentIndex++] - '0'); while (nextCh() && toHex(buf[currentIndex], &temp)) { sum = (sum << 4) + temp; currentIndex++; } return bitNotFlag ? ~sum : sum; } uint32_t factorU32(char *buf) { uint32_t temp; if (buf[currentIndex] != '(') return numberU32(buf); currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 2; return 0; } temp = expU32(buf); if (buf[currentIndex] != ')') { errFlag = true; errNumber = 3; return 0; } currentIndex++; nextCh(); return temp; } uint32_t termU32(char *buf) { uint32_t temp = factorU32(buf); while (true) { if (buf[currentIndex] == '*') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 4; return 0; } temp *= factorU32(buf); } else { break; } } return temp; } uint32_t expU32(char *buf) { uint32_t temp = termU32(buf); while (true) { if (!nextCh()) { break; } if (buf[currentIndex] == '+') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 5; return 0; } temp += termU32(buf); } else if (buf[currentIndex] == '-') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 6; return 0; } temp -= termU32(buf); } else if (buf[currentIndex] == '&') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 7; return 0; } temp &= termU32(buf); } else if (buf[currentIndex] == '|') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 8; return 0; } temp |= termU32(buf); } else if (buf[currentIndex] == '^') { currentIndex++; if (!nextCh()) { errFlag = true; errNumber = 9; return 0; } temp ^= termU32(buf); } else { break; } } return temp; } bool evalU32exp(char *buf, uint32_t *result) { currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; nextCh(); *result = expU32(buf); return errFlag; }
別シリーズ記事からのフィードバック
前回時点の被テスト関数ソースを使い、別シリーズの以下記事にて、gcov(コードカバレッジツール)も掛けてみてます。
オプション沼(13) コードカバレッジを計測する準備、gcc ‐‐coverageオプション
結果、numberU32関数内に未テスト部分が洗い出されたので、以下ではTESTを追加しています。今回追加部分はカバレッジを見ていないので、多分、ザル。またカバレッジを測定して、TESTを追加しなければ。。。
TESTフィクスチャ・コード
今回から「再帰下降型構文解析」っぽい部分に突入したので、大分TESTフィクスチャが長くなりました。「再帰」なのでちょいと気を抜くと戻ってこなくなります。Arduino環境でこれをやるとデバッグ大変ですが、Ubuntu上なら暴走し放題?だあ、と。とりあえずの暫定ソースが以下に。
#include <gtest/gtest.h> #include "undertest.hpp" char buf[256]; TEST(testrunner, nextCh) { strcpy(buf, " 1234\t 567"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_TRUE(nextCh()); EXPECT_EQ(currentIndex, 2); currentIndex += 4; EXPECT_TRUE(nextCh()); EXPECT_EQ(currentIndex, 8); } TEST(testrunner, toUpper) { EXPECT_EQ(toUpper('a'), 'A'); EXPECT_EQ(toUpper('z'), 'Z'); EXPECT_EQ(toUpper('1'), '1'); EXPECT_EQ(toUpper('X'), 'X'); EXPECT_EQ(toUpper('/'), '/'); } TEST(testrunner, toHex) { uint32_t work; EXPECT_TRUE(toHex('a', &work)); EXPECT_EQ(work, 10); EXPECT_TRUE(toHex('0', &work)); EXPECT_EQ(work, 0); EXPECT_TRUE(toHex('9', &work)); EXPECT_EQ(work, 9); EXPECT_TRUE(toHex('F', &work)); EXPECT_EQ(work, 15); EXPECT_FALSE(toHex('.', &work)); EXPECT_FALSE(toHex('G', &work)); EXPECT_FALSE(toHex('x', &work)); EXPECT_FALSE(toHex('%', &work)); } TEST(testrunner, numberU32) { strcpy(buf, "1234"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0x1234); EXPECT_EQ(currentIndex, 4); strcpy(buf, "0FF"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0xFF); EXPECT_EQ(currentIndex, 3); strcpy(buf, "~0FFFFFF80"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0x7F); EXPECT_EQ(currentIndex, 10); EXPECT_FALSE(errFlag); EXPECT_EQ(errNumber, 0); strcpy(buf, "FFFFFF80"); //TEST HOLE. currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(numberU32(buf), 0); EXPECT_TRUE(errFlag); EXPECT_EQ(errNumber, 1); } TEST(testrunner, expU32) { strcpy(buf, "1234"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x1234); EXPECT_EQ(currentIndex, 4); strcpy(buf, "1234 + 1"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x1235); EXPECT_EQ(currentIndex, 8); strcpy(buf, "1234 - 1"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x1233); EXPECT_EQ(currentIndex, 8); strcpy(buf, "1234 & 4"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x4); EXPECT_EQ(currentIndex, 8); strcpy(buf, "1234 | 3"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x1237); EXPECT_EQ(currentIndex, 8); strcpy(buf, "1234 ^ 1231"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x5); EXPECT_EQ(currentIndex, 11); strcpy(buf, "1 + 1234 * 2"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x2469); EXPECT_EQ(currentIndex, 12); strcpy(buf, "2 * (1234 + 1)"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x246A); EXPECT_EQ(currentIndex, 14); } TEST(testrunner, termU32) { strcpy(buf, "1234 * 2"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x2468); EXPECT_EQ(currentIndex, 8); } TEST(testrunner, factorU32) { strcpy(buf, "(1234)"); currentIndex = 0; lastIndex = strlen(buf); errFlag = false; errNumber = 0; EXPECT_EQ(expU32(buf), 0x1234); EXPECT_EQ(currentIndex, 6); } TEST(testrunner, evalU32exp) { uint32_t result; strcpy(buf, "1234"); EXPECT_FALSE(evalU32exp(buf,&result)); EXPECT_EQ(result, 0x1234); strcpy(buf, "2 * (1234 + 1)"); EXPECT_FALSE(evalU32exp(buf, &result)); EXPECT_EQ(result, 0x246A); }
Goolge-testの結果
テスト結果が以下に。多分ザルだけれどもとりあえずテストは通っておると。
次回は、変数への格納と呼び出し?それとも組み込み関数?それにしてもBNFとか書かずに作業していてわかんなくならないか?泥縄。