Skip to content

字句と予約語

名前の種類は大文字と小文字で決まり、文の終わりは改行で決まります。どちらも字句解析の段階で確定し、名前解決の結果には左右されません。

このページは規範としての規則をまとめたものです。書き方の流れは 基本 を、EBNF は 文法 を見てください。

  • 拡張子は .emel、符号化は UTF-8 です。
  • セミコロンは使いません。文は改行で終わります(例外は下の「行の継続規則」)。
  • 空白はスペースとタブです。改行は \n か \r\n で、どちらも1つの改行として扱います。
  • 単独の \r と、Unicode の空白(全角空白など)は認識できない文字になります(E0105)。
## 利用者を表す。
type User(
## 一意の番号
id: Int,
name: String,
)
# ふつうのコメント。行末まで
fn main() {
user = User(id: 1, name: "alice") # 行の途中からでも書ける
dbg(user.name)
}
  • # から行末までがコメントです。
  • ## は文書コメントで、直後の宣言、バリアント、フィールド、エフェクトの操作、handler の項目に付きます。
  • 宣言と derive の間に書いた ## は、宣言に付きません。
  • ブロックコメントはありません。

名前は形によって3つのクラスに分かれます。

クラス形使うもの例
小文字名小文字で始まる snake_case変数、関数、フィールド、引数名、エフェクトの操作user_id find
型名大文字で始まり、小文字を1つ以上含む PascalCase型、構成子、エラー、エフェクト、ハンドラ、layer、Trait、モジュール、型引数User Http Db Item
大文字名大文字、数字、アンダースコアだけconst、型引数MAX_SIZE A
const MAX_SIZE = 100
type Item(name: String, size: Int)
fn fits(item: Item) -> Bool {
item.size <= MAX_SIZE
}
fn main() {
dbg(fits(Item(name: "box", size: 30)))
}
  • クラスに合わない名前は構文エラーになります。HTTP や DB は型名として使えないので、Http、Db と書きます。
type HTTP(status: Int)
  • 型名と大文字名は、既存の定義を指す名前です。式やパターンの中で新しく束縛されることはありません。そのため、パターン中の打ち間違いは必ず「未定義の名前」としてエラーになります(E0211)。
  • _ は値を束縛しないパターンです。パターンの外には書けません(E0123)。
  • 型引数は大文字名(A、T)と型名(Item、Key)のどちらでも書けます。型引数を宣言できるのは [...] の中だけで、参照できるのは型の位置だけです。宣言していない名前を型に書くと、未定義の名前のエラーになります(fn id(x: A) -> A は E0211)。型名で書いた型引数が型か型引数かは、名前解決で決まります。
  • 型引数は同じ名前の外側の型、エフェクト、エラー、Trait を隠します。型名の型引数が隠したときは警告します(W0201)。後から型を足したとき(Prelude への追加を含む)に、既存のコードが壊れないようにするためです。
  • 名前に使える文字は ASCII だけです。
  • 小文字名が途中に大文字を含むこと(userId)と、型名で大文字が続くこと(HTTPServer)は、字句としては許します。snake_case と PascalCase の慣習は linter が検査します。

この規則から、. の左側を見れば意味が分かります。

  • 左が型名なら、静的な参照(モジュールの関数、Trait のメソッド)です。例: List.map
  • 左が小文字名か大文字名なら、値へのアクセス(フィールド、use で得た能力の操作)です。例: user.name、log.info、DEFAULT_CONFIG.port
種類形備考
整数42 1_000_000 0xFF 0b1010文脈が Int64 を要求すれば Int64、それ以外は Int。Float にはならない。0x は16進、0b は2進
浮動小数点3.14 1.0e9Float
文字列"hello" """..."""#{式} で補間する。式の型は Show を満たすこと。""" で囲むと複数行を書ける
リスト[1, 2, 3] [x, ..rest]List[Int]。末尾の ..式 は、その前に要素を付けたリストを作る
タプル(a, b)要素は2つ以上
Unit()要素0個のタプル

真偽値はリテラルでなく Bool の構成子 True と False です。

