低レイヤを知りたい人のためのCコンパイラ作成入門(ステップ2)

www.sigbus.info

.intel_syntax noprefix
.globl main
main:
  mov rax, 5
  add rax, 20
  sub rax, 4
  ret
$ cc -g -o tmp tmp.s
$ gdb ./tmp
(gdb) b main
Breakpoint 1 at 0x1125: file tmp.s, line 4.
(gdb) r
Starting program:

Breakpoint 1, main () at tmp.s:4
4         mov rax, 5
(gdb) n
5         add rax, 20
(gdb) p $rax
$1 = 5
(gdb) n
6         sub rax, 4
(gdb) p $rax
$2 = 25
(gdb) n
main () at tmp.s:7
7         ret
(gdb) p $rax
$3 = 21
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "引数の個数が正しくありません\n");
    return 1;
  }

  char *p = argv[1];

  printf(".intel_syntax noprefix\n");
  printf(".globl main\n");
  printf("main:\n");
  printf("  mov rax, %ld\n", strtol(p, &p, 10));

  while (*p) {
    if (*p == '+') {
      p++;
      printf("  add rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    if (*p == '-') {
      p++;
      printf("  sub rax, %ld\n", strtol(p, &p, 10));
      continue;
    }

    fprintf(stderr, "予期しない文字です: '%c'\n", *p);
    return 1;
  }
  printf("  ret\n");
  return 0;
}
$ gdb ./9cc
(gdb) b main
Breakpoint 1 at 0x401bac: file 9cc.c, line 5.
(gdb) r '5+20-4'
Starting program: /home/yoshio/rui_ueyama/9cc/9cc '5+20-4'

