imHo RSSフィード

2011-01-09

Bisonの多機能電卓の変更

昨日のこともあるけど、定期的にスクリプト言語作りたい熱が発生する。まあできることからコツコツと、Bisonを使ったパーサの演習。多機能電卓では関数が第一級じゃないので関数をコピーすることができない:

f = cos  #<- エラー!

これをできるようにするには、式の型をdoubleで扱っているところをバリアント型にする必要がある:

/// 値の型
typedef enum {
  VAR_DOUBLE,
  VAR_FNCTPTR,
} VarType;

/// バリアント
typedef struct {
  VarType type;
  union {
    double d;
    double (*fnctptr)(double);
  } u;
} Value;

そしてシンボルテーブルに値が格納されてしまっているのを止める:

typedef struct symrec {
  struct symrec* next;
  char* name;
} symrec;

そして関数がトークンのタイプとなっていて構文にされていたのを、

exp
  ...
  | FNCT '(' exp ')'           { $$ = (*($1->value.fnctptr))($3); }

式の値の型によって呼び出すようにする必要がある。expに関数呼び出しを入れたままだとコンフリクトが発生するのでprimに追い出す:

exp
  : prim                     { $$ = $1; }
  ...

prim
  : VAL               { $$ = $1; }
  | SYM               { if (!refer(&$$, $1)) YYERROR; }
  | prim '(' exp ')'  { if (!funcall(&$$, $1, $3)) YYERROR; }
;

以降全ソース:

%{

#include <math.h>
#include <stdio.h>

#define FALSE  (0)
#define TRUE   (1)

int yylex(void);

/// エラーが起きると yyparse から呼び出される
void yyerror(const char* s) {
  printf("%s\n", s);
}

/// 値の型
typedef enum {
  VAR_DOUBLE,
  VAR_FNCTPTR,
} VarType;

/// バリアント
typedef struct {
  VarType type;
  union {
    double d;
    double (*fnctptr)(double);
  } u;
} Value;

/// シンボル
typedef struct symrec {
  struct symrec* next;
  char* name;
} symrec;

int refer(Value*, symrec*);
void assign(const char*, Value);
int funcall(Value*, Value, Value);
int arith1(Value*, Value, double (*)(double));
int arith2(Value*, Value, Value, double (*)(double, double));
double add(double x, double y)  { return x + y; }
double sub(double x, double y)  { return x - y; }
double mul(double x, double y)  { return x * y; }
double div(double x, double y)  { return x / y; }
double neg(double x)  { return -x; }
void dump_value(FILE*, Value);

%}

%union {
  Value val;    // 値
  symrec* sym;  // シンボルテーブルポインタ
}

%token <val>  VAL
%token <sym>  SYM
%type  <val>  exp prim

/* 演算子の優先の低い順に並べる */
%right '='
%left  '+' '-'
%left  '*' '/'
%left  UNARY_NEG  /* 単項− */
%left  '^'

%%

input
  :  /* 空文字列 */
  | input line
;

line
  : '\n'
  | exp '\n'    { putchar('\t'); dump_value(stdout, $1); putchar('\n'); }
  | error '\n'  { yyerrok; }
;

exp
  : prim                     { $$ = $1; }
  | SYM '=' exp              { assign($1->name, $$ = $3); }
  | exp '+' exp              { if (!arith2(&$$, $1, $3, add)) YYERROR; }
  | exp '-' exp              { if (!arith2(&$$, $1, $3, sub)) YYERROR; }
  | exp '*' exp              { if (!arith2(&$$, $1, $3, mul)) YYERROR; }
  | exp '/' exp              { if (!arith2(&$$, $1, $3, div)) YYERROR; }
  | '-' exp %prec UNARY_NEG  { if (!arith1(&$$, $2, neg)) YYERROR; }
  | exp '^' exp              { if (!arith2(&$$, $1, $3, pow)) YYERROR; }
  | '(' exp ')'              { $$ = $2; }
;

prim
  : VAL               { $$ = $1; }
  | SYM               { if (!refer(&$$, $1)) YYERROR; }
  | prim '(' exp ')'  { if (!funcall(&$$, $1, $3)) YYERROR; }
;

%%

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

symrec* sym_table = NULL;

symrec* putsym(const char* sym_name) {
  symrec* ptr = (symrec*)malloc(sizeof(symrec));
  ptr->name = (char*)malloc(strlen(sym_name) + 1);
  strcpy(ptr->name, sym_name);
  ptr->next = sym_table;
  sym_table = ptr;
  return ptr;
}

symrec* getsym(const char* sym_name) {
  symrec* ptr;
  for (ptr = sym_table; ptr != NULL; ptr = ptr->next) {
    if (strcmp(ptr->name, sym_name) == 0)
      return ptr;
  }
  return NULL;
}

