Skip to content

Instantly share code, notes, and snippets.

@ptr-yudai
Created December 22, 2016 13:05
Show Gist options
  • Save ptr-yudai/a732c1218c9e8c551b62d63872fc8a9f to your computer and use it in GitHub Desktop.
Save ptr-yudai/a732c1218c9e8c551b62d63872fc8a9f to your computer and use it in GitHub Desktop.
きつねさんとLLVMで作った電卓
/*
%{ ...%}で囲まれた行はC言語そのままとして解釈されます。
したがって、%{ ... %}内は解析時にスキップされ、
コンパイル時にコンパイラが読むだけです。
*/
%{
/* stdio.hはキーボードからの入力に必要です。 */
#include <stdio.h>
/* "y.tab.h"はyaccコマンド実行時に出力されます。 */
#include "y.tab.h"
/* 必要な関数だけど使わないので空 */
int yywrap(void) { return 1; }
%}
/*
%% ... %%で囲まれた行はlexに解析されます。
ここには自分の作りたいプログラム言語の規則を書きます。
*/
%%
/**** 次のようにyaccでは「演算子」を定義します。
「足し算」とは...
"+" : "+"である
詳細な計算方法はcalc.yで定義します。
*/
"+" {
/* ここには上の定義のもと「足し算」と判断された場合の処理を書きます */
return OPERATOR_ADD;
}
"-" {
return OPERATOR_SUB;
}
/* returnだけの場合、次のように{}を省略できます。 */
"*" return OPERATOR_MUL;
"/" return OPERATOR_DIV;
"\n" return EOL;
/**** 次のようにyaccでは「整数とは」を定義する必要があります。
整数とは...
[1~9] --> 文字の1~9が最初にある
[0-9]* --> 文字の0~9が続く('*'が繰り返しを意味する)
*/
[1-9][0-9]* {
/*** ここには上の定義のもと「整数」と判断された場合の処理を書きます。 */
double temp_box;
/* 今回は実数しか使わないので実数として読み込みます */
/* 現在の文字(yytext)を整数(%d)としてtemp_boxに入力します。 */
sscanf(yytext, "%lf", &temp_box);
/* 現在のint_valueは上で取得した整数です。 */
yylval.double_value = temp_box;
return DOUBLE_LITERAL;
}
/* 上の定義だと0が整数にならないので定義します。 */
"0" {
yylval.double_value = 0.0; // "0"は0.0
return DOUBLE_LITERAL;
}
/**** 実数とは...
[0-9]* --> 文字の0~9が続く
\. --> 文字の"."がある('.'は正規表現で意味を持つので'\.'と書きます)
[0-9]* --> 文字の0~9が続く
*/
[0-9]*\.[0-9]* {
/*** ここには上の定義のもと「実数」と判断された場合の処理を書きます。 */
double temp_box;
/* 現在の文字(yytext)を実数(%lf)としてtemp_boxに入力します。 */
sscanf(yytext, "%lf", &temp_box);
/* 現在のdouble_valueは上で取得した実数です。 */
yylval.double_value = temp_box;
return DOUBLE_LITERAL;
}
/*
DOUBLE_LITERALやOPERATOR_ADDなどの定数は
calc.yの方で定義するので、それと同じ名前であれば
何でも構いません。
yytextやyylvalもcalc.yの方で定義します。
yylval内のdouble_valueなどもcalc.yで定義します。
*/
%%
/*
%{ ...%}で囲まれた行はC言語そのままとして解釈されます。
したがって、%{ ... %}内は解析時にスキップされ、
コンパイル時にコンパイラが読むだけです。
*/
%{
#include <stdio.h>
#include <stdlib.h>
%}
// 現在解析中の具体的な値が入ります
%union {
// プログラミング言語などでは型がたくさんあるので
// 構造体などをここに入れますが、
// 今回は実数しか使わないようにしました。
double double_value;
}
%token <int_value> INT_LITERAL // 例えばcalc.lで整数と判定されたら、
%token <double_value> DOUBLE_LITERAL // INT_LITERALを受け取ることになります。
%token OPERATOR_ADD // 演算子にも名前を付けておきます。
OPERATOR_SUB // これらもcalc.lで使う用なので、
OPERATOR_MUL // ここでは宣言だけをしておきます。
OPERATOR_DIV
EOL // 行の終端すら定義します
//
// %type <int_value>
%type <double_value> expression term primary_expression
/*
%% ... %%で囲まれた行はlexに解析されます。
ここには自分の作りたいプログラム言語の規則を書きます。
*/
%%
//// 次のようにyaccでは「ソースコードとは」すら定義する必要があります
// 「ソースコード」とは...
// line --> 1行
// | --> または
// source_code line --> 「ソースコード」「1行」で構成される
source_code
: line
| source_code line
;
//// 次のようにyaccでは「行とは」すら定義する必要があります
// 「行」とは...
// expression --> 何かしらの式がある
// EOL --> 改行文字がある
line
: expression EOL
{
//// ここには「行である」であると判定された際の処理を書きます。
// 行判定されたら解析結果($1)を出力します。
printf(">> %lf\n", $1);
}
;
//// 上で使った「何かしらの式」(expression)を定義します
// 「何かしらの式」(expression)とは...
// term --> 項である
// | --> または
// expression OPERATOR_ADD term --> 「何かしらの式」 + 項 である
// | --> または
// expression OPERATOR_SUB term --> 「何かしらの式」 - 項 である
expression
: term
| expression OPERATOR_ADD term
{
//// ここには入力文字が「expression + term」であると判定された際の処理を書きます。
// $1 : expression
// $2 : OPERATOR_ADD
// $3 : term
// 結果 = expression + term
$$ = $1 + $3;
}
| expression OPERATOR_SUB term
{
$$ = $1 - $3;
}
;
//// もちろん「項」も定義します。
// 「項」(term)とは...
// primary_expression --> 「1つの項」である
// | --> または
// term OPERATOR_MUL primary_expression --> 「項」 * 「1つの項」である
// | --> または
// term OPERATOR_DIV primary_expression --> 「項」 / 「1つの項」である
term
: primary_expression
| term OPERATOR_MUL primary_expression
{
$$ = $1 * $3;
}
| term OPERATOR_DIV primary_expression
{
$$ = $1 / $3;
}
;
// 「1つの項」(primary_expression)とは...
// DOUBLE_LITERAL --> 実数である
primary_expression
: DOUBLE_LITERAL
;
%%
/*
ここにコンパイルエラー発生時の処理を書きます。
*/
int yyerror(char const* str)
{
extern char *yytext;
fprintf(stderr, "Error near %s\n", yytext);
return 0;
}
/*
ここにコンパイラを書きます。
今回は電卓なので、標準入力から文字を受け取って計算結果を出力します。
*/
int main(void)
{
// yaccが勝手に解析してくれる関数
extern int yyparse(void);
// コンパイルすべきファイル
extern FILE *yyin;
// 今回はファイルではなくstdinからの入力
yyin = stdin;
if (yyparse()) {
// エラー発生
fprintf(stderr, "ERROR!\n");
exit(1);
}
return 0;
}
# calc.binを出力するコマンド
calc.bin: y.tab.c lex.yy.c # 下のコマンドに必要なファイル
cc -o calc.bin y.tab.c lex.yy.c
# y.tab.cを出力するコマンド
y.tab.c: calc.y # 下のコマンドに必要なファイル
yacc -dv calc.y
# lex.yy.cを出力するコマンド
lex.yy.c: calc.l # 下のコマンドに必要なファイル
lex calc.l
# ゴミを削除
clean:
rm y.output y.tab.c y.tab.h
rm lex.yy.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment