AiScriptを使ってみた

# はじめに
[AiScript](https://github.com/syuilo/aiscript)（読み：あいすくりぷと）はMisskeyの開発者でもあるsyuiloさんが作成したプログラミング言語の一種です。
> **AiScript** is a scripting language runing on JavaScript. Not altJS.
>
> - ホストから変数や関数を提供することが出来ます
> - 外の情報にはアクセス出来ないので安全にスクリプトを実行できます(サンドボックス環境)

ベースはTypeScript(JavaScript)で、Node.jsで動作します。

※この記事は2023年2月13日現在のものです。執筆時点では開発段階であるため、仕様変更がある場合がありますので予めご了承ください。

**2023年2月13日 追記: @saki-lere さんより編集リクエストをいただき、その情報を基に追記いたしました。お送りいただきありがとうございます。**

## 動作環境の構築

動作環境を構築するにはNode.js(LTS版での動作を推奨)が必要になります。
インストールしていない場合はインストールしておいてください。

1. リポジトリをクローンします。: `git clone https://github.com/syuilo/aiscript.git`
2. リポジトリのディレクトリに移動し、依存パッケージをインストールします。: `cd aiscript; npm install`
3. AiScript環境をコンパイルをします。: `npm run build`

# Hello Worldを表示する

`node console`でAiScriptのインタプリタを起動することができます。

起動したインタプリタで、下の関数を入力すると、`Hello World!`と表示されます。

```
print("Hello World!")
```

<script id="asciicast-320926" src="https://asciinema.org/a/320926.js" async></script>

ね、簡単でしょ。

インタプリタでは、返り値が表示されます。printは特に返すものはないため、nullとなります。

なお、printの関数は以下のようにしても同様に動作します。

```
<: "Hello World!"
```

インタプリタを抜けるにはCtrl-Cを押します。

# 注釈（コメント）

コメントはスラッシュ2文字で、改行するまで注釈扱いになります。

```
//
// これはコメントです。動作には影響しません
```

# 変数

変数の定義は以下のようにして行います。

```
let constant = "This is a constant"
// let [定数名] = 代入する内容(項目：オブジェクト を参照)
```

`let`はイミュータブル（変更不能）な変数として扱われます。ですので、一度定義したあとは内容を変更することはできません。
ミュータブル（変更可能）な変数として取り扱うには、以下のように定義すると利用できます。

```
var variable = "This is a variable"
// var [変数名] = 代入する内容(項目：オブジェクト を参照)
```

ミュータブル変数の内容を変更する場合は以下のようにして行います。

```
variable = "It's changed variable"
// [変数名] = 代入する内容(項目：オブジェクト を参照)
```

インタプリタ内であれば、定数や変数の中身を確認することができます。

```:interpreter
> let hoge = "hoge"
> hoge
< "hoge"
```

# オブジェクト

AiScirptには以下のオブジェクトがあります。

|種類|型|リテラル例|
|:--|:--:|:-------------|
|文字列|`str`|`"kawaii"`|
|数値|`num`|`42`|
|真理値|`bool`|`true`/`false`|
|配列|`arr`|`["ai" "chan" "cute"]`|
|オブジェクト|`obj`|`{ foo: "bar"; a: 42; }`|
|null|`null`|`null`|
|関数|`fn`|`@(x) { x }`|

## 配列について

配列のインデックスは0から始まります。つまり、こうなります。

```
let array = ["one" "two" "three"]
// 配列は以下のように書くこともできます。
// let array = ["one", "two", "three"]

print(array[0]) // one
print(array[1]) // two
print(array[2]) // three
```

# 演算

演算は以下のようにして書くことができます。

```
1 + 1
```

以上のスクリプトは糖衣構文であり、AiScriptでは以下のように解釈されます。

```
Core:add(1, 1)
```

以下のような演算があります。

|関数|演算子|意味|
|:--------:|:-:|:---|
|`Core:add`|`+`|加算|
|`Core:sub`|`-`|減算|
|`Core:mul`|`*`|乗算|
|`Core:div`|`/`|除算|
|`Core:mod`|`%`|剰余|
|`Core:eq`|`==`|等しい|
|`Core:eq`|`!=`|等しくない|
|`Core:and`|`&&`|かつ|
|`Core:or`|`\|\|`|または|
|`Core:gt`|`>`|大きい|
|`Core:gteq`|`>=`|以上|
|`Core:lt`|`<`|小さい|
|`Core:lteq`|`<=`|以下|

# ブロック

ブロックは処理のまとまりを書ける役割があります。ブロックの最後に書かれた式が値として返されます。

```
let foo = eval {
  let a = 1
  let b = 2
  a + b
}

print(foo) // 3
```

なお、ブロック内の変数や定数はブロック外では使用できません。

```
let foo = eval {
  let a = 1
  let b = 2
  a + b
}

print(a) // [ERROR]
```

# 条件分岐

条件分岐は以下のように書きます。

```
// if ([演算式]) { [条件が合う場合の処理] }
if (a == b) {
  <: "a is equal to b"
}
```

また、条件が一致しない場合の処理は以下のように書きます。

```
// if ([演算式]) { [条件が合う場合の処理] } else { [条件が合わない場合の処理] }
if (a == b) {
  <: "a is equal to b"
} else {
  <: "a is not equal to b"
}
```

条件が一致しなかった場合、さらに条件を出す処理もできます。

```
// if ([演算式1]) { [演算式1が合う場合の処理] } elif ([演算式n]) { [演算式nが合う場合の処理] }
if (a == b) {
  <: "a is equal to b"
} elif (a > b) {
  <: "a is grater than b"
} else {
  <: "a is less than b"
}
```

条件分岐は式として扱えるため、ブロック内で値を返すこともできます。

```
<: if (a == b) {
  "a is equal to b"
} elif (a > b) {
  "a is grater than b"
} else {
  "a is less than b"
}
```

# 繰り返し

繰り返し処理は以下のように書きます。

```
// for let [イテレータ名], [繰り返し回数] { [処理] }
for let i, 100 {
  <: i
}
// 0~99までprintされます。
```

<script id="asciicast-320960" src="https://asciinema.org/a/320960.js" async></script>

なお、イテレーター名は省略可能です。

```
// for [繰り返し回数] { [処理] }
for 100 {
  <: "hoge"
}
```

配列を使った繰り返し処理も行えます。

```
let hoge = ["a", "b", "c"]
// each let [イテレーター名], [配列変数] { [処理] }
each let h, hoge {
  <: h
}
```

なお、`each`のあとに`()`でイテレータ名と配列変数を囲っても動作します。

```
let hoge = ["a", "b", "c"]
each (let h, hoge) {
  <: h
}
```

# 関数

関数を定義することもできます。以下のように書きます。

```
// @[関数名]([引数]) { [処理] }
@fun(x) {
  x + 3
}

// 関数を実行
fun(2) // 5
```

最後の式が返り値として扱われます。
途中で返り値を出す場合は`return`を使用して値を返します。

```
@hoge(a) {
    if (a == 2) {
        return a * 2
    }
    a + 1
}

hoge(2) // 4
```

# テンプレート

文字列の中に変数や式を埋め込むことができます。以下のように書きます。

```
let hoge = "hoge"
<: `{hoge} is usually word used in programming`
```

# メタデータ

AiScriptファイルにメタデータを埋め込むには以下のように記述します。

主にMisskeyのプラグインの名前などを定義するときに利用します。

```
### {
  name: "example"
  version: 1
  keywords: ["a" "b" "c"]
}
```

# スクリプトを実行

スクリプトを実行するには、`test.is`を、AiScriptと同じディレクトリに設置してスクリプトを作成します。

作成したら、`npm start`でスクリプトを実行することができます。

---

AiScriptはまだまだ開発段階であるため、
もしかしたら仕様が変わるかもしれません。
気が向いたらこの記事も変更するかもしれません。

現在AiScriptではMisskeyで導入されており、プラグインなどで使用されています。
詳しくはMisskey公式のドキュメントやMisskeyプラグインのコミュニティ等をご参照ください。

https://misskey-hub.net/docs/advanced/create-plugin.html