static int skip_spaces() {
  int c;
  while (c = getchar(), c == ' ' || c == '\t');
  return c;
}

static int read_symbol(int c) {
  static char* buf;
  static int length;
  symrec* s;
  int i = 0;
  do {
    if (i >= length) {  // あふれたのでバッファを大きくする
      if (length == 0)    length = 16/2;
      buf = (char*)realloc(buf, (length *= 2) + 1);
    }
    buf[i++] = c;   // 文字をバッファに加える
    c = getchar();  // 次の文字を読む
  } while (c != EOF && isalnum(c));
  ungetc(c, stdin);
  buf[i] = '\0';

  s = getsym(buf);
  if (s == NULL)
    s = putsym(buf);
  yylval.sym = s;
  return SYM;
}

/// 字句解析として yyparse から呼び出される
int yylex(void) {
  int c = skip_spaces();
  if (c == EOF)    return 0;
  if (c == '.' || isdigit(c)) {  // 数値
    ungetc(c, stdin);
    scanf("%lf", &yylval.val.u.d);
    yylval.val.type = VAR_DOUBLE;
    return VAL;
  }
  if (isalpha(c)) {  // 識別子
    return read_symbol(c);
  }
  // その他の文字は文字リテラルトークン
  return c;
}

void set_double(Value* p, double x)  { p->type = VAR_DOUBLE; p->u.d = x; }
void set_fnctptr(Value* p, double (*f)(double))  { p->type = VAR_FNCTPTR; p->u.fnctptr = f; }

int arith1(Value* result, Value a, double (*f)(double)) {
  if (a.type != VAR_DOUBLE) {
    fprintf(stderr, "Can't operate arithmetic: ");
    dump_value(stderr, a);
    fprintf(stderr, "\n");
    return FALSE;
  } else {
    set_double(result, f(a.u.d));
    return TRUE;
  }
}

int arith2(Value* result, Value a, Value b, double (*f)(double, double)) {
  if (a.type != VAR_DOUBLE) {
    fprintf(stderr, "Can't operate arithmetic: ");
    dump_value(stderr, a);
    fprintf(stderr, "\n");
    return FALSE;
  } else if (b.type != VAR_DOUBLE) {
    fprintf(stderr, "Can't operate arithmetic: ");
    dump_value(stderr, b);
    fprintf(stderr, "\n");
    return FALSE;
  } else {
    set_double(result, f(a.u.d, b.u.d));
    return TRUE;
  }
}

void dump_value(FILE* ofp, Value v) {
  switch (v.type) {
  case VAR_DOUBLE:  fprintf(ofp, "%.10g", v.u.d); break;
  case VAR_FNCTPTR:  fprintf(ofp, "function"); break;
  default:  fprintf(ofp, "?? Illegal value"); break;
  }
}

#include <map>
#include <string>
using namespace std;

static map<string, Value> GlobalVariableTable;

int refer(Value* result, symrec* sym) {
  map<string, Value>::const_iterator it = GlobalVariableTable.find(sym->name);
  if (it == GlobalVariableTable.end()) {
    fprintf(stderr, "unbound `%s'\n", sym->name);
    return FALSE;
  } else {
    *result = GlobalVariableTable[sym->name];
    return TRUE;
  }
}

void assign(const char* name, Value val) {
  GlobalVariableTable[name] = val;
}

int funcall(Value* result, Value fnct, Value x) {
  if (fnct.type != VAR_FNCTPTR) {
    fprintf(stderr, "Can't call: ");
    dump_value(stderr, fnct);
    fprintf(stderr, "\n");
    return FALSE;
  } else if (x.type != VAR_DOUBLE) {
    fprintf(stderr, "Illegal argument: ");
    dump_value(stderr, x);
    fprintf(stderr, "\n");
    return FALSE;
  } else {
    set_double(result, fnct.u.fnctptr(x.u.d));
    return TRUE;
  }
}


void init_table() {
  struct {
    const char* fname;
    double (*fnct)(double);
  } static const arith_fncts[] = {
    { "sin",   sin,  },
    { "cos",   cos,  },
    { "atan",  atan, },
    { "ln",    log,  },
    { "exp",   exp,  },
    { "sqrt",  sqrt, },
    { 0, 0 }
  };

  int i;
  for (i = 0; arith_fncts[i].fname != NULL; ++i) {
    Value v;
    set_fnctptr(&v, arith_fncts[i].fnct);
    assign(arith_fncts[i].fname, v);
  }
}

int main() {
  init_table();
  yyparse();
  return 0;
}
  • グローバル変数はstd::mapでてきとーに
  • 元からだけど、符号反転の優先順位がおかしい
-abc = 123
    -123