Statistics
| Branch: | Tag: | Revision:

amiro-os / os / core / src / aos_shell.c @ 2c99037f

History | View | Annotate | Download (46.028 KB)

1 e545e620 Thomas Schöpping
/*
2
AMiRo-OS is an operating system designed for the Autonomous Mini Robot (AMiRo) platform.
3
Copyright (C) 2016..2018  Thomas Schöpping et al.
4

5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9

10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14

15
You should have received a copy of the GNU General Public License
16
along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
#include <aos_shell.h>
20
21 ba516b61 Thomas Schöpping
#if (AMIROOS_CFG_SHELL_ENABLE == true)
22 e545e620 Thomas Schöpping
#include <aos_debug.h>
23
#include <aos_time.h>
24
#include <aos_system.h>
25
#include <string.h>
26 ba516b61 Thomas Schöpping
27
/**
28
 * @brief   Event mask to be set on OS related events.
29
 */
30
#define AOS_SHELL_EVENTMASK_OS                  EVENT_MASK(0)
31
32
/**
33
 * @brief   Event mask to be set on a input event.
34
 */
35
#define AOS_SHELL_EVENTMASK_INPUT               EVENT_MASK(1)
36
37
/**
38
 * @brief   Implementation of the BaseAsynchronous write() method (inherited from BaseSequentialStream).
39
 */
40
static size_t _channelwrite(void *instance, const uint8_t *bp, size_t n)
41
{
42
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
43 dd8738ea Thomas Schöpping
    return streamWrite(((AosShellChannel*)instance)->asyncchannel, bp, n);
44 ba516b61 Thomas Schöpping
  } else {
45
    return 0;
46
  }
47
}
48
49
/**
50
 * @brief   Implementation of the BaseAsynchronous read() method (inherited from BaseSequentialStream).
51
 */
52
static size_t _channelread(void *instance, uint8_t *bp, size_t n)
53
{
54 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
55
    return streamRead(((AosShellChannel*)instance)->asyncchannel, bp, n);
56
  } else {
57
    return 0;
58
  }
59 ba516b61 Thomas Schöpping
}
60
61
/**
62
 * @brief   Implementation of the BaseAsynchronous put() method (inherited from BaseSequentialStream).
63
 */
64
static msg_t _channelput(void *instance, uint8_t b)
65
{
66
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
67 dd8738ea Thomas Schöpping
    return streamPut(((AosShellChannel*)instance)->asyncchannel, b);
68 ba516b61 Thomas Schöpping
  } else {
69
    return MSG_RESET;
70
  }
71
}
72
73
/**
74
 * @brief   Implementation of the BaseAsynchronous get() method (inherited from BaseSequentialStream).
75
 */
76
static msg_t _channelget(void *instance)
77
{
78 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
79
    return streamGet(((AosShellChannel*)instance)->asyncchannel);
80
  } else {
81
    return MSG_RESET;
82
  }
83 ba516b61 Thomas Schöpping
}
84
85
/**
86
 * @brief   Implementation of the BaseAsynchronous putt() method.
87
 */
88 2c99037f Marc Rothmann
static msg_t _channelputt(void *instance, uint8_t b, sysinterval_t time)
89 ba516b61 Thomas Schöpping
{
90
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
91 dd8738ea Thomas Schöpping
    return chnPutTimeout(((AosShellChannel*)instance)->asyncchannel, b, time);
92 ba516b61 Thomas Schöpping
  } else {
93
    return MSG_RESET;
94
  }
95
}
96
97
/**
98
 * @brief   Implementation of the BaseAsynchronous gett() method.
99
 */
100 2c99037f Marc Rothmann
static msg_t _channelgett(void *instance, sysinterval_t time)
101 ba516b61 Thomas Schöpping
{
102 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
103
    return chnGetTimeout(((AosShellChannel*)instance)->asyncchannel, time);
104
  } else {
105
    return MSG_RESET;
106
  }
107 ba516b61 Thomas Schöpping
}
108
109
/**
110
 * @brief   Implementation of the BaseAsynchronous writet() method.
111
 */
112 2c99037f Marc Rothmann
static size_t _channelwritet(void *instance, const uint8_t *bp, size_t n, sysinterval_t time)
113 ba516b61 Thomas Schöpping
{
114
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
115 dd8738ea Thomas Schöpping
    return chnWriteTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
116 ba516b61 Thomas Schöpping
  } else {
117
    return 0;
118
  }
119
}
120
121
/**
122
 * @brief   Implementation of the BaseAsynchronous readt() method.
123
 */
124 2c99037f Marc Rothmann
static size_t _channelreadt(void *instance, uint8_t *bp, size_t n, sysinterval_t time)
125 ba516b61 Thomas Schöpping
{
126 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
127
    return chnReadTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
128
  } else {
129
    return 0;
130
  }
131 ba516b61 Thomas Schöpping
}
132
133 2c99037f Marc Rothmann
/**
134
 * @brief   Implementation of the BaseAsynchronousChannel ctl() method.
135
 */
136
static msg_t _channelctl(void *instance, unsigned int operation, void *arg) {
137
  (void) instance;
138
139
  switch (operation) {
140
  case CHN_CTL_NOP:
141
    osalDbgCheck(arg == NULL);
142
    break;
143
  case CHN_CTL_INVALID:
144
    osalDbgAssert(false, "invalid CTL operation");
145
    break;
146
  default:
147
    break;
148
  }
149
  return MSG_OK;
150
}
151
152 ba516b61 Thomas Schöpping
static const struct AosShellChannelVMT _channelvmt = {
153 0128be0f Marc Rothmann
  (size_t) 0,
154 ba516b61 Thomas Schöpping
  _channelwrite,
155
  _channelread,
156
  _channelput,
157
  _channelget,
158
  _channelputt,
159
  _channelgett,
160
  _channelwritet,
161
  _channelreadt,
162 2c99037f Marc Rothmann
  _channelctl,
163 ba516b61 Thomas Schöpping
};
164
165
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
166
{
167
  aosDbgCheck(instance != NULL);
168
169
  // local variables
170
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
171
  size_t bytes;
172
  size_t maxbytes = 0;
173
174
  // iterate through the list of channels
175
  while (channel != NULL) {
176
    bytes = streamWrite(channel, bp, n);
177
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
178
    channel = channel->next;
179
  }
180
181
  return maxbytes;
182
}
183
184
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
185
{
186
  (void)instance;
187
  (void)bp;
188
  (void)n;
189
190
  return 0;
191
}
192
193
static msg_t _streamput(void *instance, uint8_t b)
194
{
195
  aosDbgCheck(instance != NULL);
196
197
  // local variables
198
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
199 dd8738ea Thomas Schöpping
  msg_t ret = MSG_OK;
200 ba516b61 Thomas Schöpping
201
  // iterate through the list of channels
202
  while (channel != NULL) {
203 dd8738ea Thomas Schöpping
    msg_t ret_ = streamPut(channel, b);
204
    ret = (ret_ < ret) ? ret_ : ret;
205 ba516b61 Thomas Schöpping
    channel = channel->next;
206
  }
207
208 dd8738ea Thomas Schöpping
  return ret;
209 ba516b61 Thomas Schöpping
}
210
211
static msg_t _streamget(void *instance)
212
{
213
  (void)instance;
214
215
  return 0;
216
}
217
218
static const struct AosShellStreamVMT _streamvmt = {
219 0128be0f Marc Rothmann
  (size_t) 0,
220 ba516b61 Thomas Schöpping
  _streamwrite,
221
  _stremread,
222
  _streamput,
223
  _streamget,
224
};
225
226 e545e620 Thomas Schöpping
/**
227
 * @brief   Enumerator of special keyboard keys.
228
 */
