Intro Blog

プログラミング言語を作っています

IntroJS の trace 文

IntroJS では、trace 文を使うと、式の値を標準エラー出力に書き出せます。

次のように記述すると、

var a = 1
var b = [1, 2]
trace a, b

次のように標準エラー出力に出力します。

example.intro:3: a, b => 1, [1,2]

式の値とともに、ファイル名、行番号、式自体が出力されるので確認しやすいのではないでしょうか。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の入出力

IntroJS の入出力は、標準入出力のみです。 ファイル入出力は、ありません。 IntroJS では、文字列を扱えないので、ファイル名が必要になるファイル入出力は、ばっさりと省きました。

入力データの書式は、空白区切りのテキストです。 行は、改行で区切ります。 行の先頭と末尾の空白は無視します。 値は、1 つ以上の半角スペース、または、水平タブで区切ります。 つまり、次の 2 行は読み込むデータとしては、同等になります。

1 2 3
  1  2  3  

標準入力からデータを読み込むには、次の組み込み関数を使います。

  • read(values: Int[], offset: Int, length: Int): Int
  • read_int(): Int
  • read_ints(max_length: Int): Int[]

これらの関数を 1 回呼び出すごとに、標準入力から 1 行を読み取ります。

read の戻り値は、値の個数です。 空行のときは、0 を返します。 入力の最後に達したときは、-1 を返します。

read_int の戻り値は、1 行の中の最初の値です。 空行、または、入力の最後のときは、0 を返します。 空行と入力の最後を区別したいときは、read を使う必要があります。

read_ints の戻り値は、1 行の値の配列です。 空行、または、入力の最後のときは、空の配列を返します。 空行と入力の最後を区別したいときは、read を使う必要があります。

read_ints の戻り値の配列の長さは、引き数で指定した長さ以下になります。 1 行の値の数が少ないときは、指定した長さよりも短い長さの配列を返します。 (引き数で指定した長さまで、0 で埋めるようなことはしません。)

出力データの書式も、空白区切りのテキストです。 通常は、行区切りは LF、値区切りは水平タブで出力します。

標準出力にデータを書き出すには、次の組み込み関数を使います。

  • write(values: Int[], offset: Int, length: Int)
  • write_int(value: Int)
  • write_ints(values: Int[])

これらの関数を 1 回呼び出すごとに、標準出力に 1 行を書き出します。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の長さ演算子

IntroJS には、配列の長さを取得するための長さ演算子 $ があります。

次のように記述すると、配列の長さ 3 を出力します。

var vv = [3, 1, 4]
write_int($vv)

他のプログラミング言語では、配列の長さを取得する演算子をあまり見かけませんが、 IntroJS では、言語仕様に配列が組み込まれているので、最低限の配列操作はプログラミング言語として用意しておこうと考え、 長さ演算子を作ってみました。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の範囲式

IntroJS では、for 文の式の部分に、範囲式を指定できます。 (逆に、for 文以外では、範囲式を使うことはできません。)

範囲式の書式は次のとおりです。

初期値 範囲演算子 境界値 [スキップ演算子 増分]

範囲式には、範囲演算子が必須です。 次の範囲演算子があります。

範囲演算子 初期値 境界値 増分
:< 含む 含まない +1
:<= 含む 含む +1
:> 含む 含まない -1
:>= 含む 含む -1

次の例では 0, 1, 2 を 1 行ずつ出力します。

for i in 0 :< 3
  write_int(i)
end

次の例では 0, 1, 2, 3 を 1 行ずつ出力します。

for i in 0 :<= 3
  write_int(i)
end

次の例では 3, 2, 1 を 1 行ずつ出力します。

for i in 3 :> 0
  write_int(i)
end

次の例では 3, 2, 1, 0 を 1 行ずつ出力します。

for i in 3 :>= 0
  write_int(i)
end

スキップ演算子は、:% のみがあり、続けて増分を指定します。

スキップ演算子と増分の部分は、省略可能です。 省略すると、前記のように、範囲演算子によって増分のデフォルト値が採用されます。

次のように記述すると、0, 2, 4 を 1 行ずつ出力します。

for i in 0 :< 5 :% 2
  write_int(i)
end

他のプログラミング言語では、範囲演算子..... を使うことが多いですが、どっちが含むで、どっちが含まないか、分かりにくいと感じていたので、 IntroJS では、(ドットを縦に並べたように見える)コロンと、比較演算子の組み合わせで表現してみました。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の for 文

IntroJS の for 文は、次のように記述します。

for 識別子 in 式
  ...
end

識別子には、ループ内で使用する変数の名前を指定します。

式に、配列 (または、配列を返す式) を指定すると、ループ変数には、配列の各要素が設定されます。 次のように記述すると、3, 1, 4 が 1 行ずつ出力されます。

for v in [3, 1, 4]
  write_int(v)
end

式に、整数 (または、整数を返す式) を指定すると、ループ変数には、0 から、指定した整数まで (指定した整数は含まない) が設定されます。 次のように記述すると、0, 1, 2 が 1 行ずつ出力されます。

for i in 3
  write_int(i)
end

式に、範囲式を指定すると、ループ変数には、範囲内の整数が順に設定されます。 次のように記述すると、1, 2, 3 が 1 行ずつ出力されます。

for i in 1 :<= 3
  write_int(i)
end

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の変数宣言

IntroJS の変数宣言は var で始めます。

var a = 0

変数名は、参照範囲内にある識別子 (関数名、仮引数名、変数名)と同じ名前にはできません。

変数の初期化は、必ず必要です。 変数初期化の結果のデータ型によって、変数のデータ型が決まります。

変数に値を再代入するときは、同じデータ型を指定しなければなりません。

var b = 1
b = 2
# b = [3] はできない。

IntroJS の型システムは、かっこよく言えば、型推論ありの静的型付けシステムということになります。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS

IntroJS の関数定義

IntroJS のトップレベルにかけるのは、今のところ関数定義のみです。

モジュールのインポートや、グローパル変数などは、まだサポートしていません。

IntroJS の関数定義では、仮引数や戻り値にデータ型の指定が必ず必要になります。

def func(v: Int, vv: Int[]): Int[][]
  ...
end

関数のオーバーロードには対応していないので、関数名はファイル内で一意にする必要があります。 仮引数の個数やデータ型が異なっても、同じ名前の関数は定義できません。

また、可変長引数、名前付き引数、デフォルト値などにも対応していません。

ということで、整数型しか使えない簡易プログラミング言語 IntroJS は、いかがでしょうか。

IntroJS