Perlテックブログ

ITエンジニアの成長意欲を刺激する技術考察、モジュール開発の日記。Perlイベントや国内や海外のPerlの記事の紹介。

実行環境とコンパイル環境の分離 - SPVM開発日記

今exeファイルを作ろうと奮闘しているところ。

いろいろ調べると、意外と難しい。でも、いけそうな感じもする。

先にやらないといけないことは、実行環境で利用するデータとコンパイル環境で利用するデータを分離しなくちゃいけないということ。

今は、コンパイル時のデータをそのまま活用する形で、これまではこれたんだけど、exe化することを目標にすると、ここをアイマイにしては通れない感じ。

2~3週間くらいは、これでかかりそうだ。

exeファイルを作り出す - SPVM開発日記

ループ展開は複雑で、難しすぎたので、ひとまずあきらめの心境。難しいことやるまえに、まずは先にベンチやんなきゃね。

ビルドツールもSPVMコアには必須ではない気がしてきたので、こちらは将来CPANモジュール化してくれる人が現れるのを期待して、リリース方法をドキュメント化する方針に変更。

今取り組んでいるのは、exeファイルを作り出すところ。

exeファイルの作り方

SPVMは、デフォルトで、バイトコードをランタイムで実行するように設計されている。

precompileオプションを使うことで、関数をC言語ソースコードファイルに変換して、gccを使ってオブジェクトファイルにすることができる。

ランタイムからプリコンパイルされた関数を呼び出す方式で実行できる。← イマココ

exe化するためには、何が必要だろうか。たぶん、こんな感じなんだろうと思う。

  1. ランタイム自体をコンパイル、SPVMのソースコードコンパイル、main関数を含むブートストラップもコンパイル
  2. そして、この3つをリンクさせて、実行ファイルを作る

すべての関数呼び出しを静的な関数呼び出しにして、ひとつのファイルにまとめ上げる。こんな感じだろうか。

exe化できると何がうれしいの?

exe化できるとランタイムなしで配布できるということが一番うれしいことだね。

たとえばWindowsのexeをポンっと吐き出せると、それがそのままWindowsで動いちゃう。exeを作り出した後は、PerlもSPVMも必要がない。

Perlを開発環境として利用して、Windowsネイティブが作り出せるというわけ。

C#.netはマイクロソフトの公式サポートがあるから、それで作るのが一番便利なので、その方法が一番いいんだけどね。

でも、Windowsは、Win32 APIというC言語のライブラリが存在するから、これをどの言語でもラッピングできたりする。

Delphiとか、Visual C++とか、C++ Builderとか、VB.netとかC#.netとか、いろいろな開発環境でWindowsネイティブが吐き出せるでしょう。

まぁ、それと、仕組み的にはおんなじことをやりたい。

コンパイラオプションとリンカオプション | SPVM開発日記

Perlには、ExtUtils::CBuilderというクラスプラットフォームで、C/C++ソースコードコンパイルと共有ライブラリ作成を助けてくれるモジュールがある。

SPVMは、内部的にこのモジュールを利用して、コンパイルと共有ライブラリの作成を行っている。

SPVMのひとつの目標はC/C++のライブラリ(ソースコードから and .soを呼び出す)のバインディングを簡単に行えることだ。

このためには、gccのオプションを柔軟に設定必要がある。たとえば、C++の場合は、コンパイラgccではなくって、g++になる。これを簡単に変えるためのモジュール SPVM::Build::Configを作成中。

# コンパイラの設定
$build_config->set_cc('g++');

#リンカの設定
$build_config->set_ld('g++');

# ヘッダのインクルードパスの追加
$build_config->add_ccflags('-I/usr/foo/lib');

みたいなのを簡単に書けるようにする。ちょうど、ExtUtils::MakeMakerのオプションと互換性を持たせるようにして設計。

ccflagsの取り扱い | SPVM開発日記

gccを使ってコンパイルするのだけれど、そのオプションを構成する方法について考えている。

gccコマンドラインオプションなので、最終的には出力は空白区切りの文字列になる。

でも、プログラムで扱う場合は、配列にしておいてもよい気がするのだけれど、文字列にしておいて、編集するたびに、配列にしてもよいかもとも思う。負荷は小さいし。

CentOSだとPerlデフォルトのccflagsは次のようになっている。

