="LPC1114 デバッガで操作する計算機を作ろう

  NXPセミコンダクタ社LPCXpresso 基板は, fgets() 関数と fputs() 関数を使って,開発環境からデータを入出力して,インタプリタを操作することができます.本格的に構文解析をせさてみましょう.

計算機の概要

 ここで紹介するアプリケーションは,開発環境のコンソールを使用した卓上計算機です.ただし,使用できる演算子は,加算・減算のみというかなり低機能なものです.文法をバッカス記法で書くと,以下のようになります.

<数字>   ::= 0|1|2|3|4|5|6|7|8|9
<演算子> ::= +|-
<整数>   ::= <数字>|<整数><数字>
<式>     ::= <整数>|<式><演算子><整数>

 この文法に従って,コンソールからの入力を解析し,計算結果を出力します.

入力する数式の構文図 さっそく,プログラム

 今まで同様,プロジェクト proj02 を変更してプログラムを構成します.プログラムに使用するファイルは, main.c だけです.さっそく,順を追って解説していきます.

ヘッダ部分

 ファイル main.c の先頭部分では,読み込むヘッダの数が増えました.追加されたヘッダ・ファイル ctype.h には,与えられた文字が数字であるかどうかを判断させる関数 isdigit() の宣言が含まれています.

/*
===============================================================
 Name        : main.c
 Author      : noritan
 Version     :
 Copyright   : Copyright (C) 2010 noritan.org
 Description : main definition
===============================================================
*/

#ifdef __USE_CMSIS
#include "LPC11xx.h"
#endif

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

const char      *p;     // Pointer to a buffer to be parsed.

 大域変数 p は,構文解析を行うときに使用されるポインタで,ライン・バッファ内の文字を指示しています.

整数の構文解析

 整数値の解析は,関数 const_int() で行います.

/*
-------------------------------------------------------
   int const_int() :
     Parse a constant integer value.

   int *result
     Pointer to the calculation result to be stored.

   Return value
     0: a constant integer is detected.
     1: non-digit first character detected.
-------------------------------------------------------
*/
int const_int(int *result) {
    int     value;      // Intermediate value.

    // Confirm the first character.
    if (!isdigit(*p)) return 1;

    // Parse the characters.
    value = (*p++ - '0');
    while (isdigit(*p)) {
        value = value * 10 + (*p++ - '0');
    }
    // Return the constant value.
    *result = value;
    return 0;
}

 この関数は,ポインタ p の位置に整数値があるという想定で呼び出されます.そのため,先頭に数字でないものが存在した場合には,エラーとして 1 を返します.

 整数の残りの部分について,数字が検出されなくなるまで,変数 value の値を更新し,得られた数値をポインタ result の指す場所に代入して, 0 を返します.オーバフローは,考慮していません.

式の構文解析

 式の解析は,関数 expr() で行います.

/*
-------------------------------------------------------
   int expr() :
     Parse an expression.

   int *result
     Pointer to the calculation result to be stored.

   Return value
     0: an expression is detected.
     1: an illegal character detected.
-------------------------------------------------------
*/
int expr(int *result) {
    int     value1;     // Left hand side value.
    int     value2;     // Right hand side value.
    int     err;        // Error code

    err = const_int(&value1);
    if (err) return err;
    while (1) {
        if (*p == '+') {
            // Add operator is detected.
            p++;
            err = const_int(&value2);
            if (err) return err;
            value1 = value1 + value2;
        } else if (*p == '-') {
            // Sub operator is detected.
            p++;
            err = const_int(&value2);
            if (err) return err;
            value1 = value1 - value2;
        } else {
            // Unknown character detected.
            break;
        }
    }
    // Return the expression value.
    *result = value1;
    return 0;
}

 この関数では,整数値解析を行う const_int() 関数を使用して,構文解析を行いながら,同時に演算を行っています.この例の場合には,加算と減算しか取り扱わないので,単純に if 文で分岐させることにしました.

 整数値が期待されるところでエラーが発生した場合には,そのエラー・コードをそのまま返します.それ以外は,正常に終了したものとして,得られた数値をポインタ result の指す場所に代入して, 0 を返します.

