exited normally

--back of the flyer--

haribote HL-3

hariboteは、はりぼて言語の処理系です。

gitを使わなくてもhariboteのhistブランチ(Commits on Mar 22, 2024)のソースコードが利用できるように、ここに置いておきます。

haribote HL-3

haribote HL-3は、プリミティブな制御構文としてgoto文をサポートしています。

goto文は、pcの値を書き換えることで次に実行する命令を変更してプログラムの実行を制御します。

この書き換え・変更をジャンプと呼びます。ジャンプの理解は、他の制御構文や関数呼び出しを実装する際の基礎になります。

他のバージョンのソースコードを読む

/*
  Copyright (c) 2023 Masahiro Oono

  This software is a translator for HL.
  HL (Haribote Language) is a programming language
  designed in 2021 by Hidemi Kawai.
  https://essen.osask.jp/?a21_txt01
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

typedef unsigned char *String;

void loadText(int argc, const char **argv, String text, int size)
{
  if (argc < 2) {
    printf("Usage: %s program-file\n", argv[0]);
    exit(1);
  }

  FILE *fp = fopen(argv[1], "rt");
  if (fp == NULL) {
    printf("Failed to open %s\n", argv[1]);
    exit(1);
  }

  int nItems = fread(text, 1, size - 1, fp);
  fclose(fp);
  text[nItems] = 0;
}

#define MAX_TOKEN_CODE 1000 // トークンコードの最大値
String tokenStrs[MAX_TOKEN_CODE + 1];
int    tokenLens[MAX_TOKEN_CODE + 1];

int vars[MAX_TOKEN_CODE + 1];

int getTokenCode(String str, int len)
{
  static unsigned char tokenBuf[(MAX_TOKEN_CODE + 1) * 10];
  static int nTokens = 0, unusedHead = 0; // 登録済みのトークンの数, 未使用領域へのポインタ

  int i;
  for (i = 0; i < nTokens; ++i) { // 登録済みのトークンコードの中から探す
    if (len == tokenLens[i] && strncmp(str, tokenStrs[i], len) == 0)
      break;
  }
  if (i == nTokens) {
    if (nTokens >= MAX_TOKEN_CODE) {
      printf("Too many tokens\n");
      exit(1);
    }
    strncpy(&tokenBuf[ unusedHead ], str, len); // 見つからなければ新規登録
    tokenBuf[ unusedHead + len ] = 0;
    tokenStrs[i] = &tokenBuf[ unusedHead ];
    tokenLens[i] = len;
    unusedHead += len + 1;
    ++nTokens;

    vars[i] = strtol(tokenStrs[i], NULL, 0); // 定数であれば初期値を設定(定数でなければ0になる)
  }
  return i;
}

inline static int isAlphabet(unsigned char ch)
{
  return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ch == '_';
}

inline static int isNumber(unsigned char ch)
{
  return '0' <= ch && ch <= '9';
}

int lexer(String str, int *tc)
{
  int pos = 0, nTokens = 0; // 現在読んでいる位置, これまでに変換したトークンの数
  int len;
  for (;;) {
    while (str[pos] == ' ' || str[pos] == '\t' || str[pos] == '\n' || str[pos] == '\r')
      ++pos;
    if (str[pos] == 0)
      return nTokens;

    len = 0;
    if (strchr("(){}[];,", str[pos]) != NULL)
      len = 1;
    else if (isAlphabet(str[pos]) || isNumber(str[pos])) {
      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)
        ++len;
    }
    else {
      printf("Lexing error: %.10s\n", &str[pos]);
      exit(1);
    }
    tc[nTokens] = getTokenCode(&str[pos], len);
    pos += len;
    ++nTokens;
  }
}

int tc[10000]; // トークンコードを格納する

int main(int argc, const char **argv)
{
  unsigned char text[10000];
  loadText(argc, argv, text, 10000);

  int equal     = getTokenCode("==", 2);
  int notEq     = getTokenCode("!=", 2);
  int lesEq     = getTokenCode("<=", 2);
  int gtrEq     = getTokenCode(">=", 2);
  int les       = getTokenCode("<", 1);
  int gtr       = getTokenCode(">", 1);
  int lparen    = getTokenCode("(", 1);
  int rparen    = getTokenCode(")", 1);
  int plus      = getTokenCode("+", 1);
  int minus     = getTokenCode("-", 1);
  int period    = getTokenCode(".", 1);
  int semicolon = getTokenCode(";", 1);
  int colon     = getTokenCode(":", 1);
  int assign    = getTokenCode("=", 1);
  int print     = getTokenCode("print", 5);
  int time      = getTokenCode("time", 4);
  int _goto     = getTokenCode("goto", 4);
  int _if       = getTokenCode("if", 2);

  int nTokens = lexer(text, tc);
  tc[nTokens] = tc[nTokens + 1] = tc[nTokens + 2] = tc[nTokens + 3] = period; // エラー表示用

  int pc;
  for (pc = 0; pc < nTokens; ++pc) { // ラベル定義命令を探して位置を登録
    if (tc[pc + 1] == colon)
      vars[tc[pc]] = pc + 2; // ラベル定義命令の次のpc値を変数に記憶させておく
  }
  for (pc = 0; pc < nTokens;) {
    if (tc[pc + 1] == assign && tc[pc + 3] == semicolon)
      vars[tc[pc]] = vars[tc[pc + 2]];
    else if (tc[pc + 1] == assign && tc[pc + 3] == plus && tc[pc + 5] == semicolon)
      vars[tc[pc]] = vars[tc[pc + 2]] + vars[tc[pc + 4]];
    else if (tc[pc + 1] == assign && tc[pc + 3] == minus && tc[pc + 5] == semicolon)
      vars[tc[pc]] = vars[tc[pc + 2]] - vars[tc[pc + 4]];
    else if (tc[pc] == print && tc[pc + 2] == semicolon)
      printf("%d\n", vars[tc[pc + 1]]);
    else if (tc[pc + 1] == colon) { // ラベル定義命令
      pc += 2; // 読み飛ばす
      continue;
    }
    else if (tc[pc] == _goto && tc[pc + 2] == semicolon) {
      pc = vars[tc[pc + 1]];
      continue;
    }
    else if (tc[pc] == _if && tc[pc + 1] == lparen && tc[pc + 5] == rparen && tc[pc + 6] == _goto && tc[pc + 8] == semicolon) {
      int lhs = vars[tc[pc + 2]], op = tc[pc + 3], rhs = vars[tc[pc + 4]];
      int dest = vars[tc[pc + 7]];
      if (op == equal && lhs == rhs) { pc = dest; continue; }
      if (op == notEq && lhs != rhs) { pc = dest; continue; }
      if (op == lesEq && lhs <= rhs) { pc = dest; continue; }
      if (op == gtrEq && lhs >= rhs) { pc = dest; continue; }
      if (op == les   && lhs <  rhs) { pc = dest; continue; }
      if (op == gtr   && lhs >  rhs) { pc = dest; continue; }
    }
    else if (tc[pc] == time && tc[pc + 1] == semicolon)
      printf("time: %.3f[sec]\n", clock() / (double) CLOCKS_PER_SEC);
    else
      goto err;

    while (tc[pc] != semicolon)
      ++pc;
    ++pc; // セミコロンを読み飛ばす
  }
  exit(0);
err:
  printf("Syntax error: %s %s %s %s\n", tokenStrs[tc[pc]], tokenStrs[tc[pc + 1]], tokenStrs[tc[pc + 2]], tokenStrs[tc[pc + 3]]);
  exit(1);
}

他のバージョンのソースコードを読む

参考

「10日くらいでできる!プログラミング言語自作入門」
インタプリタC言語で実装しながらプログラミング言語自作に入門するテキストです。無料で読めます。続編ではインタプリタJITコンパイラに改造します。

「10日くらいでできる!プログラミング言語自作入門」の続編#1-1
プログラミング言語自作入門で実装したインタプリタを、x86用またはx64用のJITコンパイラに改造します。後半では、レジスタ変数の導入や無駄なメモリアクセスの削減、コンパイル時の定数計算などの最適化に取り組みます。

「10日くらいでできる!プログラミング言語自作入門」の「残された課題」に関する補足
作者の川合秀実さんが、a21_txt01_10の「残された課題」に取り組む順序について、再検討した内容を共有してくれました。残された課題は、記号表やスタックを自前で実装する必要があるため、はりぼて言語の公式処理系の構造をベースに言語開発をしたい人にとって良い課題となるでしょう。