exited normally

--back of the flyer--

MyHL エスケープシーケンスの処理を追加

MyHLは、haribote HL-8cを改造したバージョンです。

MyHL エスケープシーケンスの処理を追加

文字列リテラルエスケープシーケンスを使えるようにするために、getTokenCode()の「手抜き実装(エスケープシーケンスを処理していない)」というコメントをつけていた箇所を変更しました。

char *str = malloc(len - 1);
if (str == NULL) {
  printf("Failed to allocate memory\n");
  exit(1);
}
vars[i] = (intptr_t) str;
int j = 0;
for (int k = 1; tokenStrs[i][k] != tokenStrs[i][0]; ++j, ++k) {
  if (tokenStrs[i][k] == '\\') { // エスケープシーケンス
    switch (tokenStrs[i][k + 1]) {
    case 'f': str[j] = 12; break; // FF
    case 'n': str[j] = 10; break; // LF
    case 'r': str[j] = 13; break; // CR
    case 't': str[j] = 9;  break; // HT
    case 'v': str[j] = 11; break; // VT
    }
    ++k;
    continue;
  }
  str[j] = tokenStrs[i][k];
}
str[j] = 0;

とりあえず、\f(フォームフィード)、\n(改行)、\r(キャリッジリターン)、\t(タブ)、\v(垂直タブ)を実装しています。

MyHL SIGSEGVシグナルを処理してコマンド履歴を保存する

MyHLは、haribote HL-8cを改造したバージョンです。

MyHL SIGSEGVシグナルを処理してコマンド履歴を保存する

@@ -1881,6 +1887,7 @@ int main(int argc, const char **argv)

 #if defined(__APPLE__) || defined(__linux__)
   trapSignal(SIGCONT, contHandler);
+  trapSignal(SIGSEGV, segvHandler);
 #endif

   int status = 0;
void segvHandler(int signum)
{
  printf("Segmentation fault\n");
  destroyTerm();
  setCanonicalMode();
  exit(1);
}

trapSignal()は、MyHL SIGCONTシグナルを処理してREPLをフォアグラウンドにするで追加した関数です。

destroyTerm()の中でsaveHistory()を呼び出しています。どちらもharibote HL-4aで追加した関数です。

MyHL historyコマンドを追加

MyHLは、haribote HL-8cを改造したバージョンです。

historyコマンドを追加

historyコマンドを実装しました。

haribote HL-4aで実装したコマンド履歴は、上下矢印キーで履歴をたどる機能のみをサポートしていました。

上下矢印キーで履歴をたどれるのは便利ですが、古いものにたどりつくには何度もキーを押さないといけません。

MyHLの開発を進める中で、もっと速く履歴からコードを呼び出したいと感じることが増えたため実装しました。

historyコマンド

  • historyコマンド: 以前に実行したコマンドやコードを古い順に、引数に指定した行数だけ表示する

結果の左側に表示されている数値は、古いものから順に割り当てたid番号です。

確認したid番号や相対位置を使って履歴からコマンドやコードを検索し実行できます。

$ haribote
[1]> history 10
 9  print 1+2*3
10  print (1+2)*3
11  a = 0; print ++a; print a
12  a = 0; print a++; print a
13  int a[30] = { 1, 1 }
14  for (i = 2; i < 30; i++) { a[i] = a[i - 2] + a[i - 1]; }
15  for (i = 0; i < 10; i++) { print a[i]; }
16  for (i = 0; i < 20; i++) { print a[i]; }
17  exit
18  history 10

!コマンドを使って履歴からコードを実行する

  • ! (Bang) コマンド: id番号または相対位置を表す負数を指定して、その位置にあるコマンドやコードを実行する

!に続けて、数値または感嘆符を入力することで、実行するコマンドやコードを指定します。

!n

!nは、id番号(n番)の位置にあるコマンドやコードを実行します。

[2]> !13
int a[30] = { 1, 1 }
[3]> !14
for (i = 2; i < 30; i++) { a[i] = a[i - 2] + a[i - 1]; }
[4]> !15
for (i = 0; i < 10; i++) { print a[i]; }
1
1
2
3
5
8
13
21
34
55

!-n