-fno-strict-aliasing -pipe -fstack-protector -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64

ccflagsの中に「-I/usr/local/include」のようなC言語ヘッダファイルのインクルードパスが含まれていたりする。

Strawbery Perlだと次のようになっている。

 -s -O2 -DWIN32 -DWIN64 -DCONSERVATIVE -D__USE_MINGW_ANSI_STDIO -DPERL_TEXTMODE_SCRIPTS -DPERL_IMPLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -fwrapv -fno-strict-aliasing -mms-bitfields

以下はldflagだけれど、次のようになっている。これはリンカで使われる。

ldflags =' -fstack-protector -L/usr/local/lib'

ldflagsの中に「-L/usr/local/lib」のようなライブラリのパスが含まれていたりする。

PerlC言語コンパイルされているのだけれど、C++のライブラリを使いたい場合やアセンブラfortranコンパイルしないといけないときは、どうするかなど、これから調査する予定。

ライブラリの使い方も、ソースからコンパイルする方法や、パッケージマネージャなんかで、ライブラリとして用意されているというのもある。非常に、理解するのが難しいけれど、うまく満たせる方法を探す。

「例えばPerlを書くエンジニアなんて、2020年にはいなくなってますよ。」

求人サイトの@typeというサイトで「SIerって本当にヤバいの? ひろゆきが語る、業界ごと沈まないためのキャリア戦略」というインタビューが掲載されていたので、記念にリンクしておく。

type.jp

「@typeとひろゆきと440人のはてなブックマーカー」の判断が正しいのか、はたまたPerlエンジニアが2020年にも生き残っているのか。結果は、いかに...。

SPVMの文法がほぼ出来上がったので定義を公開 | SPVM開発日記

SPVMの文法がほぼ出来上がったので定義を公開。yaccの文法で書かれています。

SPVMは静的型言語ですが、できる限りPerlの文法で記述できるように、工夫されています。

1055行で書かれています。

%pure-parser
%parse-param	{ SPVM_COMPILER* compiler }
%lex-param	{ SPVM_COMPILER* compiler }

%{
  #include <stdio.h>
  
  #include "spvm_compiler.h"
  #include "spvm_yacc_util.h"
  #include "spvm_toke.h"
  #include "spvm_op.h"
  #include "spvm_dumper.h"
  #include "spvm_constant.h"
  #include "spvm_type.h"
  #include "spvm_block.h"
  #include "spvm_list.h"
%}

%token <opval> PACKAGE HAS SUB OUR ENUM MY SELF USE 
%token <opval> DESCRIPTOR CONST
%token <opval> IF ELSIF ELSE FOR WHILE LAST NEXT SWITCH CASE DEFAULT EVAL
%token <opval> NAME VAR_NAME CONSTANT
%token <opval> RETURN WEAKEN CROAK NEW
%token <opval> UNDEF VOID BYTE SHORT INT LONG FLOAT DOUBLE STRING OBJECT
%token <opval> AMPERSAND

%type <opval> grammar
%type <opval> opt_packages packages package anon_package package_block
%type <opval> opt_declarations declarations declaration
%type <opval> enumeration enumeration_block opt_enumeration_values enumeration_values enumeration_value
%type <opval> sub anon_sub opt_args args arg invocant has use our
%type <opval> opt_descriptors descriptors
%type <opval> opt_statements statements statement normal_statement if_statement else_statement 
%type <opval> for_statement while_statement switch_statement case_statement default_statement
%type <opval> block eval_block
%type <opval> expression
%type <opval> unop binop
%type <opval> call_sub
%type <opval> array_access field_access weaken_field convert_type array_length 
%type <opval> deref ref
%type <opval> new array_init isa
%type <opval> my_var var
%type <opval> term opt_normal_terms normal_terms normal_term logical_term relative_term
%type <opval> field_name sub_name
%type <opval> type basic_type array_type array_type_with_length const_array_type ref_type  type_or_void

%right <opval> ASSIGN SPECIAL_ASSIGN
%left <opval> OR
%left <opval> AND
%left <opval> BIT_OR BIT_XOR
%left <opval> BIT_AND
%nonassoc <opval> REL
%left <opval> SHIFT
%left <opval> '+' '-' '.'
%left <opval> MULTIPLY DIVIDE REMAINDER
%nonassoc <opval> ISA
%right <opval> NOT '~' '@' SCALAR UMINUS REF DEREF
%nonassoc <opval> INC DEC
%nonassoc <opval> ')'
%left <opval> ARROW
%left <opval> '('
%left <opval> '[' '{'

