読者です 読者をやめる 読者になる 読者になる

言語のしくみを読む

パーサで解析されたスクリプトは構造体のリンクで表現されたノードで返される。
state.lvalにつながれたリンク。

node(node_value)
type:NODE_STMTS
void** data

node(node_op)
type:NODE_OP
op:strm_string
len:1
ptr:“|”
lhs:
node(node_values)
type:NODE_ARRAY
data
node(node_value)
value:
t:NODE_VALUE_STRING
v.s->ptr:“Hello world
v.s->len:11
rhs:
node(node_value)
type:NODE_IDENT
v.s->ptr:“STDOUT”
v.s->len:6
ノードをかたち作るのはnode構造体でその末端に納められているデータなどはnode_valueに収められている
共用体でint,long,double,void:,strm_string(const char *ではない)
main関数のnode_run関数呼ぶ

exe_expr(ctx,(node)p->lval,&v)関数に構造体のリンクを投入する
リンクを辿りながらnodeのtypeごとに処理をしている。
最初はtype:NODE_STMTS
data[i]にリンクしてある次のノードを引数にexe_expr関数を再帰呼び出しをする。 リンクしてあるのはNODE_OPタイプのノードになる。
ノードのメンバーであるlhsにリンクされている次のノードを引数にexe_expr関数を呼ぶ
リンクされているのはNODE_ARRAY(node_values)タイプである。
ここであらかじめstrm_arrayの構造体を用意しておく。
ノード内のdataをexe_expr関数に渡して呼び出すとようやく末端データ構造体の
NODE_VALUE(NODE_VALUE_STRING)にたどり着く。
ノード構造体に入っているデータ(string型)をstrm_ptr_value関数を使って
strm_value構造体にタイプ型と一緒にコピーしている。
ノードのデータをstrm_value構造体にコピーする関数はvalue.cにまとめて入っている。
bool値(NODE_VALUE):
 strm_bool_value関数
nil値(NODE_VALUE_NIL):
 strm_nil_value関数
string,IDENT値:
 strm_ptr_value関数
double値:
 strm_flt_value関数
int値
 strm_int_value関数
これら関数を使ってノードに登録しておいたデータ(ここでは"Hello World")を
val(strm_value
)変数に格納しておく。

次にNODE_OPに戻ってきたらrhsを引数にexe_expr関数を呼ぶ。
rhs(右辺)にリンクされているのはNODE_IDENT(node_value)タイプで
strm_var_get関数を呼ぶ、このとき引数には右辺の"STDOUTが渡されている。
このstreemに設定してある識別子を探しているようだ。
見つけた識別子はstrm_value配列(args[2])保管しておき、exec_call関数に引数として渡す。
今回はSTRM_VALUE_CFUNCのexec_bar関数を呼び出す。
exec_bar関数内でlhsがARRAYタイプを探して行き
strm_alloc_stream関数を呼び実際にコールする関数をstart_funcに登録する
strm_task構造体
  id:-1
  mode:strm_task_prod
  start_func:arr_exec関数
  close_func:arr_finish関数
  data:
  dst:NULL
  nextd:NULL
  flags:0
続いて右辺の処理
rhs:ioを選択しstrm_value_io関数を呼び
strm_io構造体に値を設定する
  type:STRM_OBJ_IO
  fd:1
  mode:2
  read_task = 0x0
  write_task:0x0
これをstrm_io_open関数に渡す。
modeがSTRM_IO_WRITEなのでstrm_task構造体に設定して返してくる。
strm_task構造体
  id:-1
  mode:strm_task_cons
  start_func:write_cb関数
  close_func:write_close関数
  data:
  dst:NULL
  nextd:NULL
  flags:0
/task x task/
strem_connect関数を呼ぶ
task_init関数を呼び、task_loop関数をマルチスレッドで待機させておく
strem_connect関数に戻ってきたらstrm_tasak_push関数でtaskをキューに
押し込んでおく。
strem_queue_task構造体に設定する内容
strm:strm_task構造体で左辺のtask
func:arr_exec
data
next:NULL