229
typedef enum special_key {
230
  KEY_UNKNOWN,      /**< any/unknow key */
231
  KEY_AMBIGUOUS,    /**< key is ambiguous */
232
  KEY_TAB,          /**< tabulator key */
233
  KEY_ESCAPE,       /**< escape key */
234
  KEY_BACKSPACE,    /**< backspace key */
235
  KEY_INSERT,       /**< insert key */
236
  KEY_DELETE,       /**< delete key */
237
  KEY_HOME,         /**< home key */
238
  KEY_END,          /**< end key */
239
  KEY_PAGE_UP,      /**< page up key */
240
  KEY_PAGE_DOWN,    /**< page down key */
241
  KEY_ARROW_UP,     /**< arrow up key */
242
  KEY_ARROW_DOWN,   /**< arrow down key */
243
  KEY_ARROW_LEFT,   /**< arrow left key */
244
  KEY_ARROW_RIGHT,  /**< arrow right key */
245
} special_key_t;
246
247
/**
248
 * @brief   Enumerator for case (in)sensitive character matching.
249
 */
250
typedef enum charmatch {
251
  CHAR_MATCH_NOT    = 0,  /**< Characters do not match at all. */
252
  CHAR_MATCH_NCASE  = 1,  /**< Characters would match case insensitive. */
253
  CHAR_MATCH_CASE   = 2,  /**< Characters do match with case. */
254
} charmatch_t;
255
256
/**
257
 * @brief   Print the shell prompt
258
 * @details Depending on the configuration flags, the system uptime is printed before the prompt string.
259
 *
260
 * @param[in] shell   Pointer to the shell object.
261
 */
262
static void _printPrompt(aos_shell_t* shell)
263
{
264
  aosDbgCheck(shell != NULL);
265
266 8399aeae Thomas Schöpping
  // print some time informattion before prompt if configured
267
  if (shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) {
268
    // printf the system uptime
269
    if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_UPTIME) {
270
      // get current system uptime
271
      aos_timestamp_t uptime;
272
      aosSysGetUptime(&uptime);
273
274
      chprintf((BaseSequentialStream*)&shell->stream, "[%01u:%02u:%02u:%02u:%03u:%03u] ",
275
               (uint32_t)(uptime / MICROSECONDS_PER_DAY),
276
               (uint8_t)(uptime % MICROSECONDS_PER_DAY / MICROSECONDS_PER_HOUR),
277
               (uint8_t)(uptime % MICROSECONDS_PER_HOUR / MICROSECONDS_PER_MINUTE),
278
               (uint8_t)(uptime % MICROSECONDS_PER_MINUTE / MICROSECONDS_PER_SECOND),
279
               (uint16_t)(uptime % MICROSECONDS_PER_SECOND / MICROSECONDS_PER_MILLISECOND),
280
               (uint16_t)(uptime % MICROSECONDS_PER_MILLISECOND / MICROSECONDS_PER_MICROSECOND));
281
    }
282
    else if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_DATETIME) {
283
      // get current RTC time
284
      struct tm dt;
285
      aosSysGetDateTime(&dt);
286
      chprintf((BaseSequentialStream*)&shell->stream, "[%02u-%02u-%04u|%02u:%02u:%02u] ",
287
               dt.tm_mday,
288
               dt.tm_mon + 1,
289
               dt.tm_year + 1900,
290
               dt.tm_hour,
291
               dt.tm_min,
292
               dt.tm_sec);
293
    }
294
    else {
295
      aosDbgAssert(false);
296
    }
297 e545e620 Thomas Schöpping
  }
298
299
  // print the actual prompt string
300
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
301 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
302 e545e620 Thomas Schöpping
  } else {
303 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
304 e545e620 Thomas Schöpping
  }
305
306
  return;
307
}
308
309
/**
310
 * @brief   Interprete a escape sequence
311
 *
312
 * @param[in] seq   Character sequence to interprete.
313
 *                  Must be terminated by NUL byte.
314
 *
315
 * @return          A @p special_key value.
316
 */
317
static special_key_t _interpreteEscapeSequence(const char seq[])
318
{
319
  // local variables
320
  bool ambiguous = false;
321
  int cmp = 0;
322
323
  // TAB
324
  /* not supported yet; use "\x09" instead */
325
326
  // BACKSPACE
327
  /* not supported yet; use "\x08" instead */
328
329
  // ESCAPE
330
  cmp = strcmp(seq, "\x1B");
331
  if (cmp == 0) {
332
    return KEY_ESCAPE;
333
  } else {
334
    ambiguous |= (cmp < 0);
335
  }
336
337
  // INSERT
338
  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
339
  if (cmp == 0) {
340
    return KEY_INSERT;
341
  } else {
342
    ambiguous |= (cmp < 0);
343
  }
344
345
  // DELETE
346
  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
347
  if (cmp == 0) {
348
    return KEY_DELETE;
349
  } else {
350
    ambiguous |= (cmp < 0);
351
  }
352
353
  // HOME
354
  cmp = strcmp(seq, "\x1B\x4F\x48");
355
  if (cmp == 0) {
356
    return KEY_HOME;
357
  } else {
358
    ambiguous |= (cmp < 0);
359
  }
360
361
  // END
362
  cmp = strcmp(seq, "\x1B\x4F\x46");
363
  if (cmp == 0) {
364
    return KEY_END;
365
  } else {
366
    ambiguous |= (cmp < 0);
367
  }
368
369
  // PAGE UP
370
  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
371
  if (cmp == 0) {
372
    return KEY_PAGE_UP;
373
  } else {
374
    ambiguous |= (cmp < 0);
375
  }
376
377
  // PAGE DOWN
378
  cmp = strcmp(seq, "\x1B\x5B\x36\x7E");
379
  if (cmp == 0) {
380
    return KEY_PAGE_DOWN;
381
  } else {
382
    ambiguous |= (cmp < 0);
383
  }
384
385
  // ARROW UP
386
  cmp = strcmp(seq, "\x1B\x5B\x41");
387
  if (cmp == 0) {
388
    return KEY_ARROW_UP;
389
  } else {
390
    ambiguous |= (cmp < 0);
391
  }
392
393
  // ARROW DOWN
394
  cmp = strcmp(seq, "\x1B\x5B\x42");
395
  if (cmp == 0) {
396
    return KEY_ARROW_DOWN;
397
  } else {
398
    ambiguous |= (cmp < 0);
399
  }
400
401
  // ARROW LEFT
402
  cmp = strcmp(seq, "\x1B\x5B\x44");
403
  if (cmp == 0) {
404
    return KEY_ARROW_LEFT;
405
  } else {
406
    ambiguous |= (cmp < 0);
407
  }
408
409
  // ARROW RIGHT
410
  cmp = strcmp(seq, "\x1B\x5B\x43");
411
  if (cmp == 0) {
412
    return KEY_ARROW_RIGHT;
413
  } else {
414
    ambiguous |= (cmp < 0);
415
  }
416
417
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
418
}
419
420
/**
421
 * @brief   Move the cursor in the terminal
422
 *
423
 * @param[in] shell   Pointer to the shell object.
424
 * @param[in] from    Starting position of the cursor.
425
 * @param[in] to      Target position to move the cursor to.
426
 *
427
 * @return            The number of positions moved.
428
 */
