
前回に引き続き、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とか書かずに作業していてわかんなくならないか?泥縄。