%%

grammar
  : opt_packages
    {
      $$ = SPVM_OP_build_grammar(compiler, $1);
      compiler->op_grammar = $$;
    }

opt_packages
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	packages
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
  
packages
  : packages package
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $2);
      
      $$ = op_list;
    }
  | package

package
  : PACKAGE basic_type package_block
    {
      $$ = SPVM_OP_build_package(compiler, $1, $2, $3, NULL);
    }
  | PACKAGE basic_type ':' opt_descriptors package_block
    {
      $$ = SPVM_OP_build_package(compiler, $1, $2, $5, $4);
    }

anon_package
  : PACKAGE package_block
    {
      $$ = SPVM_OP_build_package(compiler, $1, NULL, $2, NULL);
    }
  | PACKAGE ':' opt_descriptors package_block
    {
      $$ = SPVM_OP_build_package(compiler, $1, NULL, $4, $3);
    }

package_block
  : '{' opt_declarations '}'
    {
      SPVM_OP* op_class_block = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_CLASS_BLOCK, $1->file, $1->line);
      SPVM_OP_insert_child(compiler, op_class_block, op_class_block->last, $2);
      $$ = op_class_block;
    }

opt_declarations
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	declarations
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        $$ = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, $$, $$->last, $1);
      }
    }

declarations
  : declarations declaration
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $2);
      
      $$ = op_list;
    }
  | declaration

declaration
  : has
  | sub
  | enumeration
  | our ';'
  | use
  | anon_sub

use
  : USE basic_type ';'
    {
      $$ = SPVM_OP_build_use(compiler, $1, $2);
    }

enumeration
  : ENUM enumeration_block
    {
      $$ = SPVM_OP_build_enumeration(compiler, $1, $2);
    }

enumeration_block 
  : '{' opt_enumeration_values '}'
    {
      SPVM_OP* op_enum_block = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ENUM_BLOCK, $1->file, $1->line);
      SPVM_OP_insert_child(compiler, op_enum_block, op_enum_block->last, $2);
      $$ = op_enum_block;
    }

opt_enumeration_values
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	enumeration_values
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
    
enumeration_values
  : enumeration_values ',' enumeration_value 
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $3);
      
      $$ = op_list;
    }
  | enumeration_values ','
    {
      $$ = $1;
    }
  | enumeration_value
  
enumeration_value
  : NAME
    {
      $$ = SPVM_OP_build_enumeration_value(compiler, $1, NULL);
    }
  | NAME ASSIGN CONSTANT
    {
      $$ = SPVM_OP_build_enumeration_value(compiler, $1, $3);
    }

our
  : OUR var ':' type
    {
      $$ = SPVM_OP_build_our(compiler, $2, $4);
    }

has
  : HAS field_name ':' opt_descriptors type ';'
    {
      $$ = SPVM_OP_build_has(compiler, $1, $2, $4, $5);
    }

sub
  : opt_descriptors SUB sub_name ':' type_or_void '(' opt_args ')' block
     {
       $$ = SPVM_OP_build_sub(compiler, $2, $3, $5, $7, $1, $9);
     }
  | opt_descriptors SUB sub_name ':' type_or_void '(' opt_args ')' ';'
     {
       $$ = SPVM_OP_build_sub(compiler, $2, $3, $5, $7, $1, NULL);
     }

anon_sub
  : opt_descriptors SUB ':' type_or_void '(' opt_args ')' block
     {
       SPVM_OP* op_sub_name = SPVM_OP_new_op_name(compiler, "", $2->file, $2->line);
       $$ = SPVM_OP_build_sub(compiler, $2, op_sub_name, $4, $6, $1, $8);
     }
  | opt_descriptors SUB ':' type_or_void '(' opt_args ')' ';'
     {
       SPVM_OP* op_sub_name = SPVM_OP_new_op_name(compiler, "", $2->file, $2->line);
       $$ = SPVM_OP_build_sub(compiler, $2, op_sub_name, $4, $6, $1, NULL);
     }