!-nは、直近のものから数えてnつ前に実行したコマンドやコードを実行します。

[5]> prints "Riff Raff"
Riff Raff
[6]> prints "Jailbreak"
Jailbreak
[7]> prints "Beating Around The Bush"
Beating Around The Bush
[8]> history 5
21  for (i = 0; i < 10; i++) { print a[i]; }
22  prints "Riff Raff"
23  prints "Jailbreak"
24  prints "Beating Around The Bush"
25  history 5
[9]> !-3
prints "Jailbreak"
Jailbreak

!!

!!は、直前に実行したコマンドやコードを再実行するための記法です。

結果は!-1と同じになります。

[10]> !!
prints "Jailbreak"
Jailbreak

:eサフィックス

:eサフィックス!n!-n!!を修飾すると、履歴から検索したコマンドやコードが、入力が確定していない状態でコマンドラインにセットされます。

[11]> !9
print 1+2*3
7
[12]> !!:e
[13]> print 1+2*3

コマンドラインを編集して入力を確定すると、コマンドやコードが実行されます。

[13]> print (1+2)*3
9

!n!-n!!:eは直接履歴に追加されず、実行したコマンドやコードが追加されます。

特に以前のコマンドやコードを編集して実行したいときに、実装済みのコマンドライン編集とあわせて使うと便利な機能です。

実装メモ

自分が使いやすいようにbashの履歴展開に似せて作っていますが、hariboteの「!」は単なるREPLのコマンドです。

else if (text[0] == '!' && (text[1] == '!' || text[1] == '-' || isNumber(text[1]))) {
  int reedit = endsWithSuffix('e', text), i;
  char *end;
  if (reedit)
    text[inputLen - 2] = 0;
  if (text[1] == '!')
    i = -1;
  else if (i = strtol(&text[1], &end, 10), *end != 0) {
    printf("!: %s: numeric argument required\n", &text[1]);
    continue;
  }
  Command *cmd = text[1] == '!' || text[1] == '-'
    ? getPrevCmd(i - 1, text) // -1は"!"コマンド
    : getPrevCmdById(i, text);
  if (reedit) {
    ignorePrevHistory();
    eraseLine();
    setCursorX(cmd->len);
    printf("[%d]> %s", ++nLines, cmd->str);
    next = 0;
  }
  else {
    updatePrevHistory(cmd->str, cmd->len);
    resetCmdHistoryPos();
    setCursorX(0);
    printf("%s\n", cmd->str);
    reexecute = 1;
  }
}
else if (strncmp(text, "history ", 8) == 0) {
  int num = strtol(&text[8], NULL, 10);
  listPrevCmds(num, text);
}

historyコマンドや!コマンドで履歴を検索する処理は、上下矢印キーでコマンド履歴をたどる処理で使っているshowHistory()のラッパーを使って書いています。

int rewindHistory(int num)
{
  assert(num < 0);

  num = -num;
  int id = cmdHist.count - num + 1;
  if (id <= 0)
    return 0;

  int end = num;
  for (int i = 0; i < end; ++i)
    showHistory(Prev, NULL);
  return id;
}

showHistory()は、第二引数にNULLを渡すと、早期リターンしてコマンドやコードを表示しないように変更しました。履歴をたどる処理は haribote HL-4aと同じです。

historyコマンドの結果の左側に表示されている数値は、cmdHist.headに登録されているコマンドやコードの位置を1として、順に割り当てたid番号です。

id番号は内部的に保持せず、historyコマンドが実行される都度、int id = cmdHist.count - num + 1;で計算しています。

※ 変数numは、直近のものから数えて履歴をいくつたどるかを表す数値です。

haribote HL-4aに係る変更

HISTORY_SIZEを変更

-#define HISTORY_SIZE 100
+#define HISTORY_SIZE 500

不要なcontinue文を削除

@@ -1664,14 +1664,12 @@ int main(int argc, const char **argv)
       printf("[%d]> ", nLines);
       showHistory(Prev, text);
       next = 0;
-      continue;
     }
     else if (strcmp(text, "nexthist") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Next, text);
       next = 0;