429
static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
430
{
431
  aosDbgCheck(shell != NULL);
432
433
  // local variables
434
  size_t pos = from;
435
436
  // move cursor left by printing backspaces
437
  while (pos > to) {
438 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, '\b');
439 e545e620 Thomas Schöpping
    --pos;
440
  }
441
442
  // move cursor right by printing line content
443
  while (pos < to) {
444 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[pos]);
445 e545e620 Thomas Schöpping
    ++pos;
446
  }
447
448
  return (int)pos - (int)from;
449
}
450
451
/**
452
 * @brief   Print content of the shell line
453
 *
454
 * @param[in] shell   Pointer to the shell object.
455
 * @param[in] from    First position to start printing from.
456
 * @param[in] to      Position after the last character to print.
457
 *
458
 * @return            Number of characters printed.
459
 */
460
static inline size_t _printLine(aos_shell_t* shell, const size_t from, const size_t to)
461
{
462
  aosDbgCheck(shell != NULL);
463
464
  // local variables
465
  size_t cnt;
466
467
  for (cnt = 0; from + cnt < to; ++cnt) {
468 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[from + cnt]);
469 e545e620 Thomas Schöpping
  }
470
471
  return cnt;
472
}
473
474
/**
475
 * @brief   Compare two characters.
476
 *
477
 * @param[in] lhs       First character to compare.
478
 * @param[in] rhs       Second character to compare.
479
 *
480
 * @return              How well the characters match.
481
 */
482
static inline charmatch_t _charcmp(char lhs, char rhs)
483
{
484
  // if lhs is a upper case letter and rhs is a lower case letter
485
  if (lhs >= 'A' && lhs <= 'Z' && rhs >= 'a' && rhs <= 'z') {
486
    return (lhs == (rhs - 'a' + 'A')) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
487
  }
488
  // if lhs is a lower case letter and rhs is a upper case letter
489
  else if (lhs >= 'a' && lhs <= 'z' && rhs >= 'A' && rhs <= 'Z') {
490
    return ((lhs - 'a' + 'A') == rhs) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
491
  }
492
  // default
493
  else {
494
    return (lhs == rhs) ? CHAR_MATCH_CASE : CHAR_MATCH_NOT;
495
  }
496
}
497
498
/**
499
 * @brief   Maps an character from ASCII to a modified custom encoding.
500
 * @details The custom character encoding is very similar to ASCII and has the following structure:
501
 *          0x00=NULL ... 0x40='@' (identically to ASCII)
502
 *          0x4A='a'; 0x4B='A'; 0x4C='b'; 0x4D='B' ... 0x73='z'; 0x74='Z' (custom letter order)
503
 *          0x75='[' ... 0x7A='`' (0x5B..0x60 is ASCII)
504
 *          0x7B='{' ... 0x7F=DEL (identically to ASCII)
505
 *
506
 * @param[in] c   Character to map to the custom encoding.
507
 *
508
 * @return    The customly encoded character.
509
 */
510
static inline char _mapAscii2Custom(const char c)
511
{
512
  if (c >= 'A' && c <= 'Z') {
513
    return ((c - 'A') * 2) + 'A' + 1;
514
  } else if (c > 'Z' && c < 'a') {
515
    return c + ('z' - 'a') + 1;
516
  } else if (c >= 'a' && c <= 'z') {
517
    return ((c - 'a') * 2) + 'A';
518
  } else {
519
    return c;
520
  }
521
}
522
523
/**
524
 * @brief   Compares two strings wrt letter case.
525
 * @details Comparisson uses a custom character encoding or mapping.
526
 *          See @p _mapAscii2Custom for details.
527
 *
528
 * @param[in] str1    First string to compare.
529
 * @param[in] str2    Second string to compare.
530
 * @param[in] cs      Flag indicating whether comparison shall be case sensitive.
531
 * @param[in,out] n   Maximum number of character to compare (in) and number of matching characters (out).
532
 *                    If a null pointer is specified, this parameter is ignored.
533
 *                    If the value pointed to is zero, comarison will not be limited.
534
 * @param[out] m      Optional indicator whether there was at least one case mismatch.
535
 *
536
 * @return      Integer value indicating the relationship between the strings.
537
 * @retval <0   The first character that does not match has a lower value in str1 than in str2.
538
 * @retval  0   The contents of both strings are equal.
539
 * @retval >0   The first character that does not match has a greater value in str1 than in str2.
540
 */
541
static int _strccmp(const char *str1, const char *str2, bool cs, size_t* n, charmatch_t* m)
542
{
543
  aosDbgCheck(str1 != NULL);
544
  aosDbgCheck(str2 != NULL);
545
546
  // initialize variables
547
  if (m) {
548
    *m = CHAR_MATCH_NOT;
549
  }
550
  size_t i = 0;
551
552
  // iterate through the strings
553
  while ((n == NULL) || (*n == 0) || (*n > 0 && i < *n)) {
554
    // break on NUL
555
    if (str1[i] == '\0' || str2[i] == '\0') {
556
      if (n) {
557
        *n = i;
558
      }
559
      break;
560
    }
561
    // compare character
562
    const charmatch_t match = _charcmp(str1[i], str2[i]);
563
    if ((match == CHAR_MATCH_CASE) || (!cs && match == CHAR_MATCH_NCASE)) {
564
      if (m != NULL && *m != CHAR_MATCH_NCASE) {
565
        *m = match;
566
      }
567
      ++i;
568
    } else {
569
      if (n) {
570
        *n = i;
571
      }
572
      break;
573
    }
574
  }
575
576
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
577
}
578
579 ba516b61 Thomas Schöpping
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
580 e545e620 Thomas Schöpping
{
581
  aosDbgCheck(shell != NULL);
582 ba516b61 Thomas Schöpping
  aosDbgCheck(channel != NULL);
583
  aosDbgCheck(n != NULL);
584 e545e620 Thomas Schöpping
585
  // local variables
586 ba516b61 Thomas Schöpping
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
587 e545e620 Thomas Schöpping
  char c;
588 dd8738ea Thomas Schöpping
  special_key_t key;
589 e545e620 Thomas Schöpping
590 ba516b61 Thomas Schöpping
  // initialize output variables
591
  *n = 0;
592
593
  // read character by character from the channel
594
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
595 dd8738ea Thomas Schöpping
    key = KEY_UNKNOWN;
596 e545e620 Thomas Schöpping
597
    // parse escape sequence
598 ba516b61 Thomas Schöpping
    if (shell->inputdata.escp > 0) {
599
      shell->inputdata.escseq[shell->inputdata.escp] = c;
600
      ++shell->inputdata.escp;
601
      key = _interpreteEscapeSequence(shell->inputdata.escseq);
602 e545e620 Thomas Schöpping
      if (key == KEY_AMBIGUOUS) {
603
        // read next byte to resolve ambiguity
604
        continue;
605
      } else {
606 ba516b61 Thomas Schöpping
        /*
607
         * If the escape sequence could either be parsed sucessfully
608
         * or there is no match (KEY_UNKNOWN),
609
         * reset the sequence variable and interprete key/character
610
         */
611
        shell->inputdata.escp = 0;
612
        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
613 e545e620 Thomas Schöpping
      }
614
    }
615
616 ba516b61 Thomas Schöpping
    /* interprete keys or character */
617 e545e620 Thomas Schöpping
    {
618 ba516b61 Thomas Schöpping
      // default
619
      action = AOS_SHELL_ACTION_NONE;
620
621
      // printable character
622
      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
623
        action = AOS_SHELL_ACTION_READCHAR;
624
      }
625
626
      // tab key or character
627
      else if (key == KEY_TAB || c == '\x09') {
628
        /*
629
         * pressing tab once applies auto fill
630
         * pressing tab a second time prints suggestions
631
         */
632
        if (shell->inputdata.lastaction == AOS_SHELL_ACTION_AUTOFILL || shell->inputdata.lastaction == AOS_SHELL_ACTION_SUGGEST) {
633
          action = AOS_SHELL_ACTION_SUGGEST;
634 e545e620 Thomas Schöpping
        } else {
635 ba516b61 Thomas Schöpping
          action = AOS_SHELL_ACTION_AUTOFILL;
636 e545e620 Thomas Schöpping
        }
637 ba516b61 Thomas Schöpping
      }
638
639
      // INS key
640
      else if (key == KEY_INSERT) {
641
        action = AOS_SHELL_ACTION_INSERTTOGGLE;
642
      }
643
644
      // DEL key or character
645
      else if (key == KEY_DELETE || c == '\x7F') {
646
        // ignore if cursor is at very right
647
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
648
          action = AOS_SHELL_ACTION_DELETEFORWARD;
649 e545e620 Thomas Schöpping
        }
650 ba516b61 Thomas Schöpping
      }