opt_args
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	args
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
  | invocant
    {
       // Add invocant to arguments
       SPVM_OP* op_args = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
       SPVM_OP_insert_child(compiler, op_args, op_args->last, $1);
       
       $$ = op_args;
    }
  | invocant ',' args
    {
      // Add invocant to arguments
      SPVM_OP* op_args;
      if ($3->id == SPVM_OP_C_ID_LIST) {
        op_args = $3;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $3);
        op_args = op_list;
      }
      
      SPVM_OP_insert_child(compiler, op_args, op_args->first, $1);
       
      $$ = op_args;
    }

args
  : args ',' arg
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $3);
      
      $$ = op_list;
    }
  | args ','
    {
      $$ = $1;
    }
  | arg

arg
  : var ':' type
    {
      $$ = SPVM_OP_build_arg(compiler, $1, $3);
    }

invocant
  : var ':' SELF
    {
      $$ = SPVM_OP_build_arg(compiler, $1, $3);
    }

opt_descriptors
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	descriptors
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
    
descriptors
  : descriptors DESCRIPTOR
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $2);
      
      $$ = op_list;
    }
  | DESCRIPTOR

opt_statements
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	statements
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
    
statements
  : statements statement 
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $2);
      
      $$ = op_list;
    }
  | statement

statement
  : normal_statement
  | if_statement
  | for_statement
  | while_statement
  | block
  | switch_statement
  | case_statement
  | default_statement
  | eval_block

normal_statement
  : normal_term ';'
    {
      $$ = $1;
    }
  | expression ';'
    {
      $$ = $1;
    }
  | ';'
    {
      $$ = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_NULL, compiler->cur_file, compiler->cur_line);
    }

for_statement
  : FOR '(' term ';' term ';' term ')' block
    {
      $$ = SPVM_OP_build_for_statement(compiler, $1, $3, $5, $7, $9);
    }

while_statement
  : WHILE '(' term ')' block
    {
      $$ = SPVM_OP_build_while_statement(compiler, $1, $3, $5);
    }

switch_statement
  : SWITCH '(' normal_term ')' block
    {
      $$ = SPVM_OP_build_switch_statement(compiler, $1, $3, $5);
    }

case_statement
  : CASE normal_term ':'
    {
      $$ = SPVM_OP_build_case_statement(compiler, $1, $2);
    }

default_statement
  : DEFAULT ':'

if_statement
  : IF '(' term ')' block else_statement
    {
      SPVM_OP* op_if = SPVM_OP_build_if_statement(compiler, $1, $3, $5, $6);
      
      // if is wraped with block to allow the following syntax
      //  if (my $var = 3) { ... }
      SPVM_OP* op_block = SPVM_OP_new_op_block(compiler, $1->file, $1->line);
      SPVM_OP_insert_child(compiler, op_block, op_block->last, op_if);
      
      $$ = op_block;
    }

else_statement
  : /* NULL */
    {
      $$ = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_NULL, compiler->cur_file, compiler->cur_line);
    };
  | ELSE block
    {
      $$ = $2;
    }
  | ELSIF '(' term ')' block else_statement
    {
      $$ = SPVM_OP_build_if_statement(compiler, $1, $3, $5, $6);
    }

block 
  : '{' opt_statements '}'
    {
      SPVM_OP* op_block = SPVM_OP_new_op_block(compiler, $1->file, $1->line);
      SPVM_OP_insert_child(compiler, op_block, op_block->last, $2);
      $$ = op_block;
    }

eval_block
  : EVAL block
    {
      $$ = SPVM_OP_build_eval(compiler, $1, $2);
    }

expression
  : LAST
  | NEXT
  | RETURN {
      $$ = SPVM_OP_build_return(compiler, $1, NULL);
    }
  | RETURN normal_term
    {
      $$ = SPVM_OP_build_return(compiler, $1, $2);
    }
  | CROAK
    {
      $$ = SPVM_OP_build_croak(compiler, $1, NULL);
    }
  | CROAK normal_term
    {
      $$ = SPVM_OP_build_croak(compiler, $1, $2);
    }
  | field_access ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }
  | array_access ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }
  | weaken_field

opt_normal_terms
  :	/* Empty */
    {
      $$ = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
    }
  |	normal_terms
    {
      if ($1->id == SPVM_OP_C_ID_LIST) {
        $$ = $1;
      }
      else {
        SPVM_OP* op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
        $$ = op_list;
      }
    }
    