-      continue;
     }
 #endif
     else if (strncmp(text, "run ", 4) == 0) {
@@ -1684,7 +1682,6 @@ int main(int argc, const char **argv)
       eraseAll();
       printf("[%d]> ", nLines);
       next = 0;
-      continue;
     }
 #endif
     else {

prevhistとnexthistをリネーム

@@ -1537,8 +1676,8 @@ char *readLine(char *str, int size, FILE *stream)
       switch (ch) {
       case 67: if (cursorX < i) { write(0, "\e[C", 3); ++cursorX; } continue; // RightArrow
       case 68: if (cursorX > 0) { write(0, "\e[D", 3); --cursorX; } continue; // LeftArrow
-      case 65: strncpy(str, "prevhist", 9); break; // UpArrow
-      case 66: strncpy(str, "nexthist", 9); break; // DownArrow
+      case 65: strncpy(str, "__PREV_HIST", 12); break; // UpArrow
+      case 66: strncpy(str, "__NEXT_HIST", 12); break; // DownArrow
       }
       setCanonicalMode();
       return str;
@@ -1659,13 +1659,13 @@ int main(int argc, const char **argv)
     if (strcmp(text, "exit") == 0)
       goto exit;
 #if defined(__APPLE__) || defined(__linux__)
-    else if (strcmp(text, "prevhist") == 0) {
+    else if (strcmp(text, "__PREV_HIST") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Prev, text);
       next = 0;
     }
-    else if (strcmp(text, "nexthist") == 0) {
+    else if (strcmp(text, "__NEXT_HIST") == 0) {
       eraseLine();
       printf("[%d]> ", nLines);
       showHistory(Next, text);

MyHL SIGCONTシグナルを処理してREPLをフォアグラウンドにする

MyHLは、haribote HL-8cを改造したバージョンです。

SIGCONTシグナルを処理してREPLをフォアグラウンドにする

@@ -21,6 +21,7 @@
 #if defined(__APPLE__) || defined(__linux__)
 #include <unistd.h>
 #include <termios.h>
+#include <signal.h>
 #endif

 typedef unsigned char *String;
@@ -1595,6 +1596,26 @@ char *readLine(char *str, int size, FILE *stream)
   setCanonicalMode();
   return NULL;
 }
+
+int resumeAfterSuspend = 0;
+
+typedef void (*SigHandler)(int);
+
+SigHandler trapSignal(int sig, SigHandler handler)
+{
+  struct sigaction act, old;
+  act.sa_handler = handler;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags = SA_RESTART;
+  return sigaction(sig, &act, &old) < 0
+    ? NULL
+    : old.sa_handler;
+}
+
+void contHandler(int signum)
+{
+  resumeAfterSuspend = 1;
+}
 #else
 #define readLine fgets
 #endif
@@ -1610,12 +1631,22 @@ int main(int argc, const char **argv)
     exit(0);
   }

+#if defined(__APPLE__) || defined(__linux__)
+  trapSignal(SIGCONT, contHandler);
+#endif
+
   int status = 0;
   initTerm();
   for (int next = 1, nLines = 0;;) {
     if (next)
       printf("[%d]> ", ++nLines);
     if (readLine(text, LINE_SIZE, stdin) == NULL) {
+#if defined(__APPLE__) || defined(__linux__)
+      if (resumeAfterSuspend) {
+        resumeAfterSuspend = 0;
+        continue;
+      }
+#endif
       printf("\n");
       goto exit;
     }

hariboteのREPLからターミナルのシェルに戻り、lsを打った後にfgコマンドで再開しようとしたらできなかったので直しました。

MyHL 複合代入演算子を追加

MyHLは、haribote HL-8cを改造したバージョンです。

複合代入演算子を追加

@@ -139,6 +139,7 @@ int tc[10000]; // トークンコード列を格納する
 enum {
   PlusPlus,
   MinusMinus,
+  Ex,
   Equal,
   NotEq,
   Les,
@@ -158,7 +159,16 @@ enum {
   AndAnd,
   OrOr,
   Assign,
-  Ex,
+  PlusAssign,
+  MinusAssign,
+  MultiAssign,
+  DiviAssign,
+  ModAssign,
+  AndAssign,
+  XorAssign,
+  OrAssign,
+  ShlAssign,
+  ShrAssign,

   Lparen,
   Rparen,
@@ -214,6 +224,7 @@ enum {
 String defaultTokens[] = {
   "++",
   "--",
+  "!",
   "==",
   "!=",
   "<",
@@ -233,7 +244,16 @@ String defaultTokens[] = {
   "&&",
   "||",
   "=",
-  "!",
+  "+=",
+  "-=",
+  "*=",
+  "/=",
+  "%=",
+  "&=",
+  "^=",
+  "|=",
+  "<<=",
+  ">>=",

   "(",
   ")",
@@ -315,6 +335,16 @@ typedef enum {
   Infix_AndAnd = 12,
   Infix_OrOr = 13,
   Infix_Assign = 15,
+  Infix_PlusAssign = 15,
+  Infix_MinusAssign = 15,
+  Infix_MultiAssign = 15,
+  Infix_DiviAssign = 15,
+  Infix_ModAssign = 15,
+  Infix_AndAssign = 15,
+  Infix_XorAssign = 15,
+  Infix_OrAssign = 15,
+  Infix_ShlAssign = 15,
+  Infix_ShrAssign = 15,
   LowestPrecedence = 99,
   NoPrecedence = 100
 } Precedence;
@@ -322,6 +352,7 @@ typedef enum {
 Precedence precedenceTable[][2] = {
   [PlusPlus]   = {NoPrecedence,     Prefix_PlusPlus},
   [MinusMinus] = {NoPrecedence,     Prefix_MinusMinus},
+  [Ex]         = {NoPrecedence,     Prefix_Ex},
   [Equal]      = {Infix_Equal,      NoPrecedence},
   [NotEq]      = {Infix_NotEq,      NoPrecedence},
   [LesEq]      = {Infix_LesEq,      NoPrecedence},
@@ -341,7 +372,16 @@ Precedence precedenceTable[][2] = {
   [ShiftLeft]  = {Infix_ShiftLeft,  NoPrecedence},
   [ShiftRight] = {Infix_ShiftRight, NoPrecedence},
   [Assign]     = {Infix_Assign,     NoPrecedence},
-  [Ex]         = {NoPrecedence,     Prefix_Ex},
+  [PlusAssign]  = {Infix_PlusAssign,  NoPrecedence},
+  [MinusAssign] = {Infix_MinusAssign, NoPrecedence},
+  [MultiAssign] = {Infix_MultiAssign, NoPrecedence},
+  [DiviAssign]  = {Infix_DiviAssign,  NoPrecedence},
+  [ModAssign]   = {Infix_ModAssign,   NoPrecedence},
+  [AndAssign]   = {Infix_AndAssign,   NoPrecedence},
+  [XorAssign]   = {Infix_XorAssign,   NoPrecedence},
+  [OrAssign]    = {Infix_OrAssign,    NoPrecedence},
+  [ShlAssign]   = {Infix_ShlAssign,   NoPrecedence},
+  [ShrAssign]   = {Infix_ShrAssign,   NoPrecedence},
 };

 enum { Infix, Prefix, EndOfStyles };
@@ -618,6 +658,12 @@ int evalExpression(Precedence precedence)
         e0 = evalExpression(encountered);
         putIc(OpCpy, &vars[res], &vars[e0], 0, 0);
       }
+      else if (PlusAssign <= tc[epc] && tc[epc] <= ShrAssign) {
+        int oper = tc[epc];
+        ++epc;
+        e0 = evalExpression(encountered);
+        putIc(OpAdd + oper - PlusAssign, &vars[res], &vars[res], &vars[e0], 0);
+      }
       else {
         res = evalInfixExpression(res, encountered - 1, tc[epc]);
       }

MyHL 中置演算子を追加

MyHLは、haribote HL-8cを改造したバージョンです。

左シフト演算子を追加

@@ -151,6 +151,7 @@ enum {
   Divi,
   Mod,
   And,
+  ShiftLeft,
   ShiftRight,
   Assign,
   Ex,
@@ -221,6 +222,7 @@ String defaultTokens[] = {
   "/",
   "%",
   "&",
+  "<<",
   ">>",
   "=",
   "!",
@@ -291,6 +293,7 @@ typedef enum {
   Infix_Mod = 4,
   Infix_Plus = 5,
   Infix_Minus = 5,
+  Infix_ShiftLeft = 6,
   Infix_ShiftRight = 6,
   Infix_LesEq = 7,
   Infix_GtrEq = 7,
@@ -319,6 +322,7 @@ Precedence precedenceTable[][2] = {
   [Divi]       = {Infix_Divi,       NoPrecedence},
   [Mod]        = {Infix_Mod,        NoPrecedence},
   [And]        = {Infix_And,        NoPrecedence},
+  [ShiftLeft]  = {Infix_ShiftLeft,  NoPrecedence},
   [ShiftRight] = {Infix_ShiftRight, NoPrecedence},
   [Assign]     = {Infix_Assign,     NoPrecedence},
   [Ex]         = {NoPrecedence,     Prefix_Ex},
@@ -416,6 +420,7 @@ typedef enum {
   OpDiv,
   OpMod,
   OpBand,
+  OpShl,
   OpShr,
   OpAdd1,
   OpSub1,
@@ -977,6 +982,7 @@ void exec()
     case OpMod:   *icp[1] = *icp[2] %  *icp[3]; icp += 5; continue;
     case OpAdd:   *icp[1] = *icp[2] +  *icp[3]; icp += 5; continue;
     case OpSub:   *icp[1] = *icp[2] -  *icp[3]; icp += 5; continue;
+    case OpShl:   *icp[1] = *icp[2] << *icp[3]; icp += 5; continue;
     case OpShr:   *icp[1] = *icp[2] >> *icp[3]; icp += 5; continue;
     case OpClt:   *icp[1] = *icp[2] <  *icp[3]; icp += 5; continue;
     case OpCle:   *icp[1] = *icp[2] <= *icp[3]; icp += 5; continue;

中置演算子を追加

同じ要領で、次の中置演算子を追加しました。

演算子 トークンコード 文字列
ビットXOR演算子 Xor "^"
ビットXOR演算子 Or "|"
論理AND演算子 AndAnd "&&"
論理OR演算子 OrOr "||"

中置演算子は、決まった場所に決まった内容を書くだけで追加できます。

  • 追加する演算子トークンコードと文字列を追加
  • 追加する演算子の優先順位をテーブルに追加
  • Haribote VM(仮)に命令を追加

ビットXOR演算子を追加する際は、lexer()を一部変更して"^"に対応する必要があります。

@@ -113,8 +113,8 @@ int lexer(String str, int *tc)
       while (isAlphabet(str[pos + len]) || isNumber(str[pos + len]))
         ++len;
     }
-    else if (strchr("=+-*/!%&~|<>?:.#", str[pos]) != NULL) {
-      while (strchr("=+-*/!%&~|<>?:.#", str[pos + len]) != NULL && str[pos + len] != 0)
+    else if (strchr("=+-*/!%&~^|<>?:.#", str[pos]) != NULL) {
+      while (strchr("=+-*/!%&~^|<>?:.#", str[pos + len]) != NULL && str[pos + len] != 0)
         ++len;
     }
     else if (str[pos] == '"') { // 文字列

MyHL 中置演算子と複合代入演算子を追加しやすいように、条件分岐を変更

MyHLは、haribote HL-8cを改造したバージョンです。

中置演算子と複合代入演算子を追加しやすいように、条件分岐を変更

@@ -579,20 +579,13 @@ int evalExpression(Precedence precedence)
         高い(値が小さい)ときは、このブロックを実行せずにこれまでに式を評価した
         結果を呼び出し元に返す。
       */
-      switch (tc[epc]) {
-      case Multi: case Divi: case Mod:
-      case Plus: case Minus:
-      case ShiftRight:
-      case Les: case LesEq: case Gtr: case GtrEq:
-      case Equal: case NotEq:
-      case And:
-        res = evalInfixExpression(res, encountered - 1, tc[epc]);
-        break;
-      case Assign:
+      if (tc[epc] == Assign) {
         ++epc;
         e0 = evalExpression(encountered);
         putIc(OpCpy, &vars[res], &vars[e0], 0, 0);
-        break;
+      }
+      else {
+        res = evalInfixExpression(res, encountered - 1, tc[epc]);
       }
     }
     else