ここでexec_bar関数に戻り、exec_call関数に戻り、exec_expr関数に戻る。
再帰で呼ばれていたexec_expr関数に次々に戻っていく。
node_run関数まで戻ったらmain関数のstrm_loop関数を呼ぶ
キューにタスクが詰められていたらstrm_queue_exec関数を呼び
実行させる。
このときに関数はwrite_cb関数で"Hello World"を標準出力に表示させた。

言語のしくみを読む

./strem ../examples/02hello.strmで実行
main.cのmain関数内のnode_parse_file(&state,argv[i])で止める
node.c内のnode_parse_file(parse_state p,const char fname)
fnameにはコマンドライン引数"../examples/02hello.strm"が入っている
fnameで指示されたファイルをリードモードで開いて読み込む。
実際にパースしているのはnode_parse_input関数。
yyrestart,yyparse関数内は複雑そうで見ていない。

結果は渡されたstate変数のlvalフィールドに格納されている。

通常はverbose変数はFALSEになっているがこれをTRUEにして
パースされたnodeを表示させてみる。

STMTS:
OP:
op: |
ARRAY:
VALUE(STRING): “Hello World
IDENT: STDOUT

もともとのスクリプト
[“Hello World”] | STDOUT

他のスクリプトも表示させてみる。

01car.strm
スクリプト
STDIN | STDOUT
パース展開
STMTS:
 OP:
  op: |
  IDENT: STDIN
  IDENT: STDOUT

03fizzbuzz.strm
スクリプト
seq(100) | {x ->
  if x % 15 == 0 {
    "FizzBuzz"
  }
  else if x % 3 == 0 {
    "Fizz"
  }
  else if x % 5 == 0 {
    "Buzz"
  }
  else {
    x
  }
} | STDOUT
パース展開
STMTS:
 OP:
  op: |
  OP:
   op: |
   CALL:
     NIL
     seq
     ARRAY:
      VALUE(NUMBER): 100
     NIL
   LAMBDA:
    ARGS(1):
     x
    IF:
     OP:
      op: ==
      OP:
       op: %
       IDENT: x
       VALUE(NUMBER): 15
      VALUE(NUMBER): 0
    THEN:
     VALUE(STRING): "FizzBuzz"
    ELSE:
     IF:
      OP:
       op: ==
       OP:
        op: %
        IDENT: x
        VALUE(NUMBER): 3
       VALUE(NUMBER): 0
     THEN:
      VALUE(STRING): "Fizz"
     ELSE:
      IF:
       OP:
        op: ==
        OP:
         op: %
         IDENT: x
         VALUE(NUMBER): 5
        VALUE(NUMBER): 0
      THEN:
       VALUE(STRING): "Buzz"
      ELSE:
       IDENT: x
  IDENT: STDOUT

04emit.strm
スクリプト
seq(100) | {x -> emit x, x} | STDOUT
パース展開
      STMTS:
      OP:
      op: |
      OP:
      op: |
      CALL:
      NIL
      seq
      ARRAY:
      VALUE(NUMBER): 100
      NIL
      LAMBDA:
      ARGS(1):
      x
      EMIT:
      ARRAY:
      IDENT: x
      IDENT: x
      IDENT: STDOUT

05skip.strm
スクリプト
seq(100) | {x -> if x % 2 == 1 {skip}; x} | STDOUT
パース展開
    STMTS:
    OP:
    op: |
    OP:
    op: |
    CALL:
    NIL
    seq
    ARRAY:
    VALUE(NUMBER): 100
    NIL
    LAMBDA:
    ARGS(1):
    x
    STMTS:
    IF:
    OP:
    op: ==
    OP:
            op: %
            IDENT: x
            VALUE(NUMBER): 2
    VALUE(NUMBER): 1
    THEN:
    IDENT: skip
    IDENT: x
    IDENT: STDOUT

06echo.strm
スクリプト
    tcp_server(8007) | {s ->
    s | s
    }
パース展開

    STMTS:
    OP:
    op: |
    CALL:
    NIL
    tcp_server
    ARRAY:
    VALUE(NUMBER): 8007
    NIL
    LAMBDA:
    ARGS(1):
    s
    OP:
    op: |
    IDENT: s
    IDENT: s

