B演習(言語処理系演習)第3回 字句解析 田浦 今日の予定 字句解析インタフェース • 今週の課題 字句の定義 字句解析器の仕組み(概要) • 下請け部品 char_buf, char_stream, int_stack まめ知識: デバッガ デバッグに関する若干の抽象論 字句解析器とは 字句解析器 (tokenizer) ) Identifier (n) ( identifier (fib) def ‘d’ ‘e’ ‘f’ ‘ ’ ‘f’ ‘i’ ‘b’ ‘(’ ‘n’ ‘)’ ‘:’ 今週の課題 字句解析器の作成とそのテスト • テストプログラム: ファイルから字句を読み出して, 読み出された字句をその順に表示 • 表示の仕方 • • • • TOK_KW_FOR TOK_LITERAL_INT (35) TOK_ID (x) etc. • テストデータとその正解があるのでそれを用いて 比較 インタフェース tokenizer_t t = mk_tokenizer(char * filename); void tok_next(tokenizer_t t); • 次の1字句を読み込んで現在の字句に設定 token_kind_t tok_cur_token_kind(tokenizer_t t); int tok_cur_token_int_val(tokenizer_t t); double tok_cur_token_float_val(tokenizer_t t); char * tok_cur_token_str_val(tokenizer_t t); • 現在の字句に関する情報を返す インタフェース(2) 正しくエラーメッセージを表示するためのイン タフェース char * tok_cur_token_string(tokenizer_t t); • 現在構築中の字句の文字列表現 char * tok_cur_line_string(tokenizer_t t); • 現在の行の文字列表現(現在の字句まで) そのほか2, 3 (資料参照) 字句解析器のテストプログラム int tokenizer_test(char * filename) { tokenizer_t t = mk_tokenizer(filename); while (tok_cur_token_kind(t) != TOK_EOF) { print_cur_token(t); tok_next(t); } return 0; } void print_cur_token(tokenizer_t t) { char * C_name = tok_cur_token_kind_string(t); switch(tok_cur_token_kind(t)) { case TOK_ID: case TOK_LITERAL_INT: case TOK_LITERAL_FLOAT: case TOK_LITERAL_STRING: printf("%s (%s)\n", C_name, tok_cur_token_string(t)); break; default: printf("%s\n", C_name); break; } } int main(int argc, char ** argv) { if (argc != 2) { fprintf(stderr, "usage : %s filename\n", argv[0]); exit(1); } else { tokenizer_test(argv[1]); exit(0); } } 字句の種類 テキスト,HP参照 or, and, is, not, in, =, ==, !=, >, >=, <, <=, +, -, *, /, %, ~, <<, >>, ^, &, |, None, break, continue, pass, return, del, print, global, if, elif, else, for, while, def, (, ), {, }, [, ], ., ,, :, integer, floatnumber, stringliteral, identifier newline, indent, dedent, end_of_file 少し複雑な字句 integer • 0 | (1|…|9)(0|…|9)* floatnumber • (0|…|9)+.(0|…|9)* | .(0|…|9)+ string literal (エスケープ文字の扱い) • ”(x | \y)*” identifier • (a|_)(a|d|_)* テキストおよびmini-Pythonの文法を参照 注意の必要な字句 end_of_file : 入力終端に達すると生成される newline : 改行に対して生成される(無視しない) 字下げ: • indent : 字下げを1レベル深くする字句 • dedent : 字下げを1レベル浅くする字句 コメント 字下げ/改行字句生成の例 def fib(n): if n < 2: return 1 else: return fib(n-1) + fib(n-2) def another_fun(…) … 改行 字下げ(indent) 逆字下げ(dedent) コメント 基本: #から改行手前までを無視 行が空白とコメントのみの場合,改行も無視 例: • x = y + z # calc x • id(x), =, id(y), +, id(z), newline • # calc x first x=y # then p p=q • id(x), =, id(y), newline, id(p), =, id(q), newline 字句の種類のCプログラムでの定義 typedef enum { TOK_OR, /* or */ TOK_AND, /* and */ …, …, TOK_ID, TOK_NEWLINE, TOK_INDENT, TOK_DEDENT, TOK_EOF } token_kind_t; 字句のCプログラム内での定義 typedef struct token { token_kind_t k; union { char * s_val; /* id, 文字列リテラル */ double f_val; /* 浮動小数点数リテラル */ int i_val; /* 整数リテラル */ } v; } token, * token_t; 字句解析器の基本的な仕組み 「現在の文字」に基づく場合わけ • tok_next(tokenizer_t t) { … switch (現在の文字) { case ’%’ : t->tok.k = …; break; case ’0’..’9’ : t->tok.k = …; break; } … } いくつかややこしいところ 各種literalとidentifier (正規表現) コメントの読み飛ばし indent/dedent字句の生成 現在構築中の行,字句の保持(エラーメッ セージ用) いきなりだとどうしていいか悩んでしまう人は, • newline/indent/dedent 字句を後回しにする • エラーメッセージ表示(そのための状態管理)を後 回しにする tok_nextの主要部分(次の文字で場合わけを しながら,字句の切れ目まで読んでいく.読ん だtokenをtokにセットする)に集中 有用な下請け部品 char_stream_t 先週からの拡張 • 文字を読み出すための下請け部品 • ファイル名,行番号,列番号,現在行(現在の文字まで)を 維持 char_buf_t • 文字をためるバッファ(append) • cur_line, cur_token_string, string literalの読み込み用 int_stack_t • 整数のスタック • indent/dedent用 デバッグの心構え いけないこと • 平常心を失うこと(「なぜ動かないんだっっ!!」) • バグが消えてほしいと思うこと • プログラムをむやみにいじること(うまく動けばラッ キー,という考え方) よい心構え • 目の前の(バグ入り)プログラムの動作を理解・観 察・追跡する 実践的心構え 極力小さな入力でバグを再現する • どんな入力なら動いて,どんな入力は動かない か? (できるだけ小さな入力で),どこまでまともに 動作し,どこから狂いだすかを追跡する • printfで変数の値を表示 • デバッガ 「デバッガ」の使い方よりも大切な 「デバッグ」の方法 平常心 • プログラムを直そうとやっきにならない 大切な心構え • 目の前にあるプログラムの挙動の調査 • どこで「本来の挙動」を逸脱したかの調査 本来の挙動 異常観測地点 プログラム開始時点 実際の挙動 デバッガ プログラムを実行 実行途中で停止 • 関数先頭,行番号 • ステップ(1行ずつ)実行 停止した状態で,変数,その他の「状態を観 察」 • 変数の値 • 関数呼び出しの履歴(なぜ今ここにいるか) 起動方法 gcc –g … (shell)% gdb プログラム名 (emacs) M-x gdb まめ知識: gdbの良く使うコマンド r コマンドライン引数… p bt b 関数名 b ファイル名:行番号 (Emacs: C-x SPACE) c n s disp <RET>で最後のコマンドの繰り返し メモリ割り当てについて よく分からない人はmalloc (C++ではnew)が 基本と思っておく • 関数呼び出し後も生き延びてほしい(使われる)領 域は必ずmallocで割り当てる tokenizer_t mk_tokenizer() tokenizer_t mk_tokenizer() { tokenizer_t t { = (tokenizer_t)malloc(sizeof(tokenizer)); tokenizer t[1]; if (t == 0) { … exit(1); } t->… = …; t-> … = …; … … return t; return t; } } M-x info Cut & paste (コピペ) • C-SPACE … C-w • C-y M-x query-replace M-x replace-string キーボードマクロ
© Copyright 2025 ExpyDoc