term
  : normal_term
  | relative_term
  | logical_term
  | isa
  | '(' term ')'
    {
      $$ = $2;
    }

normal_term
  : var
  | CONSTANT
    {
      $$ = SPVM_OP_build_constant(compiler, $1);
    }
  | UNDEF
  | call_sub
  | field_access
  | array_access
  | convert_type
  | new
  | array_init
  | array_length
  | my_var
  | binop
  | unop
  | ref
  | deref

normal_terms
  : normal_terms ',' normal_term
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      SPVM_OP_insert_child(compiler, op_list, op_list->last, $3);
      
      $$ = op_list;
    }
  | normal_terms ',' '(' normal_terms ')'
    {
      SPVM_OP* op_list;
      if ($1->id == SPVM_OP_C_ID_LIST) {
        op_list = $1;
      }
      else {
        op_list = SPVM_OP_new_op_list(compiler, $1->file, $1->line);
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $1);
      }
      
      if ($4->id == SPVM_OP_C_ID_LIST) {
        SPVM_OP* op_term = $4->first;
        while ((op_term = SPVM_OP_sibling(compiler, op_term))) {
          SPVM_OP* op_stab = SPVM_OP_cut_op(compiler, op_term);
          SPVM_OP_insert_child(compiler, op_list, op_list->last, op_term);
          op_term = op_stab;
        }
      }
      else {
        SPVM_OP_insert_child(compiler, op_list, op_list->last, $4);
      }
      
      $$ = op_list;
    }
  | normal_terms ','
    {
      $$ = $1;
    }
  | '(' normal_terms ')'
    {
      $$ = $2;
    }
  | normal_term
    {
      $$ = $1;
    }

unop
  : '+' normal_term %prec UMINUS
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_PLUS, $1->file, $1->line);
      $$ = SPVM_OP_build_unop(compiler, op, $2);
    }
  | '-' normal_term %prec UMINUS
    {
      SPVM_OP* op_negate = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_NEGATE, $1->file, $1->line);
      $$ = SPVM_OP_build_unop(compiler, op_negate, $2);
    }
  | INC normal_term
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_PRE_INC, $1->file, $1->line);
      $$ = SPVM_OP_build_unop(compiler, op, $2);
    }
  | normal_term INC
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_POST_INC, $2->file, $2->line);
      $$ = SPVM_OP_build_unop(compiler, op, $1);
    }
  | DEC normal_term
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_PRE_DEC, $1->file, $1->line);
      $$ = SPVM_OP_build_unop(compiler, op, $2);
    }
  | normal_term DEC
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_POST_DEC, $2->file, $2->line);
      $$ = SPVM_OP_build_unop(compiler, op, $1);
    }
  | '~' normal_term
    {
      $$ = SPVM_OP_build_unop(compiler, $1, $2);
    }

binop
  : normal_term '+' normal_term
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ADD, $2->file, $2->line);
      $$ = SPVM_OP_build_binop(compiler, op, $1, $3);
    }
  | normal_term '-' normal_term
    {
      SPVM_OP* op = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_SUBTRACT, $2->file, $2->line);
      $$ = SPVM_OP_build_binop(compiler, op, $1, $3);
    }
  | normal_term '.' normal_term
    {
      $$ = SPVM_OP_build_concat(compiler, $2, $1, $3);
    }
  | normal_term MULTIPLY normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term DIVIDE normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term REMAINDER normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term BIT_XOR normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term AMPERSAND normal_term %prec BIT_AND
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term BIT_OR normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | normal_term SHIFT normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }
  | my_var ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }
  | var ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }
  | var SPECIAL_ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }
  | '(' normal_term ')'
    {
      $$ = $2;
    }
  | deref ASSIGN normal_term
    {
      $$ = SPVM_OP_build_assign(compiler, $2, $1, $3);
    }