651
652
      // backspace key or character
653
      else if (key == KEY_BACKSPACE || c == '\x08') {
654
        // ignore if cursor is at very left
655
        if (shell->inputdata.cursorpos > 0) {
656
          action = AOS_SHELL_ACTION_DELETEBACKWARD;
657 e545e620 Thomas Schöpping
        }
658 ba516b61 Thomas Schöpping
      }
659
660
      // 'page up' of 'arrow up' key
661
      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
662 e545e620 Thomas Schöpping
        // ignore if there was some input
663 ba516b61 Thomas Schöpping
        if (shell->inputdata.noinput) {
664
          action = AOS_SHELL_ACTION_RECALLLAST;
665 e545e620 Thomas Schöpping
        }
666 ba516b61 Thomas Schöpping
      }
667
668
      // 'page down' key, 'arrow done' key, 'end of test' character or 'end of transmission' character
669
      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03') {
670 e545e620 Thomas Schöpping
        // ignore if line is empty
671 ba516b61 Thomas Schöpping
        if (shell->inputdata.lineend > 0) {
672
          action = AOS_SHELL_ACTION_CLEAR;
673 e545e620 Thomas Schöpping
        }
674 ba516b61 Thomas Schöpping
      }
675
676
      // 'home' key
677
      else if (key == KEY_HOME) {
678 e545e620 Thomas Schöpping
        // ignore if cursor is very left
679 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
680
          action = AOS_SHELL_ACTION_CURSOR2START;
681 e545e620 Thomas Schöpping
        }
682 ba516b61 Thomas Schöpping
      }
683
684
      // 'end' key
685
      else if (key == KEY_END) {
686
        // ignore if cursos is very right
687
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
688
          action = AOS_SHELL_ACTION_CURSOR2END;
689 e545e620 Thomas Schöpping
        }
690 ba516b61 Thomas Schöpping
      }
691
692
      // 'arrow left' key
693
      else if (key == KEY_ARROW_LEFT) {
694 e545e620 Thomas Schöpping
        // ignore if cursor is very left
695 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
696
          action = AOS_SHELL_ACTION_CURSORLEFT;
697 e545e620 Thomas Schöpping
        }
698 ba516b61 Thomas Schöpping
      }
699
700
      // 'arrow right' key
701
      else if (key == KEY_ARROW_RIGHT) {
702
        // irgnore if cursor is very right
703
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
704
          action = AOS_SHELL_ACTION_CURSORRIGHT;
705 e545e620 Thomas Schöpping
        }
706 ba516b61 Thomas Schöpping
      }
707
708
      // carriage return ('\r') or line feed ('\n') character
709
      else if (c == '\x0D' || c == '\x0A') {
710
        action = AOS_SHELL_ACTION_EXECUTE;
711
      }
712
713
      // ESC key or [ESCAPE] character
714
      else if (key == KEY_ESCAPE || c == '\x1B') {
715
        action = AOS_SHELL_ACTION_ESCSTART;
716 e545e620 Thomas Schöpping
      }
717
    }
718
719
    /* handle function */
720 ba516b61 Thomas Schöpping
    switch (action) {
721
      case AOS_SHELL_ACTION_READCHAR:
722
      {
723 e545e620 Thomas Schöpping
        // line is full
724 ba516b61 Thomas Schöpping
        if (shell->inputdata.lineend + 1 >= shell->linesize) {
725
          _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
726
          chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
727 e545e620 Thomas Schöpping
          _printPrompt(shell);
728 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
729
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
730 e545e620 Thomas Schöpping
        }
731
        // read character
732
        else {
733
          // clear old line content on first input
734 ba516b61 Thomas Schöpping
          if (shell->inputdata.noinput) {
735 e545e620 Thomas Schöpping
            memset(shell->line, '\0', shell->linesize);
736 ba516b61 Thomas Schöpping
            shell->inputdata.noinput = false;
737 e545e620 Thomas Schöpping
          }
738
          // overwrite content
739
          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
740 ba516b61 Thomas Schöpping
            shell->line[shell->inputdata.cursorpos] = c;
741
            ++shell->inputdata.cursorpos;
742
            shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
743
            streamPut(&shell->stream, (uint8_t)c);
744 e545e620 Thomas Schöpping
          }
745
          // insert character
746
          else {
747 ba516b61 Thomas Schöpping
            memmove(&(shell->line[shell->inputdata.cursorpos+1]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
748
            shell->line[shell->inputdata.cursorpos] = c;
749
            ++shell->inputdata.lineend;
750
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
751
            ++shell->inputdata.cursorpos;
752
            _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
753 e545e620 Thomas Schöpping
          }
754
        }
755
        break;
756 ba516b61 Thomas Schöpping
      }
757 e545e620 Thomas Schöpping
758 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_AUTOFILL:
759 e545e620 Thomas Schöpping
      {
760
        const char* fill = shell->line;
761 ba516b61 Thomas Schöpping
        size_t cmatch = shell->inputdata.cursorpos;
762 e545e620 Thomas Schöpping
        charmatch_t matchlevel = CHAR_MATCH_NOT;
763
        size_t n;
764
        // iterate through command list
765
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
766
          // compare current match with command
767
          n = cmatch;
768
          charmatch_t mlvl = CHAR_MATCH_NOT;
769
          _strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
770
          const int cmp = (n < cmatch) ?
771
                            (n - cmatch) :
772
                            (cmd->name[n] != '\0') ?
773
                              strlen(cmd->name) - n :
774
                              0;
775
          // if an exact match was found
776 ba516b61 Thomas Schöpping
          if (cmatch + cmp == shell->inputdata.cursorpos) {
777
            cmatch = shell->inputdata.cursorpos;
778 e545e620 Thomas Schöpping
            fill = cmd->name;
779
            // break the loop only if there are no case mismatches with the input
780 ba516b61 Thomas Schöpping
            n = shell->inputdata.cursorpos;
781 e545e620 Thomas Schöpping
            _strccmp(fill, shell->line, false, &n, &mlvl);
782
            if (mlvl == CHAR_MATCH_CASE) {
783
              break;
784
            }
785
          }
786
          // if a not exact match was found
787 ba516b61 Thomas Schöpping
          else if (cmatch + cmp > shell->inputdata.cursorpos) {
788 e545e620 Thomas Schöpping
            // if this is the first one
789
            if (fill == shell->line) {
790
              cmatch += cmp;
791
              fill = cmd->name;
792
            }
793
            // if this is a worse one
794
            else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
795
              cmatch += cmp;
796
            }
797
          }
798
          // non matching commands are ignored
799
          else {}
800
        }
801
        // evaluate if there are case mismatches
802
        n = cmatch;
803
        _strccmp(shell->line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
804
        // print the auto fill if any
805 ba516b61 Thomas Schöpping
        if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
806
          shell->inputdata.noinput = false;
807 e545e620 Thomas Schöpping
          // limit auto fill so it will not overflow the line width
808 ba516b61 Thomas Schöpping
          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
809
            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
810 e545e620 Thomas Schöpping
          }
811
          // move trailing memory further in the line
812 ba516b61 Thomas Schöpping
          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
813
          shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
814 e545e620 Thomas Schöpping
          // if there was no incorrect case when matching
815
          if (matchlevel == CHAR_MATCH_CASE) {
816
            // insert fill command name to line
817 ba516b61 Thomas Schöpping
            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
818 e545e620 Thomas Schöpping
            // print the output
819 ba516b61 Thomas Schöpping
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
820 e545e620 Thomas Schöpping
          } else {
821
            // overwrite line with fill command name
822
            memcpy(shell->line, fill, cmatch);
823
            // reprint the whole line
824 ba516b61 Thomas Schöpping
            _moveCursor(shell, shell->inputdata.cursorpos, 0);
825
            _printLine(shell, 0, shell->inputdata.lineend);
826 e545e620 Thomas Schöpping
          }
827
          // move cursor to the end of the matching sequence
828 ba516b61 Thomas Schöpping
          shell->inputdata.cursorpos = cmatch;
829
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
830 e545e620 Thomas Schöpping
        }
