OSDN Git Service

First commitment for the BlackTank LPC1769.
[blacktank/blacktank.git] / ntshell.c
1 /**
2  * @file ntshell.c
3  * @author Shinichiro Nakamura
4  * @brief 小規模組み込みシステム向けのシェルシステムの実装。
5  */
6
7 /*
8  * ===============================================================
9  *  Natural Tiny Shell (NT-Shell)
10  *  Version 0.0.7
11  * ===============================================================
12  * Copyright (c) 2010-2011 Shinichiro Nakamura
13  *
14  * Permission is hereby granted, free of charge, to any person
15  * obtaining a copy of this software and associated documentation
16  * files (the "Software"), to deal in the Software without
17  * restriction, including without limitation the rights to use,
18  * copy, modify, merge, publish, distribute, sublicense, and/or
19  * sell copies of the Software, and to permit persons to whom the
20  * Software is furnished to do so, subject to the following
21  * conditions:
22  *
23  * The above copyright notice and this permission notice shall be
24  * included in all copies or substantial portions of the Software.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
28  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
30  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
31  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
32  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
33  * OTHER DEALINGS IN THE SOFTWARE.
34  * ===============================================================
35  */
36
37 #include "ntshell.h"
38 #include "ntlibc.h"
39
40 #define VERSION_MAJOR 0     /**< メジャー番号。 */
41 #define VERSION_MINOR 0     /**< マイナー番号。 */
42 #define VERSION_RELEASE 6   /**< リリース番号。 */
43
44 /**
45  * @brief 処理で用いるデータ構造体。
46  *
47  * @details
48  * vtparseはユーザデータのポインタを設定することができる。
49  * Natural Tiny Shellはこれを使って自身の処理で必要な情報を保持する。
50  */
51 typedef struct {
52     text_editor_t *editor;
53     text_history_t *history;
54     int suggest_index;
55     char suggest_source[TEXTEDITOR_MAXLEN];
56     int (*func_read)(char *buf, int cnt);
57     int (*func_write)(const char *buf, int cnt);
58     int (*func_cb)(const char *text);
59 } ntshell_user_data_t;
60
61 #define SUGGEST_INDEX(vtp) \
62     ((ntshell_user_data_t *)(vtp)->user_data)->suggest_index
63 #define SUGGEST_SOURCE(vtp) \
64     ((ntshell_user_data_t *)(vtp)->user_data)->suggest_source
65
66 /**
67  * @brief テキストエディタを取得する。
68  *
69  * @param vtp vtparse構造体。
70  */
71 #define GET_EDITOR(vtp) \
72     ((ntshell_user_data_t *)(vtp)->user_data)->editor
73
74 /**
75  * @brief テキストヒストリを取得する。
76  *
77  * @param vtp vtparse構造体。
78  */
79 #define GET_HISTORY(vtp) \
80     ((ntshell_user_data_t *)(vtp)->user_data)->history
81
82 /**
83  * @brief シリアルポートから読み込む。
84  *
85  * @param vtp vtparse構造体。
86  * @param buf 読み込みバッファ。
87  * @param cnt 読み込み文字数。
88  */
89 #define SERIAL_READ(vtp,buf,cnt) \
90     ((ntshell_user_data_t *)(vtp)->user_data)->func_read(buf, cnt)
91
92 /**
93  * @brief シリアルポートへ書き込む。
94  *
95  * @param vtp vtparse構造体。
96  * @param buf 書き込みバッファ。
97  * @param cnt 書き込み文字数。
98  */
99 #define SERIAL_WRITE(vtp,buf,cnt) \
100     ((ntshell_user_data_t *)(vtp)->user_data)->func_write(buf, cnt)
101
102 /**
103  * @brief コールバックを呼び出す。
104  *
105  * @param vtp vtparse構造体。
106  * @param text コールバック関数へ渡す文字列。
107  */
108 #define CALLBACK(vtp, text) \
109     ((ntshell_user_data_t *)(vtp)->user_data)->func_cb(text)
110
111 /**
112  * @brief テキストヒストリで1つ後ろを辿る。
113  *
114  * @param parser パーサー。
115  * @param action アクション。
116  * @param ch 入力文字。
117  */
118 static void actfunc_history_prev(
119         vtparse_t *parser,
120         vtparse_action_t action,
121         unsigned char ch) {
122     if (text_history_read_point_prev(GET_HISTORY(parser))) {
123         char txt[TEXTHISTORY_MAXLEN];
124         int n = text_history_read(GET_HISTORY(parser), &txt[0], sizeof(txt));
125         if (0 < n) {
126             SERIAL_WRITE(parser, "\x1b[2K", 4);
127             SERIAL_WRITE(parser, "\x1b[80D", 5);
128             SERIAL_WRITE(parser, ">", 1);
129             SERIAL_WRITE(parser, txt, n);
130             text_editor_set_text(GET_EDITOR(parser), txt);
131         }
132     }
133 }
134
135 /**
136  * @brief テキストヒストリで1つ前を辿る。
137  *
138  * @param parser パーサー。
139  * @param action アクション。
140  * @param ch 入力文字。
141  */
142 static void actfunc_history_next(
143         vtparse_t *parser,
144         vtparse_action_t action,
145         unsigned char ch) {
146     if (text_history_read_point_next(GET_HISTORY(parser))) {
147         char txt[TEXTHISTORY_MAXLEN];
148         int n = text_history_read(GET_HISTORY(parser), &txt[0], sizeof(txt));
149         if (0 < n) {
150             SERIAL_WRITE(parser, "\x1b[2K", 4);
151             SERIAL_WRITE(parser, "\x1b[80D", 5);
152             SERIAL_WRITE(parser, ">", 1);
153             SERIAL_WRITE(parser, txt, n);
154             text_editor_set_text(GET_EDITOR(parser), txt);
155         }
156     }
157 }
158
159 /**
160  * @brief カーソルを左へ移動させる。
161  *
162  * @param parser パーサー。
163  * @param action アクション。
164  * @param ch 入力文字。
165  */
166 static void actfunc_cursor_left(
167         vtparse_t *parser,
168         vtparse_action_t action,
169         unsigned char ch) {
170     if (text_editor_cursor_left(GET_EDITOR(parser))) {
171         SERIAL_WRITE(parser, "\x1b[1D", 4);
172     }
173 }
174
175 /**
176  * @brief カーソルを右へ移動させる。
177  *
178  * @param parser パーサー。
179  * @param action アクション。
180  * @param ch 入力文字。
181  */
182 static void actfunc_cursor_right(
183         vtparse_t *parser,
184         vtparse_action_t action,
185         unsigned char ch) {
186     if (text_editor_cursor_right(GET_EDITOR(parser))) {
187         SERIAL_WRITE(parser, "\x1b[1C", 4);
188     }
189 }
190
191 /**
192  * @brief エンターキーの処理を実行する。
193  *
194  * @param parser パーサー。
195  * @param action アクション。
196  * @param ch 入力文字。
197  */
198 static void actfunc_enter(
199         vtparse_t *parser,
200         vtparse_action_t action,
201         unsigned char ch) {
202     char txt[TEXTEDITOR_MAXLEN];
203     text_editor_get_text(GET_EDITOR(parser), &txt[0], sizeof(txt));
204     text_editor_clear(GET_EDITOR(parser));
205     text_history_write(GET_HISTORY(parser), txt);
206     SERIAL_WRITE(parser, "\r\n", 2);
207     CALLBACK(parser, txt);
208     SERIAL_WRITE(parser, ">", 1);
209 }
210
211 /**
212  * @brief キャンセルキーの処理を実行する。
213  * @details
214  * 一般的なOSのCTRL+C処理はシグナルを発行し、受信したプロセスが
215  * 中断処理を実行する。
216  * ここでのキャンセルは見た目を再現したもので、
217  * 入力中の文字列を破棄してカーソルを新しい入力に備えて復帰させるものだ。
218  *
219  * @param parser パーサー。
220  * @param action アクション。
221  * @param ch 入力文字。
222  */
223 static void actfunc_cancel(
224         vtparse_t *parser,
225         vtparse_action_t action,
226         unsigned char ch) {
227     SERIAL_WRITE(parser, "^C\r\n", 4);
228     text_editor_clear(GET_EDITOR(parser));
229     SERIAL_WRITE(parser, ">", 1);
230 }
231
232 /**
233  * @brief 挿入処理を実行する。
234  *
235  * @param parser パーサー。
236  * @param action アクション。
237  * @param ch 入力文字。
238  */
239 static void actfunc_insert(
240         vtparse_t *parser,
241         vtparse_action_t action,
242         unsigned char ch) {
243
244     /*
245      * 入力があった場合、入力補完状態から抜ける。
246      */
247     SUGGEST_INDEX(parser) = -1;
248
249     /*
250      * テキストエディタを使って文字を文字列に挿入する。
251      */
252     if (text_editor_insert(GET_EDITOR(parser), ch)) {
253         char txt[TEXTEDITOR_MAXLEN];
254         int len = text_editor_get_text(GET_EDITOR(parser), &txt[0], sizeof(txt));
255         int pos = text_editor_cursor_get_position(GET_EDITOR(parser));
256         SERIAL_WRITE(parser, (char *)&ch, sizeof(ch));
257         int n = len - pos;
258         if (n > 0) {
259             int i;
260             SERIAL_WRITE(parser, txt + pos, len - pos);
261             for (i = 0; i < n; i++) {
262                 SERIAL_WRITE(parser, "\x1b[1D", 4);
263             }
264         }
265     }
266 }
267
268 /**
269  * @brief バックスペース処理を実行する。
270  *
271  * @param parser パーサー。
272  * @param action アクション。
273  * @param ch 入力文字。
274  */
275 static void actfunc_backspace(
276         vtparse_t *parser,
277         vtparse_action_t action,
278         unsigned char ch) {
279     if (text_editor_backspace(GET_EDITOR(parser))) {
280         char txt[TEXTEDITOR_MAXLEN];
281         SERIAL_WRITE(parser, "\x1b[1D", 4);
282         int len = text_editor_get_text(GET_EDITOR(parser), &txt[0], sizeof(txt));
283         int pos = text_editor_cursor_get_position(GET_EDITOR(parser));
284         int n = len - pos;
285         if (n > 0) {
286             int i;
287             SERIAL_WRITE(parser, txt + pos, len - pos);
288             SERIAL_WRITE(parser, " ", 1);
289             for (i = 0; i < n + 1; i++) {
290                 SERIAL_WRITE(parser, "\x1b[1D", 4);
291             }
292         } else {
293             SERIAL_WRITE(parser, " ", 1);
294             SERIAL_WRITE(parser, "\x1b[1D", 4);
295         }
296     }
297 }
298
299 /**
300  * @brief 入力補完処理を実行する。
301  *
302  * @param parser パーサー。
303  * @param action アクション。
304  * @param ch 入力文字。
305  */
306 static void actfunc_suggest(
307         vtparse_t *parser,
308         vtparse_action_t action,
309         unsigned char ch) {
310     char buf[TEXTEDITOR_MAXLEN];
311     if (SUGGEST_INDEX(parser) < 0) {
312         /*
313          * 入力補完モードにこれから入る場合。
314          * 現在の入力文字列を元に補完候補を取得する。
315          */
316         if (text_editor_get_text(
317                     GET_EDITOR(parser),
318                     SUGGEST_SOURCE(parser),
319                     sizeof(SUGGEST_SOURCE(parser))) > 0) {
320             SUGGEST_INDEX(parser) = 0;
321             if (text_history_find(
322                         GET_HISTORY(parser),
323                         SUGGEST_INDEX(parser),
324                         SUGGEST_SOURCE(parser),
325                         buf,
326                         sizeof(buf)) == 0) {
327                 // 候補が見つかればテキストを設定して、インデックスをメモする。
328                 int n = ntlibc_strlen((const char *)buf);
329                 SERIAL_WRITE(parser, "\x1b[2K", 4);
330                 SERIAL_WRITE(parser, "\x1b[80D", 5);
331                 SERIAL_WRITE(parser, ">", 1);
332                 SERIAL_WRITE(parser, buf, n);
333                 text_editor_set_text(GET_EDITOR(parser), buf);
334             } else {
335                 // 候補がなければ入力補完モードから抜ける。
336                 SUGGEST_INDEX(parser) = -1;
337             }
338         }
339     } else {
340         /*
341          * 既に入力補完モードに入っている場合、
342          * 次の候補を探して見つかればテキストとして設定する。
343          */
344         SUGGEST_INDEX(parser) = SUGGEST_INDEX(parser) + 1;
345         if (text_history_find(
346                     GET_HISTORY(parser),
347                     SUGGEST_INDEX(parser),
348                     SUGGEST_SOURCE(parser),
349                     buf,
350                     sizeof(buf)) == 0) {
351             // 候補が見つかればテキストを設定する。
352             int n = ntlibc_strlen((const char *)buf);
353             SERIAL_WRITE(parser, "\x1b[2K", 4);
354             SERIAL_WRITE(parser, "\x1b[80D", 5);
355             SERIAL_WRITE(parser, ">", 1);
356             SERIAL_WRITE(parser, buf, n);
357             text_editor_set_text(GET_EDITOR(parser), buf);
358         } else {
359             // 候補が見つからなければ元の入力文字列に戻し、入力補完モードから抜ける。
360             int n = ntlibc_strlen(SUGGEST_SOURCE(parser));
361             SERIAL_WRITE(parser, "\x1b[2K", 4);
362             SERIAL_WRITE(parser, "\x1b[80D", 5);
363             SERIAL_WRITE(parser, ">", 1);
364             SERIAL_WRITE(parser, SUGGEST_SOURCE(parser), n);
365             text_editor_set_text(GET_EDITOR(parser), SUGGEST_SOURCE(parser));
366             SUGGEST_INDEX(parser) = -1;
367         }
368     }
369 }
370
371 static void actfunc_cursor_head(
372         vtparse_t *parser,
373         vtparse_action_t action,
374         unsigned char ch) {
375     SERIAL_WRITE(parser, "\x1b[80D", 5);
376     SERIAL_WRITE(parser, ">", 1);
377     text_editor_cursor_head(GET_EDITOR(parser));
378 }
379
380 static void actfunc_cursor_tail(
381         vtparse_t *parser,
382         vtparse_action_t action,
383         unsigned char ch) {
384     char buf[TEXTEDITOR_MAXLEN];
385     int len;
386     text_editor_get_text(GET_EDITOR(parser), buf, sizeof(buf));
387     len = ntlibc_strlen((const char *)buf);
388     SERIAL_WRITE(parser, "\x1b[80D", 5);
389     SERIAL_WRITE(parser, ">", 1);
390     SERIAL_WRITE(parser, buf, len);
391     text_editor_cursor_tail(GET_EDITOR(parser));
392 }
393
394 /**
395  * @brief アクションテーブルのデータ構造体。
396  * @details
397  * アクションは状態と入力文字によって与えられる。
398  * アクションに対する関数もここで定義する。
399  */
400 typedef struct {
401     vtparse_action_t action;
402     unsigned char ch;
403     void (*func)(
404             vtparse_t *parser,
405             vtparse_action_t action,
406             unsigned char ch);
407 } ntshell_action_table_t;
408
409 /**
410  * @brief アクションに対する処理関数テーブル。
411  * @details
412  * やってくるコードは仮想端末側の処理に依存する。
413  * よって様々なプラットフォームの様々な仮想端末で試すと良い。
414  *
415  * <table>
416  *   <th>
417  *     <td>Platform</td>
418  *     <td>Tools</td>
419  *   </th>
420  *   <tr>
421  *     <td>Windows</td>
422  *     <td>Hyper Terminal, Poderossa, TeraTerm</td>
423  *   </tr>
424  *   <tr>
425  *     <td>Linux</td>
426  *     <td>minicom, screen, kermit</td>
427  *   </tr>
428  * </table>
429  */
430 static const ntshell_action_table_t action_table[] = {
431     {VTPARSE_ACTION_EXECUTE, 0x01, actfunc_cursor_head},
432     {VTPARSE_ACTION_EXECUTE, 0x02, actfunc_cursor_left},
433     {VTPARSE_ACTION_EXECUTE, 0x03, actfunc_cancel},
434     {VTPARSE_ACTION_EXECUTE, 0x05, actfunc_cursor_tail},
435     {VTPARSE_ACTION_EXECUTE, 0x06, actfunc_cursor_right},
436     {VTPARSE_ACTION_EXECUTE, 0x08, actfunc_backspace},
437     {VTPARSE_ACTION_EXECUTE, 0x09, actfunc_suggest},
438     {VTPARSE_ACTION_EXECUTE, 0x0d, actfunc_enter},
439     {VTPARSE_ACTION_EXECUTE, 0x0e, actfunc_history_next},
440     {VTPARSE_ACTION_EXECUTE, 0x10, actfunc_history_prev},
441     {VTPARSE_ACTION_CSI_DISPATCH, 0x41, actfunc_history_prev},
442     {VTPARSE_ACTION_CSI_DISPATCH, 0x42, actfunc_history_next},
443     {VTPARSE_ACTION_CSI_DISPATCH, 0x43, actfunc_cursor_right},
444     {VTPARSE_ACTION_CSI_DISPATCH, 0x44, actfunc_cursor_left},
445     {VTPARSE_ACTION_PRINT, 0x7f, actfunc_backspace},
446 };
447
448 /**
449  * @brief パーサーに対するコールバック関数。
450  * @details vtparseモジュールのコールバック関数に従った実装である。
451  *
452  * @param parser パーサー。
453  * @param action アクション。
454  * @param ch キャラクタ。
455  */
456 void parser_callback(
457         vtparse_t *parser,
458         vtparse_action_t action,
459         unsigned char ch) {
460     ntshell_action_table_t *p;
461     int i;
462     const int ACTTBLSIZ = sizeof(action_table) / sizeof(action_table[0]);
463
464     /*
465      * 制御コードに対する処理はテーブルから探す。
466      */
467     p = (ntshell_action_table_t *)action_table;
468     for (i = 0; i < ACTTBLSIZ; i++) {
469         if ((p->action == action) && (p->ch == ch)) {
470             p->func(parser, action, ch);
471             return;
472         }
473         p++;
474     }
475
476     /*
477      * 通常の文字列は入力として扱う。
478      */
479     if (VTPARSE_ACTION_PRINT == action) {
480         actfunc_insert(parser, action, ch);
481     }
482 }
483
484 /**
485  * @brief Natural Tiny Shellのバージョンを返す。
486  * @details 返すバージョンはリリースバージョンである。
487  *
488  * @param major メージャーバージョン。
489  * @param minor マイナーバージョン。
490  * @param release リリースバージョン。
491  */
492 void ntshell_version(int *major, int *minor, int *release)
493 {
494     *major = VERSION_MAJOR;
495     *minor = VERSION_MINOR;
496     *release = VERSION_RELEASE;
497 }
498
499 /**
500  * @brief Natural Tiny Shellを実行する。
501  * @details この関数は実行を返さない。
502  *
503  * @param parser VT100パーサー。
504  * @param editor テキストエディタ。
505  * @param history テキストヒストリ。
506  * @param func_read シリアルリード関数。
507  * @param func_write シリアルライト関数。
508  * @param func_cb コールバック関数。
509  */
510 void ntshell_execute(
511         ntshell_t *p,
512         int (*func_read)(char *buf, int cnt),
513         int (*func_write)(const char *buf, int cnt),
514         int (*func_cb)(const char *text))
515 {
516     /*
517      * vtparseはユーザデータへのポインタを設定できるようになっている。
518      * Natural Tiny Shellはこれを利用してテキストエディタやヒストリ、
519      * リード関数やライト関数、コールバック関数を処理の中で使用できる
520      * ようにしてある。
521      */
522     ntshell_user_data_t user_data;
523
524     user_data.editor = &(p->editor);
525     user_data.history = &(p->history);
526     user_data.func_read = func_read;
527     user_data.func_write = func_write;
528     user_data.func_cb = func_cb;
529
530     p->parser.user_data = &user_data;
531
532     /*
533      * 各モジュールを初期化する。
534      */
535     vtparse_init(&(p->parser), parser_callback);
536     text_editor_init(GET_EDITOR(&(p->parser)));
537     text_history_init(GET_HISTORY(&(p->parser)));
538     SUGGEST_INDEX(&(p->parser)) = -1;
539
540     /*
541      * ユーザ入力ループ。
542      */
543     SERIAL_WRITE(&(p->parser), ">", 1);
544     while(1)
545     {
546         unsigned char ch;
547         SERIAL_READ(&(p->parser), (char *)&ch, sizeof(ch));
548         vtparse(&(p->parser), &ch, sizeof(ch));
549     }
550 }
551