new
  : NEW basic_type
    {
      $$ = SPVM_OP_build_new(compiler, $1, $2, NULL);
    }
  | NEW array_type_with_length
    {
      $$ = SPVM_OP_build_new(compiler, $1, $2, NULL);
    }
  | NEW array_type '{' opt_normal_terms '}'
    {
      $$ = SPVM_OP_build_new(compiler, $1, $2, $4);
    }
  | anon_package
    {
      // New
      SPVM_OP* op_new = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_NEW, $1->file, $1->line);

      $$ = SPVM_OP_build_new(compiler, op_new, $1, NULL);
    }
  | anon_sub
    {
      // Package
      SPVM_OP* op_package = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_PACKAGE, $1->file, $1->line);
      
      // Create class block
      SPVM_OP* op_class_block = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_CLASS_BLOCK, $1->file, $1->line);
      SPVM_OP* op_list_declarations = SPVM_OP_new_op_list(compiler, compiler->cur_file, compiler->cur_line);
      SPVM_OP_insert_child(compiler, op_list_declarations, op_list_declarations->last, $1);
      SPVM_OP_insert_child(compiler, op_class_block, op_class_block->last, op_list_declarations);
      
      // Build package
      SPVM_OP_build_package(compiler, op_package, NULL, op_class_block, NULL);
      
      // New
      SPVM_OP* op_new = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_NEW, $1->file, $1->line);
      
      $$ = SPVM_OP_build_new(compiler, op_new, op_package, NULL);
    }

array_init
  : '[' opt_normal_terms ']'
    {
      SPVM_OP* op_array_init = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ARRAY_INIT, compiler->cur_file, compiler->cur_line);
      $$ = SPVM_OP_build_array_init(compiler, op_array_init, $2);
    }

convert_type
  : '(' type ')' normal_term
    {
      SPVM_OP* op_convert = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_CONVERT, $2->file, $2->line);
      $$ = SPVM_OP_build_convert(compiler, op_convert, $2, $4);
    }

array_access
  : normal_term ARROW '[' normal_term ']'
    {
      $$ = SPVM_OP_build_array_access(compiler, $1, $4);
    }
  | array_access '[' normal_term ']'
    {
      $$ = SPVM_OP_build_array_access(compiler, $1, $3);
    }
  | field_access '[' normal_term ']'
    {
      $$ = SPVM_OP_build_array_access(compiler, $1, $3);
    }

call_sub
  : sub_name '(' opt_normal_terms  ')'
    {
      $$ = SPVM_OP_build_call_sub(compiler, NULL, $1, $3);
    }
  | basic_type ARROW sub_name '(' opt_normal_terms  ')'
    {
      $$ = SPVM_OP_build_call_sub(compiler, $1, $3, $5);
    }
  | basic_type ARROW sub_name
    {
      SPVM_OP* op_normal_terms = SPVM_OP_new_op_list(compiler, $1->file, $2->line);
      $$ = SPVM_OP_build_call_sub(compiler, $1, $3, op_normal_terms);
    }
  | normal_term ARROW sub_name '(' opt_normal_terms ')'
    {
      $$ = SPVM_OP_build_call_sub(compiler, $1, $3, $5);
    }
  | normal_term ARROW sub_name
    {
      SPVM_OP* op_normal_terms = SPVM_OP_new_op_list(compiler, $1->file, $2->line);
      $$ = SPVM_OP_build_call_sub(compiler, $1, $3, op_normal_terms);
    }
  | normal_term ARROW '(' opt_normal_terms ')'
    {
      SPVM_OP* op_sub_name = SPVM_OP_new_op_name(compiler, "", $2->file, $2->line);
      $$ = SPVM_OP_build_call_sub(compiler, $1, op_sub_name, $4);
    }

field_access
  : normal_term ARROW '{' field_name '}'
    {
      $$ = SPVM_OP_build_field_access(compiler, $1, $4);
    }
  | field_access '{' field_name '}'
    {
      $$ = SPVM_OP_build_field_access(compiler, $1, $3);
    }
  | array_access '{' field_name '}'
    {
      $$ = SPVM_OP_build_field_access(compiler, $1, $3);
    }

weaken_field
  : WEAKEN field_access
    {
      $$ = SPVM_OP_build_weaken_field(compiler, $1, $2);
    }

