なんちゃってPascalを作ってみる(1)

プログラミング

こんにちは。

前回までで、普段使っている中置記法の数式を逆ポーランド記法に変換して計算するプログラムを作りました。

そうしたら、もしかしてプログラム言語も作れるんじゃないかと思い、試してみることにしました。

ただ、言語の設計から始めるのはとんでもなく大変になりそうなので、Pascalの一部を再現してみることにします。

Pascalにした理由

Pascalにした理由は、再帰下降によりひとつだけ先読みすれば構文解析できるLL(1)文法になっているので、作れそうじゃないかと思いました。

完全に見切り発車です。そのため、完成まで一直線では進まなく、やり直すこともあるような気がしながら進めていくことにします。

ちなみに、完全再現できるような気がしないので、「なんちゃってPascal」略して「NPascal」と呼ぶことにします。

トークンを入れるクラスを作る

token クラスを作ります。

ファイル名は token.h です。

#pragma once
#include<string>
using namespace std;

class token{
public:
    void setToken(string token,string t,int num){
        _token = token;
        _type = t;
        _lineNumber = num;
    }

    int getLineNumber(){
        return _lineNumber;
    }

    string getToken(){
        return _token;
    }

    string toFullString(){
        string res = "( ";
        res += to_string(_lineNumber);
        res += " , ";
        res += _type;
        res += " , ";
        res += _token;
        res += " )";
        return res;
    }

private:
    int _lineNumber;
    string _type;
    string _token;
};

字句解析器を作る

差し当たり字句解析器を作ってみます。

予約語とか変数とかあまり気にしないで、文字列として扱うことにします。

ファイル名は lexer.h です。

#pragma once
#include<string>
#include<iostream>
#include<sstream>
#include"token.h"
using namespace std;

class lexer{
public:
    static const char EMPTY = -1;

    lexer(char* fname){
        _fp = fopen(fname, "r");
    }

    token read(){
        ostringstream ss;
        token t;
        char c;
        do{
            c = getChar();
            if(c=='\r'||c=='\n'){
                _lineNumber++;
            }
            if(c==-1){
                return t;
            }
        } while (isSpace(c));

        if(c<0){
            return t;
        }else if(isDigit(c)){
            do{
                ss << c;
                c = getChar();
            } while (isDigit(c));
            t.setToken(ss.str(), "Dig", _lineNumber);
        }else if(isLetter(c)){
            do{
                ss << c;
                c = getChar();
            } while (isLetter(c) || isDigit(c));
            t.setToken(ss.str(), "Let", _lineNumber);
        }else if(c==':'){
            c = getChar();
            if(c=='=')  {
                t.setToken(":=", "Ass", _lineNumber);
                return t;
            }else{
                ungetChar(c);
                t.setToken(":", "Col", _lineNumber);
                return t;
            }
        }else if (c == '<'){
            c = getChar();
            if(c=='='){
                t.setToken("<=", "LeT", _lineNumber);
                return t;
            }else if(c=='>'){
                t.setToken("<>", "NEq", _lineNumber);
                return t;
            }else{
                ungetChar(c);
                t.setToken("<", "Les", _lineNumber);
                return t;
            }
        }else if (c == '>'){
            c = getChar();
            if(c=='='){
                t.setToken(">=", "GrT", _lineNumber);
                return t;
            }else{
                ungetChar(c);
                t.setToken(">", "Gre", _lineNumber);
                return t;
            }
        }else if (c == '='){
            t.setToken("=", "Equ", _lineNumber);
            return t;
        }else if (c == ';'){
            t.setToken(";", "SCo", _lineNumber);
            return t;
        }else if (c == ','){
            t.setToken(",", "Com", _lineNumber);
            return t;
        }else if (c == '('){
            t.setToken("(", "LPa", _lineNumber);
            return t;
       }else if (c == ')'){
            t.setToken(")", "RPa", _lineNumber);
            return t;
        }else if (c == '.'){
            t.setToken(".", "Dot", _lineNumber);
            return t;
        }

        if(c>0){
            ungetChar(c);
        }
        return t;
    }

private:
    char _lastChar = EMPTY;
    int _lineNumber = 1;
    FILE *_fp;

