IoT何をいまさら(121) インタプリタもどき、WSL2上でGoogle-testその2

Joseph Halfmoon

前回、ユニットテスト用のWSL2環境上でですが、16進数と演算子からなる文字列を「インタプリタもどき」に渡して、お答えをもらうところまで作りました。今回はその続き、変数を導入です。数式の中に変数名を書いておくと、数値にして計算に使ってくれるもの。とりあえず変数の参照だけで、代入はまだなんだけれども。

※「IoT何をいまさら」投稿順Indexはこちら

被ユニットテストに追加した関数

しょぼくれた「インタプリタもどき」なので、変数名に縛りを想定しています。

    • 英大文字先頭で英数字か”$”, “@”, “_” がつづく5文字まで
    • 英文字は大文字小文字の区別なし

まだ上記はちゃんとチェックしてませんが、そのうちね。

結果、全ての変数はuint32_t 型のユニークなID番号を持つことになります。今回新たに追加した関数は以下の3つです。

    1. bool searchIdU32(uint32_t id, uint32_t *res);
    2. bool toIdU32(char *buf, uint32_t *res);
    3. bool toIdChar(char arg, uint32_t *res);

最初の searchIdU32は、第1引数に変数IDを与えると、resの指す変数にその変数の値を代入するもの。発見できない場合は偽を返します。

第2のtoIdU32は、文字列バッファへのポインタを渡すと、ポインタの先にある変数名文字列をID番号に変換し、resの指す変数に代入するもの。やはり変換不能(不正な文字など)ある場合には偽を返します。

第3のtoldCharは、文字を与えると、ID番号につかう6bit幅の番号をresの指す変数に代入するもの。変換不能文字にあたると偽を返します。

undertest.cpp に追加したソースが以下に。

bool toIdChar(char arg, uint32_t *res) {
  if ((arg >= '0') && (arg <= '9')) {
    *res = (uint32_t)(arg - 48);
    return true;
  }
  arg = toUpper(arg);
  if ((arg >= 'A') && (arg <= 'Z')) {
    *res = (uint32_t)(arg - 55);
    return true;
  }
  if (arg == '@') {
    *res = 36;
    return true;
  }
  if (arg == '$') {
    *res = 37;
    return true;
  }
  if (arg == '_') {
    *res = 63;
    return true;
  }
  return false;
}

bool toIdU32(char *buf, uint32_t *res) {
  uint32_t id = 0;
  int nameCount=0;
  uint32_t work;
  while (nameCount < 5) {
    if (toIdChar(buf[currentIndex], &work)) {
      id = (id << 6) | work;
      nameCount++;
      currentIndex++;
    } else {
      break;
    }
  }
  if (nameCount == 0) {
    return false;
  }
  *res = id;
  return true;
}

bool searchIdU32(uint32_t id, uint32_t *res) {
  int idx=0;
  while (0 != idtable[idx][0]) {
    if (id == idtable[idx][0]) {
      *res = idtable[idx][1];
      return true; 
    }
    idx++;
  }
  return false;
}

なお、あとでCしか使えないマイコン上にも移植するつもりなので、Arduino風味のC++でも限りなくCに近い感じにしてます。

上記の関数群の新設にともない、既存のnumberU32(char *buf)関数に変更を加えてます。numberU32()は、ポインタの場所にある16進文字列を数値に変換する関数でしたが、数値でない(0-9までの数字で始まらない場合。16進ABCDEFの場合は先頭に0をつければ数値になる)場合、変数テーブルを検索して変数の値に「変換」しようと試みるように改造しました。修正した関数が以下に。

uint32_t numberU32(char *buf) {
  uint32_t sum, temp, id, u32val;
  bool bitNotFlag = false;
  if (buf[currentIndex] == '~') {
    bitNotFlag = true;
    currentIndex++;
    nextCh();
  }
  if (! isdigit(buf[currentIndex])) { //first character must be 0-9
    if (toIdU32(buf, &id)) { //variable
      if (searchIdU32(id, &u32val)) {
        sum = u32val;
        nextCh();
      } else {
        errFlag = true;
        errNumber = 1;
        return 0;
      }
    } else {
      errFlag = true;
      errNumber = 1;
      return 0;
    }    
  } else  {
    sum = (uint32_t)(buf[currentIndex++] - '0');
    while (nextCh() && toHex(buf[currentIndex], &temp)) {
      sum = (sum << 4) + temp;
      currentIndex++;
    }
  }
  return bitNotFlag ? ~sum : sum;
}
ヘッダファイルundertest.hppの修正

関数を追加したのでユニットテスト本体にインクルードされるヘッダにも修正が入ってます。

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>