831
        break;
832
      }
833
834 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_SUGGEST:
835 e545e620 Thomas Schöpping
      {
836
        unsigned int matches = 0;
837
        // iterate through command list
838
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
839
          // compare line content with command, excpet if cursorpos=0
840 ba516b61 Thomas Schöpping
          size_t i = shell->inputdata.cursorpos;
841
          if (shell->inputdata.cursorpos > 0) {
842 e545e620 Thomas Schöpping
            _strccmp(shell->line, cmd->name, true, &i, NULL);
843
          }
844 ba516b61 Thomas Schöpping
          const int cmp = (i < shell->inputdata.cursorpos) ?
845
                            (i - shell->inputdata.cursorpos) :
846 e545e620 Thomas Schöpping
                            (cmd->name[i] != '\0') ?
847
                              strlen(cmd->name) - i :
848
                              0;
849
          // if a match was found
850
          if (cmp > 0) {
851
            // if this is the first one
852
            if (matches == 0) {
853 ba516b61 Thomas Schöpping
              _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
854
              streamPut(&shell->stream, '\n');
855 e545e620 Thomas Schöpping
            }
856
            // print the command
857 ba516b61 Thomas Schöpping
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
858 e545e620 Thomas Schöpping
            ++matches;
859
          }
860
        }
861
        // reprint the prompt and line if any matches have been found
862
        if (matches > 0) {
863
          _printPrompt(shell);
864 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
865
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
866
          shell->inputdata.noinput = false;
867 e545e620 Thomas Schöpping
        }
868
        break;
869
      }
870
871 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_INSERTTOGGLE:
872
      {
873 e545e620 Thomas Schöpping
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
874
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
875
        } else {
876
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
877
        }
878
        break;
879 ba516b61 Thomas Schöpping
      }
880 e545e620 Thomas Schöpping
881 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_DELETEFORWARD:
882
      {
883
        --shell->inputdata.lineend;
884
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
885
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
886
        streamPut(&shell->stream, ' ');
887
        _moveCursor(shell, shell->inputdata.lineend + 1, shell->inputdata.cursorpos);
888 e545e620 Thomas Schöpping
        break;
889 ba516b61 Thomas Schöpping
      }
890 e545e620 Thomas Schöpping
891 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_DELETEBACKWARD:
892
      {
893
        --shell->inputdata.cursorpos;
894
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
895
        --shell->inputdata.lineend;
896
        shell->line[shell->inputdata.lineend] = '\0';
897
        _moveCursor(shell, shell->inputdata.cursorpos + 1, shell->inputdata.cursorpos);
898
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
899
        streamPut(&shell->stream, ' ');
900
        _moveCursor(shell, shell->inputdata.lineend+1, shell->inputdata.cursorpos);
901 e545e620 Thomas Schöpping
        break;
902 ba516b61 Thomas Schöpping
      }
903 e545e620 Thomas Schöpping
904 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_RECALLLAST:
905 e545e620 Thomas Schöpping
      {
906
        // replace any intermediate NUL bytes with spaces
907 ba516b61 Thomas Schöpping
        shell->inputdata.lineend = 0;
908 e545e620 Thomas Schöpping
        size_t nul_start = 0;
909
        size_t nul_end = 0;
910
        // search line for a NUL byte
911
        while (nul_start < shell->linesize) {
912
          if (shell->line[nul_start] == '\0') {
913
            nul_end = nul_start + 1;
914
            // keep searcjing for a byte that is not NUL
915
            while (nul_end < shell->linesize) {
916
              if (shell->line[nul_end] != '\0') {
917
                // an intermediate NUL sequence was found
918
                memset(&(shell->line[nul_start]), ' ', nul_end - nul_start);
919 ba516b61 Thomas Schöpping
                shell->inputdata.lineend = nul_end + 1;
920 e545e620 Thomas Schöpping
                break;
921
              } else {
922
                ++nul_end;
923
              }
924
            }
925
            nul_start = nul_end + 1;
926
          } else {
927 ba516b61 Thomas Schöpping
            ++shell->inputdata.lineend;
928 e545e620 Thomas Schöpping
            ++nul_start;
929
          }
930
        }
931 ba516b61 Thomas Schöpping
        shell->inputdata.cursorpos = shell->inputdata.lineend;
932 e545e620 Thomas Schöpping
        // print the line
933 ba516b61 Thomas Schöpping
        shell->inputdata.noinput = _printLine(shell, 0, shell->inputdata.lineend) == 0;
934 e545e620 Thomas Schöpping
        break;
935
      }
936
937 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CLEAR:
938
      {
939 e545e620 Thomas Schöpping
        // clear output
940 ba516b61 Thomas Schöpping
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
941
        for (shell->inputdata.cursorpos = 0; shell->inputdata.cursorpos < shell->inputdata.lineend; ++shell->inputdata.cursorpos) {
942
          streamPut(&shell->stream, ' ');
943 e545e620 Thomas Schöpping
        }
944 ba516b61 Thomas Schöpping
        _moveCursor(shell, shell->inputdata.lineend, 0);
945
        shell->inputdata.cursorpos = 0;
946
        shell->inputdata.lineend = 0;
947
        shell->inputdata.noinput = true;
948 e545e620 Thomas Schöpping
        break;
949 ba516b61 Thomas Schöpping
      }
950 e545e620 Thomas Schöpping
951 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2START:
952
      {
953
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
954
        shell->inputdata.cursorpos = 0;
955 e545e620 Thomas Schöpping
        break;
956 ba516b61 Thomas Schöpping
      }
957 e545e620 Thomas Schöpping
958 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2END:
959
      {
960
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
961
        shell->inputdata.cursorpos = shell->inputdata.lineend;
962 e545e620 Thomas Schöpping
        break;
963 ba516b61 Thomas Schöpping
      }