07netcat.strm
スクリプト
    s = tcp_socket("localhost", 8007)
    STDIN | s
    s | STDOUT
パース展開
    STMTS:
    LET: s
    CALL:
    NIL
    tcp_socket
    ARRAY:
    VALUE(STRING): "localhost"
    VALUE(NUMBER): 8007
    NIL
    OP:
    op: |
    IDENT: STDIN
    IDENT: s
    OP:
    op: |
    IDENT: s
    IDENT: STDOUT

08chat.strm
スクリプト
broadcast = chan()
tcp_server(8008) | {s ->
  broadcast | s   # connect to broadcast channel
  s | broadcast   # broadcast incoming message
}
パース展開
  STMTS:
  LET: broadcast
    CALL:
      NIL
      chan
      ARRAY:
      NIL
  OP:
    op: |
    CALL:
      NIL
      tcp_server
      ARRAY:
      VALUE(NUMBER): 8008
      NIL
    LAMBDA:
    ARGS(1):
      s
    STMTS:
      OP:
      op: |
      IDENT: broadcast
      IDENT: s
      OP:
      op: |
      IDENT: s
      IDENT: broadcast

言語のしくみを読む

node_run関数
strm_seq_init(),strm_socket_init()が準備を完了させたら
次に呼ぶのがexec_expr関数で、渡されたnodeをswitch文で分岐させながら
taskを作ってキューに登録する。登録されたtaskはqueue.cのstrm_queue_exec関数が
実行を行っている。
キューに登録された関数はwrite_cb関数で渡されたデータは"Hello world"という文字列
であった。

exec_expr関数にnodeが渡されると最初に取り出されるのはNODE_STMTS  
そこから再帰的にexec_expr関数が呼ばれNODE_OPが取り出される。
strm_string opは文字列に"|“を持っている
node * lhs NODE_ARRAY
node * rhs NODE_IDENT

lhsからexec_expr関数を呼び出し、中身は"Hello world"を持っている
一度呼び出し側に戻り今度はrhsでexec_expr関数を呼び出す。
NODE_OPに帰ってきたらexec_call関数を呼び出す。
この時呼び出される関数はexec_bar関数で、今回はlhs:arrayのコメントが
あるパートからstrm_alloc_stream関数を呼びstrm_taskにタスクを登録している
start_funcにwrite_cb関数を登録している。

他のスレッドによりstrm_queue_exec関数内からwrite_cb関数を
呼び、画面に(STDOUT)に"Hello World"を表示させている

言語のしくみを読む

main関数からstrm_var_def関数を呼び出す
引数の"ARGV"はstrm_stringに保管されてる、av変数にはコマンドライン
引数が入っているので"ARGV"の名前で保管される。

次はnode_run関数を呼ぶ //exec.c

node_init関数内でstrm_var_def関数を使ってシンボルをハッシュに登録している
“STDIN”->strm_io_new(0, STRM_IO_READ)
“STDOUT”->strm_io_new(1, STRM_IO_WRITE)
“STDERR”->strm_io_new(2, STRM_IO_WRITE)
“puts”->strm_cfunc_value(exec_puts)
“+”->strm_cfunc_value(exec_plus)
“-”->strm_cfunc_value(exec_minus)
“*”->strm_cfunc_value(exec_mult)
“/”->strm_cfunc_value(exec_div)
“<”->strm_cfunc_value(exec_lt)
“<=”->strm_cfunc_value(exec_le)
“>”->strm_cfunc_value(exec_gt)
“>=”->strm_cfunc_value(exec_ge)
“==”->strm_cfunc_value(exec_eq)
“!=”->strm_cfunc_value(exec_neq)
“|”->strm_cfunc_value(exec_bar)
“%”->strm_cfunc_value(exec_mod)
“fread”->strm_cfunc_value(exec_fread)
“fwrite”->strm_cfunc_value(exec_fwrite)

