文法定義

mimium言語の文法定義に関する項目です。

mimium言語の文法規則に関する項目です。

コメントアウト

C++やJavaScriptと同様、行中の//より右側はコメントとして扱われます。 また/* */のように囲むと、複数行をまとめてコメントアウトできます。

変数宣言、代入

mimiumでは=演算子で値を代入すると、左辺に指定した名前の変数がまだ存在していなければ新しく変数が作られます。 letのような変数宣言時のキーワードは必要ありません。

mynumber = 1000

変数はすべて変更可能(mutable)です。すでに宣言されている変数に新たに値を代入もできます。

mynumber = 1000 // variable is declared, and 1000 is assigned
mynumber = 2000 // 2000 is newly assigned to mynumber

とは変数などのデータを数値や文字列など目的に応じて区別するための概念です。 mimiumは静的型付け言語と呼ばれる、コンパイル時に(音を実際に鳴らす前)すべての型が決定される言語です。

静的型付け言語は一般的に、実行中に型をチェックする言語よりも実行速度の面で有利です。その一方、型の指定を手動で行う場合は記述が長くなりがちというデメリットも存在しますが、mimiumでは型推論と呼ばれる、文脈から型が自動的に決定できる場合は型注釈を省略できる機能が存在しているので、コードを簡潔に保つことが可能です。

型にはそれ以上分解できない最小単位であるプリミティブ型と、複数の型を組み合わせて作る合成型(aggregate type)が存在します。

型の明示的な注釈は変数の宣言と関数の宣言時に可能です。 変数および関数のパラメータでは名前に続けて:(コロン)を挟み型名を書くことで指定可能です。

myvar:float = 100

以下のように異なる型へ代入した場合はコンパイル時にエラーが発生します。

myvar:string = 100

関数での型宣言では返り値をパラメータの括弧に続けて->を挟んで書くことで指定できます。

fn add(x:float,y:float)->float{
  return x + y
}

このadd関数の場合、文脈からxとyがfloatであることを予測できる1ので以下のように省略できます。

fn add(x,y){
  return x+y
}

プリミティブ型

mimiumにおけるプリミティブ型はfloatstringvoidのみです。

mimiumでは数値型はfloat(内部的には64bit float)のみとなっています。 整数を利用するにはroundceilfloor関数などを利用します。

string型の値は"hoge"のようにダブルクオーテーションで囲った文字列リテラルから生成できます。 現在は文字列の切り出しや結合には対応しておらず、用途は基本的には

  1. printstr関数に渡してデバッグ用途に使う
  2. loadwav関数に渡してオーディオファイルを読み込む
  3. includeに渡して他のソースファイルを読み込む

のいずれかに限られています。

voidは値を持たない型で、関数の返り値が存在しないことを明示するのに使用します。

合成型

配列

配列は、同じ型の値を複数個連続して格納できる型です。[](アングルブラケット)で囲んだカンマ区切りの値で生成できます。

myarr = [1,2,3,4,5,6,7,8,9,10]

配列型の値にmyarr[0]のようにアングルブラケットで0基準のインデックスを指定することで配列の値を取り出すことができます。

arr_content = myarr[0] //arr_content should be 1

また左辺値に同様にアングルブラケットを使うことで配列の中身を書き換えることができます。

myarr[4] = 20 //myarr becomes [1,2,3,4,20,6,7,8,9,10]

配列のサイズは固定です。配列の後ろに値を追加していくような操作はできません。また境界チェックもないため範囲外へのアクセスはクラッシュを引き起こします。

自動内挿

インデックスは小数点以下の値でアクセスされた場合、自動で線形補完されて出力されます。

arr_content = myarr[1.5] //should be 2.5

自動で整数に丸められることはないので内挿を避けたい場合はround関数などでインデックスを丸める必要があります。

タプル

タプルは、異なる型を1つにまとめた値です。変数を()(丸括弧)で囲んでカンマ区切りの変数を入れることで生成できます。 タプルは配列とも似ていますが、各要素で異なる型を持つことができます。

mytup = (100,200,300)

左辺値にカンマ区切りの変数を置くことでタプルの値を取り出すことができます。この時には括弧で区切る必要がありません。

one,two,three = mytup

タプルはmimiumの中では典型的に信号処理でステレオやマルチチャンネルなどのオーディオ信号のチャンネルをまとめて扱うために利用されています。

型エイリアス

タプルは型注釈が長いので、以下のような構文でエイリアスを作ることができます。

type FilterCoeffs = (float,float,float,float,float)

構造体(レコード型)

構造体は、タプルと同じ機能を持ちますがフィールド名を持たせることが可能です。 構造体型の変数には変数.フィールド名のようにドット演算子でアクセスすることができます。

構造体は無名型が作れません。 先に型エイリアスを宣言してから型名{変数1,変数2...}という構文で初期化して使用する形になります。

type MyBigStruct = {"field1":float,"field2":FilterCoeffs,"field3":string}

mystr = MyBigStruct{100,(0.0,1.0,1.2,0.8,0.4),"test"}

println(mystr.field1)
printlnstr(mystr.field3)

関数

関数は、複数の値を取って新しい値を返すような、再利用可能な手続きをまとめたものです。

例として2つの値を足算して返すだけのadd関数を考えます。

mimiumで型を明示して変数として格納する場合は以下のように書きます。