    char getChar(){
        if (_lastChar==EMPTY){
            return fgetc(_fp);
        }else{
            char c = _lastChar;
            _lastChar = EMPTY;
            return c;
        }
    }

    void ungetChar(char c){
        _lastChar = c;
    }

    bool isDigit(char c){
        return '0' <= c && c <= '9';
    }

    bool isLetter(char c){
        return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z';
    }

    bool isSpace(char c){
        return 0 <= c && c <= ' ';
    }
};

うーん、きれいでない・・・

NPascalのプログラムを作る

とりあえず、ユークリッドの互除法で最大公約数を求めることができるくらいを目指すしたいと思います。

ということで、NPascal のプログラムを作っておきます。

program euclid;
var a,b,r: integer;
begin
    a:=1234;
    b:=9753;
    if b>a then begin
        r:=a;
        a:=b;
        b:=r;
    end;
    repeat
        r:=a mod b;
        a:=b;
        b:=r;
    until b=0;
    writeln(a);
end.

このプログラムが動作することを目指します。

字句解析器を試してみる

字句解析器が動作するか確かめるプログラムを作ります。

#include"token.h"
#include"lexer.h"
#include<iostream>
using namespace std;

int main(int argc, char* argv[]){

    char fn[11] = "euclid.pas";
    lexer lex(fn);
    token ret;
    ret = lex.read();
    while (ret.getToken()!=""){
        cout << ret.toFullString() << endl;
        ret = lex.read();
    }
}

それでは実行してみます。

( 1 , Let , program )
( 1 , Let , euclid )
( 1 , SCo , ; )
( 2 , Let , var )
( 2 , Let , a )
( 2 , Com , , )
( 2 , Let , b )
( 2 , Com , , )
( 2 , Let , r )
( 2 , Col , : )
( 2 , Let , integer )
( 2 , SCo , ; )
( 3 , Let , begin )
( 4 , Let , a )
( 4 , Ass , := )
( 4 , Dig , 1234 )
( 4 , SCo , ; )
( 5 , Let , b )
( 5 , Ass , := )
( 5 , Dig , 9753 )
( 5 , SCo , ; )
( 6 , Let , if )
( 6 , Let , b )
( 6 , Gre , > )
( 6 , Let , a )
( 6 , Let , then )
( 6 , Let , begin )
( 7 , Let , r )
( 7 , Ass , := )
( 7 , Let , a )
( 7 , SCo , ; )
( 8 , Let , a )
( 8 , Ass , := )
( 8 , Let , b )
( 8 , SCo , ; )
( 9 , Let , b )
( 9 , Ass , := )
( 9 , Let , r )
( 9 , SCo , ; )
( 10 , Let , end )
( 10 , SCo , ; )
( 11 , Let , repeat )
( 12 , Let , r )
( 12 , Ass , := )
( 12 , Let , a )
( 12 , Let , mod )
( 12 , Let , b )
( 12 , SCo , ; )
( 13 , Let , a )
( 13 , Ass , := )
( 13 , Let , b )
( 13 , SCo , ; )
( 14 , Let , b )
( 14 , Ass , := )
( 14 , SCo , ; )
( 15 , Let , until )
( 15 , Let , b )
( 15 , Equ , = )
( 15 , Dig , 0 )
( 15 , SCo , ; )
( 16 , Let , writeln )
( 16 , LPa , ( )
( 16 , Let , a )
( 16 , RPa , ) )
( 16 , SCo , ; )
( 17 , Let , end )
( 17 , Dot , . )

一応、字句解析できていそうです。

今回はこれでおしまいにします。

それではまた。

Posted by 春日井 優