strem_seq_init関数を呼ぶ strem_var_def関数を使ってseq->strem_cfunc_value(exec_seq)を登録している
strem_socket_init関数を呼ぶ
strem_var_def関数を使って
tcp_server->strem_cfunc_value(exec_tcp_server)
strem_var_def関数を使って
tcp_socket->strem_cfunc_value(exec_tcp_socket)
を登録している

言語のしくみを読む

main関数から読んでいく

$ ./streem ../examples/02hello.strm

main関数
オプション処理部はパスして
node_parse_init(&state) //node.c
state構造体の初期化
e_progはNULLなのでelse節以降を実行、引数を持っているので最後のelse節を実行
for(i = 1;i < argc;i++){
node_parse_file(&state,argv[i]) //node_parse_file node.c
}
node_parse_fileに渡されるのは引数の"../examples/02hekko.strm"になる。
渡されたファイル名を使ってreadモードで開かれる。
node_parse_input関数を呼び出す。
yyrestart関数にファイルポインタを渡して呼び出す //yyrestart:y.tab.c
yyparse関数のparser変数を渡して呼び出す
中身はまったくわからない。
parser_state pは
p.nerr = 0
p.lval = (void
) 0x61a510 ?
p.fname = 0x0
p.lineno = 2
p.tline = 3
node_parse_file関数に戻ってきてファイルを閉じる
main関数に戻る
strm_ary_new(NULL,argc=2)関数を呼ぶ。//array.c
struct strem_array + struct strem_value * lenだけメモリを確保
main関数に戻る
先ほど確保したstrm_arrayはav変数が指示、strm_value部分はbufが指示している
strm_str_value関数->strm_ptr_value->strm_str_new関数 //string.c
シングルスレッドモードのときとマルチスレッドモードで文字列確保関数を
呼び分けている。
シングルスレッドモードのときはstr_intern関数でsym_tableにkeyと一緒に文字列を保存
マルチスレッドモードの時はstr_new関数でメモリを確保してstrm_stringを返してmain関数の
buf[]配列に保存している。

言語のしくみを読む

Cの開発環境を整備

ソースの分量が増えてきたのでエディタだけで読むのが困難になってきたので
Cの統合環境を入れることにした。
最初はeclise C/C++ Linuxを入れたが使い方がわからず。
次にNetBeans IDE 8.2 C/C++ Linuxを入れたところ比較的わかりやすく
こちらにした。

<installの仕方>

netbeans.org

上記リンク先の画面の右上オレンジ色のボタンを押して「NetBeans IDE 8.2 ダウンロード」画面 に遷移する。 f:id:bitop:20170312112115p:plain 「ダウンロードx64」のボタンを押下。
f:id:bitop:20170312112422p:plain

手元のPCのダウンロードフォルダにnetbeans-8.2-cpp-linux-x64.sh(119.7MB)が
ダウンロードされている。ファイルを選択してマウスの右ボタンでコンテキストメニューを出し
一番下のプロパティを選択。アクセス権タグを選択し、下にある実行欄のプログラムとして
実行可能を チエックし、端末からsudo netbeans-8.2-cpp-linux-x64.shと入力した後は 画面の指示に従ってインストールが進む。
途中-jdkhomeがないといわれるが仔細がわからないため無視して続行した。  現在順調に作動中。  

言語のしくみを読む

2-7AST(抽象構文木)に変換

今回はsrcフォルダーに大幅に追加がなされた。
早速makeしてみるが、node.cコンパイル中にy.tab.hがないとエラーが出ている。
Gitで次のtag(201507)に行ってみるとy.tab.hがあったので201506は無視して201507tag
で実行。
binフォルダーにstreemができていたので

./streem ../examples/01cat.stem
<結果>
dw (stdin 入力)
dw (stdout 出力)
エコーです

./streem ../examples/02hello.stem
<結果>
Hello World

結構おもしろい。

src/main関数の最初でオプションをチエックしているようだが特になにもしていない?
読み込んだファイルをパースしているようだ(フロントエンド)。
そのあとstat変数をnode_run関数に渡してnode.cのexec_expr関数に
実行させている(バックエンド)ようだ。