array_length
  : '@' normal_term
    {
      SPVM_OP* op_array_length = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ARRAY_LENGTH, compiler->cur_file, compiler->cur_line);
      $$ = SPVM_OP_build_array_length(compiler, op_array_length, $2);
    }
  | '@' '{' normal_term '}'
    {
      SPVM_OP* op_array_length = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ARRAY_LENGTH, compiler->cur_file, compiler->cur_line);
      $$ = SPVM_OP_build_array_length(compiler, op_array_length, $3);
    }
  | SCALAR '@' normal_term
    {
      SPVM_OP* op_array_length = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ARRAY_LENGTH, compiler->cur_file, compiler->cur_line);
      $$ = SPVM_OP_build_array_length(compiler, op_array_length, $3);
    }
  | SCALAR '@' '{' normal_term '}'
    {
      SPVM_OP* op_array_length = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_ARRAY_LENGTH, compiler->cur_file, compiler->cur_line);
      $$ = SPVM_OP_build_array_length(compiler, op_array_length, $4);
    }

deref
  : DEREF var
    {
      $$ = SPVM_OP_build_deref(compiler, $1, $2);
    }
  | DEREF '{' var '}'
    {
      $$ = SPVM_OP_build_deref(compiler, $1, $3);
    }

ref
  : REF var
    {
      SPVM_OP* op_ref = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_REF, $1->file, $1->line);
      $$ = SPVM_OP_build_ref(compiler, op_ref, $2);
    }
  | REF '{' var '}'
    {
      SPVM_OP* op_ref = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_REF, $1->file, $1->line);
      $$ = SPVM_OP_build_ref(compiler, op_ref, $3);
    }

relative_term
  : normal_term REL normal_term
    {
      $$ = SPVM_OP_build_binop(compiler, $2, $1, $3);
    }

isa
  : term ISA type
    {
      $$ = SPVM_OP_build_isa(compiler, $2, $1, $3);
    }

logical_term
  : term OR term
    {
      $$ = SPVM_OP_build_or(compiler, $2, $1, $3);
    }
  | term AND term
    {
      $$ = SPVM_OP_build_and(compiler, $2, $1, $3);
    }
  | NOT term
    {
      $$ = SPVM_OP_build_not(compiler, $1, $2);
    }

my_var
  : MY var ':' type
    {
      $$ = SPVM_OP_build_my(compiler, $1, $2, $4);
    }
  | MY var
    {
      $$ = SPVM_OP_build_my(compiler, $1, $2, NULL);
    }

var
  : VAR_NAME
    {
      $$ = SPVM_OP_build_var(compiler, $1);
    }

type
  : basic_type
  | array_type
  | const_array_type
  | ref_type

basic_type
  : NAME
    {
      $$ = SPVM_OP_build_basic_type(compiler, $1);
    }
  | BYTE
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_byte_type(compiler);
      
      $$ = op_type;
    }
  | SHORT
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_short_type(compiler);
      
      $$ = op_type;
    }
  | INT
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_int_type(compiler);
      
      $$ = op_type;
    }
  | LONG
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_long_type(compiler);
      
      $$ = op_type;
    }
  | FLOAT
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_float_type(compiler);
      
      $$ = op_type;
    }
  | DOUBLE
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_double_type(compiler);
      
      $$ = op_type;
    }
  | OBJECT
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_object_type(compiler);
      
      $$ = op_type;
    }
  | STRING
    {
      SPVM_OP* op_type = SPVM_OP_new_op(compiler, SPVM_OP_C_ID_TYPE, $1->file, $1->line);
      op_type->uv.type = SPVM_TYPE_create_string_type(compiler);
      
      $$ = op_type;
    }

ref_type
  : AMPERSAND basic_type
    {
      $$ = SPVM_OP_build_ref_type(compiler, $2);
    }

array_type
  : basic_type '[' ']'
    {
      $$ = SPVM_OP_build_array_type(compiler, $1, NULL);
    }
  | array_type '[' ']'
    {
      $$ = SPVM_OP_build_array_type(compiler, $1, NULL);
    }

const_array_type
  : CONST array_type
    {
      $$ = SPVM_OP_build_const_array_type(compiler, $2);
    }

array_type_with_length
  : basic_type '[' normal_term ']'
    {
      $$ = SPVM_OP_build_array_type(compiler, $1, $3);
    }
  | array_type '[' normal_term ']'
    {
      $$ = SPVM_OP_build_array_type(compiler, $1, $3);
    }

type_or_void
  : type
  | VOID
    {
      $$ = SPVM_OP_new_op_void(compiler, compiler->cur_file, compiler->cur_line);
    }

field_name : NAME
sub_name : NAME

%%