964 e545e620 Thomas Schöpping
965 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORLEFT:
966
      {
967
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
968
        --shell->inputdata.cursorpos;
969 e545e620 Thomas Schöpping
        break;
970 ba516b61 Thomas Schöpping
      }
971 e545e620 Thomas Schöpping
972 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORRIGHT:
973
      {
974
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
975
        ++shell->inputdata.cursorpos;
976 e545e620 Thomas Schöpping
        break;
977 ba516b61 Thomas Schöpping
      }
978 e545e620 Thomas Schöpping
979 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_EXECUTE:
980
      {
981
        streamPut(&shell->stream, '\n');
982
        // set the number of read bytes and return
983
        if (!shell->inputdata.noinput) {
984
          *n = shell->linesize - shell->inputdata.lineend;
985 e545e620 Thomas Schöpping
          // fill the remainder of the line with NUL bytes
986 ba516b61 Thomas Schöpping
          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
987
          // reset static variables
988
          shell->inputdata.noinput = true;
989 e545e620 Thomas Schöpping
        }
990 ba516b61 Thomas Schöpping
        return AOS_SUCCESS;
991 e545e620 Thomas Schöpping
        break;
992 ba516b61 Thomas Schöpping
      }
993 e545e620 Thomas Schöpping
994 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_ESCSTART:
995
      {
996
        shell->inputdata.escseq[0] = c;
997
        ++shell->inputdata.escp;
998 e545e620 Thomas Schöpping
        break;
999 ba516b61 Thomas Schöpping
      }
1000 e545e620 Thomas Schöpping
1001 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_NONE:
1002 e545e620 Thomas Schöpping
      default:
1003 ba516b61 Thomas Schöpping
      {
1004 e545e620 Thomas Schöpping
        // do nothing (ignore input) and read next byte
1005
        continue;
1006
        break;
1007 ba516b61 Thomas Schöpping
      }
1008
    } /* end of switch */
1009 e545e620 Thomas Schöpping
1010 ba516b61 Thomas Schöpping
    shell->inputdata.lastaction = action;
1011 e545e620 Thomas Schöpping
  } /* end of while */
1012
1013 ba516b61 Thomas Schöpping
  // no more data could be read from the channel
1014
  return AOS_WARNING;
1015 e545e620 Thomas Schöpping
}
1016
1017
/**
1018
 * @brief   Parses the content of the input buffer (line) to separate arguments.
1019
 *
1020
 * @param[in] shell   Pointer to the shell object.
1021
 *
1022
 * @return            Number of arguments found.
1023
 */
1024
static size_t _parseArguments(aos_shell_t* shell)
1025
{
1026
  aosDbgCheck(shell != NULL);
1027
1028
  /*
1029
   * States for a very small FSM.
1030
   */
1031
  typedef enum {
1032
    START,
1033
    SPACE,
1034
    TEXT,
1035
    END,
1036
  } state_t;
1037
1038
  // local variables
1039
  state_t state = START;
1040
  size_t arg = 0;
1041
1042
  // iterate through the line
1043
  for (char* c = shell->line; c < shell->line + shell->linesize; ++c) {
1044
    // terminate at first NUL byte
1045
    if (*c == '\0') {
1046
      state = END;
1047
      break;
1048
    }
1049
    // spaces become NUL bytes
1050
    else if (*c == ' ') {
1051
      *c = '\0';
1052
      state = SPACE;
1053
    }
1054
    // handle non-NUL bytes
1055
    else {
1056
      switch (state) {
1057
        case START:
1058
        case SPACE:
1059
          // ignore too many arguments
1060
          if (arg < shell->arglistsize) {
1061
            shell->arglist[arg] = c;
1062
          }
1063
          ++arg;
1064
          break;
1065
        case TEXT:
1066
        case END:
1067
        default:
1068
          break;
1069
      }
1070
      state = TEXT;
1071
    }
1072
  }
1073
1074
  // set all remaining argument pointers to NULL
1075
  for (size_t a = arg; a < shell->arglistsize; ++a) {
1076
    shell->arglist[a] = NULL;
1077
  }
1078
1079
  return arg;
1080
}
1081
1082
/**
1083
 * @brief   Initializes a shell object with the specified parameters.
1084
 *
1085
 * @param[in] shell         Pointer to the shell object.
1086
 * @param[in] stream        I/O stream to use.
1087
 * @param[in] prompt        Prompt line to print (NULL = use default prompt).
1088
 * @param[in] line          Pointer to the input buffer.
1089
 * @param[in] linesize      Size of the input buffer.
1090
 * @param[in] arglist       Pointer to the argument buffer.
1091
 * @param[in] arglistsize   Size of te argument buffer.
1092
 */
1093 ba516b61 Thomas Schöpping
void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
1094 e545e620 Thomas Schöpping
{
1095
  aosDbgCheck(shell != NULL);
1096 ba516b61 Thomas Schöpping
  aosDbgCheck(oseventsource != NULL);
1097 e545e620 Thomas Schöpping
  aosDbgCheck(line != NULL);
1098
  aosDbgCheck(arglist != NULL);
1099
1100
  // set parameters
1101
  shell->thread = NULL;
1102
  chEvtObjectInit(&shell->eventSource);
1103 ba516b61 Thomas Schöpping
  shell->os.eventSource = oseventsource;
1104
  aosShellStreamInit(&shell->stream);
1105 e545e620 Thomas Schöpping
  shell->prompt = prompt;
1106
  shell->commands = NULL;
1107
  shell->execstatus.command = NULL;
1108
  shell->execstatus.retval = 0;
1109
  shell->line = line;
1110
  shell->linesize = linesize;
1111 ba516b61 Thomas Schöpping
  shell->inputdata.lastaction = AOS_SHELL_ACTION_NONE;
1112
  shell->inputdata.escp = 0;
1113
  memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
1114
  shell->inputdata.cursorpos = 0;
1115
  shell->inputdata.lineend = 0;
1116
  shell->inputdata.noinput = true;
1117 e545e620 Thomas Schöpping
  shell->arglist = arglist;
1118
  shell->arglistsize = arglistsize;
1119
  shell->config = 0x00;
1120
1121
  // initialize arrays
1122
  memset(shell->line, '\0', shell->linesize);
1123
  for (size_t a = 0; a < shell->arglistsize; ++a) {
1124
    shell->arglist[a] = NULL;
1125
  }
1126
1127
  return;
1128
}
1129
1130
/**
1131 ba516b61 Thomas Schöpping
 * @brief   Initialize an AosShellStream object.
1132
 *
1133
 * @param[in] stream  The AosShellStrem to initialize.
1134
 */
1135
void aosShellStreamInit(AosShellStream* stream)
1136
{
1137
  aosDbgCheck(stream != NULL);
1138
1139
  stream->vmt = &_streamvmt;
1140
  stream->channel = NULL;
1141
1142
  return;
1143
}
1144
1145
/**
1146
 * @brief   Initialize an AosShellChannel object with the specified parameters.
1147
 *
1148 dd8738ea Thomas Schöpping
 * @param[in] channel       The AosShellChannel to initialize.
1149
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1150 ba516b61 Thomas Schöpping
 */