#define isSpace(arg)    isspace(arg)

#define MAXID   (32)

extern int currentIndex;
extern int lastIndex;
extern bool errFlag;
extern bool errNumber;

bool nextCh();
char toUpper(char arg);
bool toHex(char arg, uint32_t *res);
uint32_t numberU32(char *buf);

uint32_t factorU32(char *buf);
uint32_t termU32(char *buf);
uint32_t expU32(char *buf);
bool evalU32exp(char *buf, uint32_t *result);

bool searchIdU32(uint32_t id, uint32_t *res);
bool toIdU32(char *buf, uint32_t *res);
bool toIdChar(char arg, uint32_t *res);ゆ
ユニットテストの追加

さて新設関数と新設関数の影響をうけた関数用の追加のテストフィクスチャーが以下に。

#include <gtest/gtest.h>
#include "undertest.hpp"

char buf[256];
uint32_t idtable[MAXID][2];

// 途中略

TEST(testrunner, toIdChar) {
    uint32_t result;
    EXPECT_TRUE(toIdChar('0',&result));
    EXPECT_EQ(result, 0);
    EXPECT_TRUE(toIdChar('9',&result));
    EXPECT_EQ(result, 9);
    EXPECT_TRUE(toIdChar('A',&result));
    EXPECT_EQ(result, 10);
    EXPECT_TRUE(toIdChar('Z',&result));
    EXPECT_EQ(result, 35);
    EXPECT_TRUE(toIdChar('a',&result));
    EXPECT_EQ(result, 10);
    EXPECT_TRUE(toIdChar('z',&result));
    EXPECT_EQ(result, 35);
    EXPECT_TRUE(toIdChar('@',&result));
    EXPECT_EQ(result, 36);
    EXPECT_TRUE(toIdChar('$',&result));
    EXPECT_EQ(result, 37);
    EXPECT_TRUE(toIdChar('_',&result));
    EXPECT_EQ(result, 63);
    EXPECT_FALSE(toIdChar('+',&result));
    EXPECT_FALSE(toIdChar('-',&result));
    EXPECT_FALSE(toIdChar('*',&result));
    EXPECT_FALSE(toIdChar('/',&result));
}

TEST(testrunner, toIdU32) {
    uint32_t result;
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    strcpy(buf, "AAAAA");
    EXPECT_TRUE(toIdU32(buf,&result));
    EXPECT_EQ(result, 0xa28a28a);
    currentIndex = 0;
    strcpy(buf, "Z");
    EXPECT_TRUE(toIdU32(buf,&result));
    EXPECT_EQ(result, 0x23);
 }

 TEST(testrunner, searchIdU32) {
    uint32_t result;
    idtable[0][0]=0xa28a28a;
    idtable[0][1]=0x12345678;
    idtable[1][0]=0x23;
    idtable[1][1]=0xFFFFFFFF;
    idtable[2][0]=0;
    EXPECT_TRUE(searchIdU32(0xa28a28a,&result));
    EXPECT_EQ(result, 0x12345678);
    EXPECT_TRUE(searchIdU32(0x23,&result));
    EXPECT_EQ(result, 0xFFFFFFFF);
    EXPECT_FALSE(searchIdU32(0,&result));
    EXPECT_FALSE(searchIdU32(100,&result));
 }

 TEST(testrunner, expU32_V) {
    strcpy(buf, "AAAAA");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    idtable[0][0]=0xa28a28a;
    idtable[0][1]=0x12345678;
    idtable[1][0]=0x23;
    idtable[1][1]=0xFFFFFFFF;
    idtable[2][0]=0;
    EXPECT_EQ(expU32(buf), 0x12345678);
    EXPECT_EQ(currentIndex, 5);
    strcpy(buf, "AAAAA + 1");
    currentIndex = 0;
    lastIndex = strlen(buf);
    errFlag = false;
    errNumber = 0;
    EXPECT_EQ(expU32(buf), 0x12345679);
    EXPECT_EQ(currentIndex, 9);
 }
実機確認

長くなってきたので、結果のボトムライン付近のみです。過去のテスト(一応リグレッションテストになるか)も含めて全テスト・パスです(穴だらけだけれども。)

testResults

変数名を含む数式の文字列を計算できるようになったので、次は変数に代入するところだな。しかし、そろそろ文法をちゃんと記述した方がいいんでないかい、「もどき」とは言え。半月休んだだけで忘れてるし。

IoT何をいまさら(120) 続Arduino用ソースをWSL2上でGoogle-test へ戻る

IoT何をいまさら(122) 泥縄で思い出す。インタプリタもどきの文法をEBNF化 へ進む