fn add(x,y){
  return x+y
}

mimiumでは関数が第一級の値として扱えます。これは、関数を変数に代入したり、関数のパラメータとして取ったりすることができるということです。

たとえば、先ほどのadd関数の型注釈は(float,float)->floatのようになっています。先ほどのadd関数を変数に代入する場合は以下のように書けます。関数を関数のパラメータとして代入する場合は高階関数の項を参照してください。

my_function:(float,float)->float = add

無名関数(ラムダ式)

実は先ほどの関数宣言は以下のような、無名関数を変数に格納する構文へのエイリアスです。

add = |x:float,y:float|->float{return x+y} 

このような関数を変数に代入しないまま直接呼び出すことも可能です。

println(|x,y|{return x + y}(1,2)) //print "3"

またブロックの構文のところで解説しますが、ブロックの最後の1行はreturnの代わりに単に式を置くことで、returnの代わりにできます。つまり、add関数は型推論も組み合わせると以下の例まで省略できます。

add = |x,y|{x+y}

パイプ(|>)演算子

mimiumではパイプ演算子|>利用することでa(b(c(d)))のようにネストした関数呼び出しをd |> c |> b |> aのように書き換えることができます。

再帰によるループ

名前のついている関数は自分自身を呼び出すことも可能です。

階乗を計算するfact関数は以下のように定義できます。

fn fact(input:float){
  if(input>0){
    return 1
  }else{
    return input * fact(input-1)
  }
}

再帰関数は無限ループを発生させる可能性があるので注意して使用してください。

self

関数の中では、selfという特別なキーワードが使用できます。 selfは関数が最後に返した値を参照できる変数です。たとえば、以下のような関数を作ると呼び出される度incrementずつ増える値を返します。

fn counter(increment){
  return self+increment
}

selfは基本的にdsp関数を起点にして呼び出される関数でのみ使用できます。selfはオーディオエンジンが開始された時に0で初期化され、呼び出しコンテキストごとに別々の値が生成、管理されます。 たとえば以下の例ではcounter関数にそれぞれ異なるincrementを与えていますが、この場合内部的にselfのためのメモリは2つ分確保され、lchは毎サンプル0.01ずつ増えて1を越えるたび0にリセットされ、rchは毎サンプル0.05ずつ増えます。

fn dsp()->(float,float){
  lch = counter(0.01)%1
  rch = counter(0.05)%1
  return (lch,rch)
}

変数のスコープ

mimiumはレキシカルスコープと呼ばれる言語で、関数の外側で定義されている変数を参照することが可能です。

TBD

式(expression)、文(statement)、ブロック

関数などで使われていた中括弧{}で囲まれた文の集まりはブロックと呼ばれる単位です。 文(statement)はほとんどの場合a = bのようなの代入をする構文で構成されています。 **式(expression)**は1000のような数字、mynumberのような変数シンボル、1+2*3のような演算式、add(x,y)のような返り値を持つ関数呼び出しなどで構成される単位です。

ブロックは実はの1つです。 ブロックには複数の文を置くことができ、最後の1行はreturnを使って返却する値を指定できます。また最後の行のreturnは省略も可能です。

たとえば以下のような構文も文法上は正しいです。(v0.3.0現在この構文は実装が間違っていて動きません。

//mynumber should be 6
mynumber = {
  x = 2
  y = 4
  return x+y
}

条件分岐

mimiumの条件分岐はif (condition) then_expression else else_expressionという構文を持っています。 conditionthen_expressionelse_expressionはすべて式です。 conditionの値が0より大きい時then_expression部分が、そうでなければelse_expressionが評価されます。

then/elseの部分をブロックとして表現すれば、以下のようにC言語風の書き方ができます。

fn fact(input:float){
  if(input>0){
    return 1
  }else{
    return input * fact(input-1)
  }
}

一方でif文自体も式として扱えるので、同じ構文を以下のように書き換えることもできます。 条件部分の括弧は省略することができません。

fn fact(input:float){
  return if (input>0) 1 else input * fact(input-1)
}

@演算子による遅延実行

関数呼び出しに続けて@と数値型の値を続けることで、関数の実行を遅らせることができます。 時間の単位はサンプルです。

たとえば以下の例ではオーディオドライバをスタートしてから0サンプル目と48000サンプル目に、100と200を続けて標準出力に書き込みます。

println(100)@0
println(200)@48000

現在@演算子はvoid型の(返り値を持たない)関数にのみ使用することが可能です。

再帰関数の実行を@で遅延させることにより、一定間隔で特定の処理を繰り返すことも可能です。 たとえば以下の例では48000サンプル間隔で0から1ずつ数値を増やして標準出力に書き込みます。

fn loopprint(input)->void{
  println(input)
  loopprint(input+1)@(now+48000)
}
loopprint(0)@0

include

include("path/to/file.mmm")という構文を用いると他のファイルをそのファイル内で読み込むことができます。

ファイルパスは絶対パスもしくはそのファイルからの相対パスで指定します。 現在は名前空間の分割などはなく、純粋にinclude文をそのファイルのテキストに置換するだけになっています(ただし、一度読み込まれたファイルが2回以上読み込まれることはありません)。

BNFによる文法定義、演算子の優先順位など

TBD


  1. mimiumでは+*などの算術演算子を数値型にしか使えないため。今後変更になる可能性もあります。 ↩︎