1151 dd8738ea Thomas Schöpping
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1152 ba516b61 Thomas Schöpping
{
1153
  aosDbgCheck(channel != NULL);
1154 dd8738ea Thomas Schöpping
  aosDbgCheck(asyncchannel != NULL);
1155 ba516b61 Thomas Schöpping
1156
  channel->vmt = &_channelvmt;
1157 dd8738ea Thomas Schöpping
  channel->asyncchannel = asyncchannel;
1158
  channel->listener.wflags = 0;
1159 ba516b61 Thomas Schöpping
  channel->next = NULL;
1160
  channel->flags = 0;
1161
1162
  return;
1163
}
1164
1165
/**
1166 e545e620 Thomas Schöpping
 * @brief   Inserts a command to the shells list of commands.
1167
 *
1168
 * @param[in] shell   Pointer to the shell object.
1169
 * @param[in] cmd     Pointer to the command to add.
1170
 *
1171
 * @return            A status value.
1172
 * @retval AOS_SUCCESS  The command was added successfully.
1173
 * @retval AOS_ERROR    Another command with identical name already exists.
1174
 */
1175
aos_status_t aosShellAddCommand(aos_shell_t *shell, aos_shellcommand_t *cmd)
1176
{
1177
  aosDbgCheck(shell != NULL);
1178
  aosDbgCheck(cmd != NULL);
1179
  aosDbgCheck(cmd->name != NULL && strlen(cmd->name) > 0 && strchr(cmd->name, ' ') == NULL && strchr(cmd->name, '\t') == NULL);
1180
  aosDbgCheck(cmd->callback != NULL);
1181
  aosDbgCheck(cmd->next == NULL);
1182
1183
  aos_shellcommand_t* prev = NULL;
1184
  aos_shellcommand_t** curr = &(shell->commands);
1185
1186
  // insert the command to the list wrt lexographical order (exception: lower case characters preceed upper their uppercase counterparts)
1187 ba516b61 Thomas Schöpping
  while (*curr != NULL) {
1188
    // iterate through the list as long as the command names are 'smaller'
1189
    const int cmp = _strccmp((*curr)->name, cmd->name, true, NULL, NULL);
1190
    if (cmp < 0) {
1191
      prev = *curr;
1192
      curr = &((*curr)->next);
1193
      continue;
1194
    }
1195
    // error if the command already exists
1196
    else if (cmp == 0) {
1197
      return AOS_ERROR;
1198
    }
1199
    // insert the command as soon as a 'larger' name was found
1200
    else /* if (cmpval > 0) */ {
1201
      cmd->next = *curr;
1202
      // special case: the first command is larger
1203
      if (prev == NULL) {
1204
        shell->commands = cmd;
1205
      } else {
1206
        prev->next = cmd;
1207 e545e620 Thomas Schöpping
      }
1208 ba516b61 Thomas Schöpping
      return AOS_SUCCESS;
1209 e545e620 Thomas Schöpping
    }
1210
  }
1211 ba516b61 Thomas Schöpping
  // the end of the list has been reached
1212
1213
  // append the command
1214
  *curr = cmd;
1215
  return AOS_SUCCESS;
1216 e545e620 Thomas Schöpping
}
1217
1218
/**
1219
 * @brief   Removes a command from the shells list of commands.
1220
 *
1221
 * @param[in] shell     Pointer to the shell object.
1222
 * @param[in] cmd       Name of the command to removde.
1223
 * @param[out] removed  Optional pointer to the command that was removed.
1224
 *
1225
 * @return              A status value.
1226
 * @retval AOS_SUCCESS  The command was removed successfully.
1227
 * @retval AOS_ERROR    The command name was not found.
1228
 */
1229
aos_status_t aosShellRemoveCommand(aos_shell_t *shell, char *cmd, aos_shellcommand_t **removed)
1230
{
1231
  aosDbgCheck(shell != NULL);
1232
  aosDbgCheck(cmd != NULL && strlen(cmd) > 0);
1233
1234
  aos_shellcommand_t* prev = NULL;
1235
  aos_shellcommand_t** curr = &(shell->commands);
1236
1237
  // iterate through the list and seach for the specified command name
1238
  while (curr != NULL) {
1239
    const int cmpval = strcmp((*curr)->name, cmd);
1240
    // iterate through the list as long as the command names are 'smaller'
1241
    if (cmpval < 0) {
1242
      prev = *curr;
1243
      curr = &((*curr)->next);
1244
      continue;
1245
    }
1246
    // remove the command when found
1247
    else if (cmpval == 0) {
1248
      // special case: the first command matches
1249
      if (prev == NULL) {
1250
        shell->commands = (*curr)->next;
1251
      } else {
1252
        prev->next = (*curr)->next;
1253
      }
1254
      (*curr)->next = NULL;
1255
      // set the optional output argument
1256
      if (removed != NULL) {
1257
        *removed = *curr;
1258
      }
1259
      return AOS_SUCCESS;
1260
    }
1261
    // break the loop if the command names are 'larger'
1262
    else /* if (cmpval > 0) */ {
1263
      break;
1264
    }
1265
  }
1266
1267
  // if the command was not found, return an error
1268
  return AOS_ERROR;
1269
}
1270
1271
/**
1272 ba516b61 Thomas Schöpping
 * @brief   Add a channel to a AosShellStream.
1273
 *
1274
 * @param[in] stream    The AosShellStream to extend.
1275
 * @param[in] channel   The channel to be added to the stream.
1276
 */
1277
void aosShellStreamAddChannel(AosShellStream* stream, AosShellChannel* channel)
1278
{
1279
  aosDbgCheck(stream != NULL);
1280 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
1281 ba516b61 Thomas Schöpping
1282
  // prepend the new channel
1283
  chSysLock();
1284
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
1285
  channel->next = stream->channel;
1286
  stream->channel = channel;
1287
  chSysUnlock();
1288
1289
  return;
1290
}
1291
1292
/**
1293
 * @brief   Remove a channel from an AosShellStream.
1294
 *
1295
 * @param[in] stream    The AosShellStream to modify.
1296
 * @param[in] channel   The channel to remove.
1297
 * @return
1298
 */
1299
aos_status_t aosShellStreamRemoveChannel(AosShellStream* stream, AosShellChannel* channel)
1300
{
1301
  aosDbgCheck(stream != NULL);
1302 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
1303 ba516b61 Thomas Schöpping
1304
  // local varibales
1305
  AosShellChannel* prev = NULL;
1306
  AosShellChannel* curr = stream->channel;
1307
1308
  // iterate through the list and search for the specified channel
1309
  while (curr != NULL) {
1310
    // if the channel was found
1311
    if (curr == channel) {
1312
      chSysLock();
1313
      // special case: the first channel matches (prev is NULL)
1314
      if (prev == NULL) {
1315
        stream->channel = curr->next;
1316
      } else {
1317
        prev->next = channel->next;
1318
      }
1319
      curr->next = NULL;
1320
      curr->flags &= ~AOS_SHELLCHANNEL_ATTACHED;
1321
      chSysUnlock();
1322
      return AOS_SUCCESS;
1323
    }
1324
  }
1325
1326
  // if the channel was not found, return an error
1327
  return AOS_ERROR;
1328
}
1329
1330
/**
1331
 * @brief   Enable a AosSheööChannel as input.
1332
 *
1333
 * @param[in] channel   The channel to enable as input.
1334
 */
1335
void aosShellChannelInputEnable(AosShellChannel* channel)
1336
{
1337 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1338 ba516b61 Thomas Schöpping
1339
  chSysLock();
1340
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
1341
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
1342
  chSysUnlock();
1343
1344
  return;
1345
}
1346
1347
/**
1348
 * @brief   Disable a AosSheööChannel as input.
1349
 *
1350
 * @param[in] channel   The channel to disable as input.
1351
 */