整数の表示

 printf() 関数を使わないことにしたので,整数を表示する処理も自分で書かなくてはなりません. fputdec() 関数は,与えられた整数値を与えられたファイルに表示する関数です.

/*
-------------------------------------------------------
   void fputdec() :
     Show a value as a decimal number.

   int value
     A value to be shown.
   FILE *fp
     Pointer to a FILE structure to be used to put
     the number.
-------------------------------------------------------
*/
void fputdec(int value, FILE *fp) {
    int     i;          // General purpose counter.
    int     m;          // Mask for decimal number parser.
    char    nbuf[12];   // Buffer to store the decimal number.

    // Maximum power of 10 number in 32-bit.
    m = 1000000000;
    // Find the first masking number.
    while ((m > 1) && (value < m)) {
        m /= 10;
    }
    // Parse the value into a decimal number.
    i = 0;
    while (m >= 1) {
        nbuf[i++] = '0' + (value / m);
        value %= m;
        m /= 10;
    }
    nbuf[i++] = 0;      // End of string.
    fputs(nbuf, fp);    // Put the decimal number.
}

 32ビットで表現される十進数の最大の桁である10億から始めて,1桁ずつ,バッファに数字を入れていき,最後に fputs() 関数で書き出す仕掛けです.

インタプリタ関数

 インタプリタ関数 interpret() のインターフェースは,前回のインタプリタのときと同じにしました.

/*
-------------------------------------------------------
   int interpret() :
     Parse a command line provided as a string.

   const char *line
     A command line to be parsed.

   Return value
     0: a next command is expected.
     1: a command to terminate parsing is detected.
-------------------------------------------------------
*/
int interpret(const char *line) {
    int     value;      // a value returned by parser.
    int     err;        // Error code.
    int     i;          // General purpose counter.

    // Escape from the program
    if (strncmp(line, "bye", 3) == 0) {
        fputs("Good bye.\n", stdout);
        return 1;
    }

    // Parsing
    p = line;           // Initialize the buffer pointer.
    err = expr(&value); // Parse as an expression.

    // Confirm the returned error code.
    if (err) {
      for (i = 0; i < p - line; i++) {
          fputs(" ", stdout);
      }
      fputs("^ error\n", stdout);
      return 0;
    }

    // Show the value parsed as expression
    fputdec(value, stdout);
    fputs("\n", stdout);
    return 0;
}

 前回同様,入力が "bye" で始まっていたら, 1 を返して,処理の終了をメイン・ルーチンに伝えます.

 それ以外の文字列は, expr() 関数で数式として構文解析します.その結果,エラーが発生した場合には,ポインタ p からエラーが発生した位置を特定し,エラー表示を行います.

 エラーが発生しなかった場合は, fputdec() 関数で得られた結果を表示して,メイン・ルーチンに戻ります.

メイン関数

インタプリタ関数のインターフェイスを変更しなかったので,メインルーチンは,多少,コメントは,追加されていますが,前回から変更されていません.

/*
-------------------------------------------------------
   int main() :
     Main routine.
-------------------------------------------------------
*/
int main(void) {
    char    line[64];   // Line buffer.
    int     done;       // Flag indicating a process done.

    // Show message
    fputs("HELLO WORLD\n", stdout);

    do {
        // Get a line
        fgets(line, sizeof line, stdin);

        // Interpret
        done = interpret(line);
    } while (!done);

    // Infinite loop.
    while (1) {
    }
    return 0;
}
実行しよう

 プログラムを書き込んだら,実行させます.

計算機の実行

 さすがに,コンソールの入出力をするだけでは,面白くないので,次回は,コンソールからハードウェアを操作してみます.

田中範明(noritan.org)


トラックバック(0)

このブログ記事を参照しているブログ一覧: LPCXpresso で HELLO WORLD (4)

このブログ記事に対するトラックバックURL: http://www.eleki-jack.com/mt/mt-tb.cgi/4129





カテゴリ


Copyright (C) 2006-2015 CQ Publishing Co.,Ltd. All Rights Reserved.