fn main() {
count = 1_000_000
mask = 0xFF
bits = 0b1010
big: Int64 = 3_000_000_000
ratio = 1.0e9
rest = [2, 3]
xs = [1, ..rest]
pair = (count, "items")
dbg("#{count} #{mask} #{bits} #{big} #{ratio} #{xs} #{pair}")
}
  • 浮動小数点の指数は小数点のある形にだけ付けられます。1e9 は書けず、1.0e9 と書きます。
  • 数値の直後に英字が続くもの(1e9、3px)は字句のエラーになります(E0104)。
  • 16進と2進の接頭辞は小文字だけ(0x、0b)です。0XFF は E0104 になります。16進の数字は大文字と小文字のどちらでも構いません。
  • 8進は持ちません。
  • 値が文脈の型(Int か Int64)の範囲を超えればエラーになります(E0311)。2の補数として読み替えないので、0xFFFF_FFFF は Int では書けず、Int64 なら書けます。
  • 負の数はリテラルではなく、単項マイナスを付けた式です。リテラル自体は正の数として範囲を見るので、Int の最小値 -2147483648 はリテラルでは書けないので、式では -2147483647 - 1 と書きます。パターンの中だけは -2147483648 を1つの値として書けます。
  • パターンでは、数のリテラルの前に - を書けます(パターンマッチ)。
fn main() {
x = 0xFFFF_FFFF # error[E0311]: Int の範囲を超える
}
fn main() {
x: Int64 = 0xFFFF_FFFF # Int64 なら書ける
dbg(x)
}
  • 文字列は改行をまたげません(E0101)。複数行は """ の文字列で書きます。
  • #{ } の中は普通のコードとして字句解析します。コメントと改行を書けて、改行は継続になります。
  • 文字の型(Char)は持ちません。1文字は長さ1の String で表します(ターゲット)。
  • エスケープを解釈しない生の文字列は持ちません。
  • 字句は最長一致で切ります。"""" は """ と " で、空の文字列 "" を2つ並べたものとは読みません。

エスケープは次の7つです。それ以外の \ はエラーになります(E0107)。行末の \ は E0106 です。

エスケープ意味
\""
\\\
\n改行(U+000A)
\tタブ(U+0009)
\r復帰(U+000D)
\##。#{ を補間でなく文字として書くときに使う
\u{...}1〜6桁の16進で書いた Unicode のコードポイント。サロゲート(D800〜DFFF)と 10FFFF を超える値はエラー(E0108)
fn main() {
s = "\u{D800}" # error[E0108]: サロゲートは書けない
}
fn main() {
id = 1
query = """
SELECT *
FROM users
WHERE id = #{id}
"""
dbg(query)
}