Breakpoint 1, main (argc=2, argv=0x7fffffffdfe8) at 9cc.c:5
5         if (argc != 2) {
(gdb) n
10        char *p = argv[1];
(gdb) 
12        printf(".intel_syntax noprefix\n");
(gdb) p p
$1 = 0x7fffffffe389 "5+20-4"
(gdb) n
.intel_syntax noprefix
13        printf(".globl main\n");
(gdb) 
.globl main
14        printf("main:\n");
(gdb) 
main:
15        printf("  mov rax, %ld\n", strtol(p, &p, 10));
(gdb) 
  mov rax, 5
17        while (*p) {
(gdb) p p
$2 = 0x7fffffffe38a "+20-4"
(gdb) n
18          if (*p == '+') {
(gdb) 
19            p++;
(gdb) 
20            printf("  add rax, %ld\n", strtol(p, &p, 10));
(gdb) p p
$3 = 0x7fffffffe38b "20-4"
(gdb) n
  add rax, 20
21            continue;
(gdb) p p
$4 = 0x7fffffffe38d "-4"
(gdb) n
17        while (*p) {
(gdb) 
18          if (*p == '+') {
(gdb) 
24          if (*p == '-') {
(gdb) 
25            p++;
(gdb) 
26            printf("  sub rax, %ld\n", strtol(p, &p, 10));
(gdb) p p
$5 = 0x7fffffffe38e "4"
(gdb) n
  sub rax, 4
27            continue;
(gdb) p p
$6 = 0x7fffffffe38f ""
(gdb) n
17        while (*p) {
(gdb) 
33        printf("  ret\n");
(gdb) 
  ret
34        return 0;
(gdb) 
35      }
(gdb) 

Rust入門 コマンドライン引数を取得して数値に変換する

from_str関数はu64型を返すのではなく、パースが成功したかどうかを表すResult型を返す。
成功した場合には、Ok(v)を返す。vが生成したu64型のデータ
失敗した場合には、Err(e)を返す。eはエラーの理由を説明するエラー値
Resultのexpectメソッドが値vを返す。失敗の場合には、eを表示しプログラムを中断する。

use std::str::FromStr;

fn main() {
    let mut numbers = Vec::new();

    for arg in std::env::args().skip(1) {
        numbers.push(u64::from_str(&arg).expect("error"));
    }
    println!("{:?}", numbers);
}

実行結果

$ ./args_to_number
[]

$ ./args_to_number 0 12
[0, 12]

$ ./args_to_number -1
thread 'main' panicked at 'error: ParseIntError { kind: InvalidDigit }', args_to_number.rs:7:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

$ ./args_to_number 1.2
thread 'main' panicked at 'error: ParseIntError { kind: InvalidDigit }', args_to_number.rs:7:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

$ ./args_to_number a
thread 'main' panicked at 'error: ParseIntError { kind: InvalidDigit }', args_to_number.rs:7:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Rust入門 ベクタ イテレータ fold

ベクタはサイズを変更できる配列である。vec!マクロで生成できる。
ベクタの各要素の合計を求めるプログラム

fn main() {
    let mut v = vec![1,2,3,4,5];
    println!("{:?}", v);
    println!("{}", v.iter().fold(0, |a, b| a + b));
    v.push(6);
    println!("{:?}", v);
    println!("{}", v.iter().fold(0, |a, b| a + b));
}

実行結果

[1, 2, 3, 4, 5]
15
[1, 2, 3, 4, 5, 6]
21

Rust入門 エラトステネスのふるい

エラトステネスのふるいで、30より小さい素数を表示するプログラム
配列sieveに30個のtrueを入れて、jが素数でなければsieve[j]をfalseにする。

fn main() {
    let mut sieve = [true; 30];
    sieve[0] = false;
    sieve[1] = false;
    for i in 2..6 {
        if sieve[i] {
            let mut j = i * i;
            while j < 30 {
                sieve[j] = false;
                j += i;
            }
        }
    }

    for i in 2..sieve.len() {
        if sieve[i] {
            println!("{}", i);
        }
    }
}

テキストエディタを作っています(1)

勉強のためにCでcursesライブラリを使ってシンプルなテキストエディタを作っています。

入力できる文字は半角英数文字(アスキーコード32から126)と改行(LF,アスキーコード10)

できたもの

  • テキストデータを入れる双方向リストを作り、リストにノード(1文字のデータ)を追加・削除する処理
  • キーボードから入力された文字をリストに追加する処理
  • 編集対象のテキストファイルを読み込み、リストに入れる処理
  • リストのテキストデータをファイルに保存する処理
#include <stdlib.h>
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>

struct charactor {
  char c;
  struct charactor *prev;
  struct charactor *next;
};

struct charactor *head;
struct charactor *tail;
struct charactor *current;

struct charactor *make_new_node(char c) {
  struct charactor *new;

  if ((new = (struct charactor*)malloc(sizeof(struct charactor))) == NULL) {
    exit(EXIT_FAILURE);
  }
  new->c = c;
  new->prev = NULL;
  new->next = NULL;

  return new;
}

void add_node(char c) {
  struct charactor *new;

  new = make_new_node(c);

  /* 新しいnodeが既存のnodeを指すようにする設定 */
  new->prev = current;
  new->next = current->next;

  /* 既存のnodeが新しいnodeを指すようにする設定 */
  (new->prev)->next = new;
  (new->next)->prev = new;

  current = new;
}

void delete_node(struct charactor *target) {
  (target->prev)->next = target->next;
  (target->next)->prev = target->prev;
  current = target->prev;
  free(target);
}

void free_list() {
  struct charactor *target;
  target = head;
  while (target != tail) {
    target = target->next;
    free(target->prev);
  }
  free(tail);
}

void initialize_list() {
  head = make_new_node('\0');
  tail = make_new_node('\0');
  head->next = tail;
  tail->prev = head;
  current = head;
}

void read_file(char *file) {
  FILE *fp;
  char c;

  if ((fp = fopen(file, "r")) == NULL) {
    perror("read_file fopen");
    exit(1);
  }

  while ((c = getc(fp)) != EOF) {
    add_node(c);
  }

  if (fclose(fp) == EOF) {
    perror("read_file fclose");
    exit(1);
  }
}

void save_file(char *file) {
  FILE *fp;
  struct charactor *target;

  if ((fp = fopen(file, "w")) == NULL) {
    perror("save_file fopen");
    exit(1);
  }

  target = head->next;
  while (target != tail) {
    if (putc(target->c, fp) == EOF) {
      perror("save_file putc");
      exit(1);
    }
    target = target->next;
  }

  if (fclose(fp) == EOF) {
    perror("save_file fclose");
    exit(1);
  }
}

int main(int argc, char *argv[]) {
  FILE *fp;
  char c;
  char *file_name;

  if (argc != 2) {
    fprintf(stderr, "usage: %s <file>\n", argv[0]);
    exit(1);
  }
  file_name = argv[1];

  initialize_list();
  read_file(file_name);

  initscr();
  noecho();
  cbreak();

  while ((c = getch()) != 'q') {
    if ((32 <= c && c <= 126) || c == 10) {
      add_node(c);
    }
  }

  save_file(file_name);

  endwin();

  free_list();

  return 0;
}

やりたいこと

  • プログラムを終了させる処理(今は仮にqを入力すると終了するようになっている(qが使えないエディタになってしまっている))
  • テキストデータを保存する処理を呼び出す処理(今はプログラムが終了するときに自動的に保存するようになっている)
  • テキストデータを画面に表示する処理(今はユーザーがテキストデータを頭の中で思い浮かべながら編集しないといけない目隠しテキストエディタになっている)
  • バックスペースの処理(今はひたすら追加することしかできない)

Cでcursesライブラリを使う

準備

sudo apt install libncurses-dev

キーボードからの入力を受け取って、その内容を画面に表示するプログラム

#include <curses.h>

int main(void) {
  char buf[1024];
  initscr(); /* 端末の初期化 */
  noecho(); /* echoをオフにする */
  getnstr(buf, sizeof(buf)); /* キーボードからの入力を受け取る */
  move(2, 3); /* カーソル(出力位置)を移動させる */
  attron(A_REVERSE); /* 画面に表示させるときに反転表示させる設定 */
  printw("input = %s\n", buf); /* 画面に表示させる */
  refresh(); /* バッファの更新を画面に反映させる */
  getch(); /* キーボードからの入力を受け取る */
  endwin(); /* 端末の後始末 */
}

コンパイル

$ gcc -lcurses -o curses-sample curses-sample.c