1352
void aosShellChannelInputDisable( AosShellChannel* channel)
1353
{
1354 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1355 ba516b61 Thomas Schöpping
1356
  chSysLock();
1357
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
1358
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
1359
  chSysUnlock();
1360
1361
  return;
1362
}
1363
1364
/**
1365
 * @brief   Enable a AosSheööChannel as output.
1366
 *
1367
 * @param[in] channel   The channel to enable as output.
1368
 */
1369
void aosShellChannelOutputEnable(AosShellChannel* channel)
1370
{
1371 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1372 ba516b61 Thomas Schöpping
1373
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1374
1375
  return;
1376
}
1377
1378
/**
1379
 * @brief   Disable a AosSheööChannel as output.
1380
 *
1381
 * @param[in] channel   The channel to disable as output.
1382
 */
1383
void aosShellChannelOutputDisable(AosShellChannel* channel)
1384
{
1385 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1386 ba516b61 Thomas Schöpping
1387
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1388
1389
  return;
1390
}
1391
1392
/**
1393 e545e620 Thomas Schöpping
 * @brief   Thread main function.
1394
 *
1395
 * @param[in] aosShellThread    Name of the function;
1396
 * @param[in] shell             Pointer to the shell object.
1397
 */
1398
THD_FUNCTION(aosShellThread, shell)
1399
{
1400
  aosDbgCheck(shell != NULL);
1401
1402
  // local variables
1403 ba516b61 Thomas Schöpping
  eventmask_t eventmask;
1404
  eventflags_t eventflags;
1405
  AosShellChannel* channel;
1406
  aos_status_t readeval;
1407
  size_t nchars = 0;
1408 e545e620 Thomas Schöpping
  size_t nargs = 0;
1409 ba516b61 Thomas Schöpping
  aos_shellcommand_t* cmd;
1410
1411
1412
  // register OS related events
1413
  chEvtRegisterMask(((aos_shell_t*)shell)->os.eventSource, &(((aos_shell_t*)shell)->os.eventListener), AOS_SHELL_EVENTMASK_OS);
1414
  // register events to all input channels
1415
  for (channel = ((aos_shell_t*)shell)->stream.channel; channel != NULL; channel = channel->next) {
1416 dd8738ea Thomas Schöpping
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), AOS_SHELL_EVENTMASK_INPUT, channel->listener.wflags);
1417 ba516b61 Thomas Schöpping
  }
1418 e545e620 Thomas Schöpping
1419
  // fire start event
1420
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1421
1422 ba516b61 Thomas Schöpping
  // print the prompt for the first time
1423
  _printPrompt((aos_shell_t*)shell);
1424
1425 e545e620 Thomas Schöpping
  // enter thread loop
1426
  while (!chThdShouldTerminateX()) {
1427 ba516b61 Thomas Schöpping
    // wait for event and handle it accordingly
1428
    eventmask = chEvtWaitOne(ALL_EVENTS);
1429
1430
    // handle event
1431
    switch (eventmask) {
1432
1433
      // OS related events
1434
      case AOS_SHELL_EVENTMASK_OS:
1435
      {
1436
        eventflags = chEvtGetAndClearFlags(&((aos_shell_t*)shell)->os.eventListener);
1437
        // handle shutdown/restart events
1438
        if (eventflags & AOS_SYSTEM_EVENTFLAGS_SHUTDOWN) {
1439
          chThdTerminate(((aos_shell_t*)shell)->thread);
1440
        } else {
1441
          // print an error message
1442
          chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown OS event received (0x%08X)\n", eventflags);
1443
        }
1444 e545e620 Thomas Schöpping
        break;
1445
      }
1446
1447 ba516b61 Thomas Schöpping
      // input events
1448
      case AOS_SHELL_EVENTMASK_INPUT:
1449
      {
1450
        // check and handle all channels
1451
        channel = ((aos_shell_t*)shell)->stream.channel;
1452
        while (channel != NULL) {
1453
          eventflags = chEvtGetAndClearFlags(&channel->listener);
1454
          // if there is new input
1455
          if (eventflags & CHN_INPUT_AVAILABLE) {
1456 dd8738ea Thomas Schöpping
            // read input from channel
1457
            readeval = _readChannel((aos_shell_t*)shell, channel, &nchars);
1458
            // parse input line to argument list only if the input shall be executed
1459
            nargs = (readeval == AOS_SUCCESS && nchars > 0) ? _parseArguments((aos_shell_t*)shell) : 0;
1460
            // check number of arguments
1461
            if (nargs > ((aos_shell_t*)shell)->arglistsize) {
1462
              // error too many arguments
1463
              chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\ttoo many arguments\n");
1464
            } else if (nargs > 0) {
1465
              // search command list for arg[0] and execute callback
1466
              cmd = ((aos_shell_t*)shell)->commands;
1467
              while (cmd != NULL) {
1468
                if (strcmp(((aos_shell_t*)shell)->arglist[0], cmd->name) == 0) {
1469
                  ((aos_shell_t*)shell)->execstatus.command = cmd;
1470
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXEC);
1471
                  ((aos_shell_t*)shell)->execstatus.retval = cmd->callback((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, nargs, ((aos_shell_t*)shell)->arglist);
1472
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_DONE);
1473
                  // notify if the command was not successful
1474
                  if (((aos_shell_t*)shell)->execstatus.retval != 0) {
1475
                    chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "command returned exit status %d\n", ((aos_shell_t*)shell)->execstatus.retval);
1476 ba516b61 Thomas Schöpping
                  }
1477 dd8738ea Thomas Schöpping
                  break;
1478 ba516b61 Thomas Schöpping
                }
1479 dd8738ea Thomas Schöpping
                cmd = cmd->next;
1480
              } /* end of while */
1481 e545e620 Thomas Schöpping
1482 dd8738ea Thomas Schöpping
              // if no matching command was found, print an error
1483
              if (cmd == NULL) {
1484
                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "%s: command not found\n", ((aos_shell_t*)shell)->arglist[0]);
1485 ba516b61 Thomas Schöpping
              }
1486
            }
1487 dd8738ea Thomas Schöpping
1488
            // reset some internal variables and eprint a new prompt
1489
            if (readeval == AOS_SUCCESS && !chThdShouldTerminateX()) {
1490
              ((aos_shell_t*)shell)->inputdata.cursorpos = 0;
1491
              ((aos_shell_t*)shell)->inputdata.lineend = 0;
1492
              _printPrompt((aos_shell_t*)shell);
1493 ba516b61 Thomas Schöpping
            }
1494 e545e620 Thomas Schöpping
          }
1495 ba516b61 Thomas Schöpping
1496
          // iterate to next channel
1497
          channel = channel->next;
1498 e545e620 Thomas Schöpping
        }
1499 ba516b61 Thomas Schöpping
        break;
1500 e545e620 Thomas Schöpping
      }
1501 ba516b61 Thomas Schöpping
1502
      // other events
1503
      default:
1504
      {
1505
        // print an error message
1506
        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown event received (0x%08X)\n", eventmask);
1507
        break;
1508 e545e620 Thomas Schöpping
      }
1509
1510 ba516b61 Thomas Schöpping
    } /* end of switch */
1511
1512
  } /* end of while */
1513 e545e620 Thomas Schöpping
1514
  // fire event and exit the thread
1515
  chSysLock();
1516
  chEvtBroadcastFlagsI(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXIT);
1517
  chThdExitS(MSG_OK);
1518
  // no chSysUnlock() required since the thread has been terminated an all waiting threads have been woken up
1519
}
1520 ba516b61 Thomas Schöpping
1521
#endif /* AMIROOS_CFG_SHELL_ENABLE == true */