規則上、query は "SELECT *\nFROM users\nWHERE id = 1" と同じ値です。

  • 開く """ の直後は改行でなければなりません。
  • 閉じる """ はそれだけで1行に置きます。
  • 閉じる """ の行のインデントを、各行の先頭から取り除きます。
  • 開く """ の直後の改行と、閉じる """ の直前の改行は含めません。
  • 閉じる """ より浅くインデントした行(空行を除く)はエラーになります(E0109)。
  • インデントは文字の並びとして比べます。空行を除く各行は閉じる """ の行のインデントと同じ並びで始まらなければなりません。タブと空白が違う行も浅いとみなします。
  • 補間とエスケープは、普通の文字列と同じく使えます。

改行は文を終わらせます。ただし次の4つの場合は、前の行が続きます。

  1. 次の行が二項演算子、.、escape、match のいずれかで始まる。match は、次の意味のあるトークンが { のときだけ続けます。後置の match は必ず match { なので、正しいプログラムの読み方は変わりません。そうでない行は前置の match として新しい文に読み、E0142 を出します。コンパイラは前置のまま読んで回復し、型の検査は続けます。
  2. 開いた ( または [ がまだ閉じていない。
  3. 次の行が else、->、| のいずれかで始まる。どれも文を始めることがないので、継続にしても曖昧さは生まれません。} else { には formatter がそろえます。
  4. シグネチャの中で、次の行が fails か use で始まる。シグネチャの中とは、fn の宣言の本体の { より前と、effect の操作の宣言です。本体の中では、fails はふつうの識別子に使え、use は式を始めるので、新しい文になります。本体の { は、シグネチャの最後の行に書きます。
error NotFound(id: Int)
error Invalid
effect Users {
fn find(id: Int) -> Option[String]
}
fn lookup(id: Int) -> String
fails NotFound | Invalid
use Users {
if id < 0 { fail Invalid }
name = (use Users).find(id)
|> Option.or_fail(NotFound(id:)) # 規則1: 前の行の続き
total = [
name,
"!",
] # 規則2: [ の中
String.join(total, "")
}

次のトークンで始まる行は、常に新しい文になります。

  • ( と [: 関数呼び出しや型引数の開き括弧は、名前と同じ行に書きます。
  • -: 単項マイナスとして読みます。引き算を次の行に続けるときは、行末に - を置きます。
fn main() {
a = 10
+ 1 # 前の行の続き。a は 11
b = (10 -
1) # ( の中なので続く。b は 9
c = 10
-1 # c とは別の文(単項マイナスの式)
dbg((a, b, c))
}

補足:

  • 継続の判定は改行の次の「意味のあるトークン」を見ます。間の空白、コメントだけの行、空行は飛ばします。
  • 規則2の「開いた括弧」は、括弧の入れ子の最も内側で判定します。( の中でも、その内側に開いた { があれば、改行は文を区切ります(f(fn(x) {⏎a⏎b⏎}))。
  • 文字列の補間 #{ } の中の改行は、( の中と同じく継続になります。
  • type と enum の宣言の直後の行が derive で始まるときも、宣言の続きになります。これは字句の継続規則ではなく、宣言の位置でだけ扱う規則です(文法)。
fn main() {
x = 1
match x { # error[E0142]: match は後置で書く
_ -> 0
}
}

予約語は25個で、どの位置でも識別子に使えません。

分類語
宣言fn type enum error const trait impl effect handler layer import
修飾pub opaque suspend
式if else match fail escape use with assert
特別な名前self Self
初版では未使用as

as は import の別名のための予約語です。初版の範囲では構文を持ちません。

次の語は示した位置でだけキーワードとして働きます。それ以外の位置では普通の識別子です。

語位置
implementshandler の宣言
forimpl A for B
fails関数のシグネチャと関数型
derivetype と enum の宣言の末尾、trait の本体
init releasehandler の本体
of defaultラベルの宣言(ラベル)
bywith の項目(ラベル)
fn main() {
fails = 1
init = 2
by = 3
default = 4
dbg(fails + init + by + default)
}

次の語は構文を持ちませんが、識別子にも使えません。

var return while loop let mut in where do try catch throw async yield defer macro mod

fn main() {
var = 1
}
  • True、False、Some、None は Prelude の enum の構成子です。
  • panic、todo、dbg は Prelude の関数です。
  • resume はハンドラの引数に付ける慣習的な名前です。
  • Immediate、Async、Eq などは組み込みの Trait やエフェクトの名前です。

注釈は @名前(引数) の形で、宣言の直前に置きます。使えるのはコンパイラ組み込みの注釈だけで、新しい注釈は定義できません。

注釈対象意味
@external(...)本体のない fnホスト側の実装を指す。フィールドのない type にも付けられる(外部関数)
@test引数のない fn(引数があれば E0226)テスト関数である。テスト関数はテスト関数の中からしか参照できない(値としての参照も同じ。E0227)。テスト関数どうしは参照できる(テスト)
@deprecated(...)任意の宣言使用箇所で警告する
@intrinsic本体のない fn(同梱の core のソースのトップレベルだけ)コンパイラが実装を持つ組み込み関数である。シグネチャは Emela の文法で書き、実装は名前でコンパイラの組み込み関数の表から引く。core のソースの外では使えない(E0224)。付いた fn は @external の制約を満たすものとして扱う。同梱の core は組み込み関数の表と一対一で対応するので、コンパイラと同じ版でしか動かない
@internalトップレベルの fn(同梱の core のソースだけ)core のモジュールの外から参照できない(E0228)。core の外や、トップレベルの fn 以外には付けられない(E0229)
フィールド注釈type と enum のフィールド導出の細部を指定する。名前と引数は未定
fn add(a: Int, b: Int) -> Int { a + b }
@test
fn adds() {
assert add(1, 2) == 3
}