字句と予約語
名前の種類は大文字と小文字で決まり、文の終わりは改行で決まります。どちらも字句解析の段階で確定し、名前解決の結果には左右されません。
このページは規範としての規則をまとめたものです。書き方の流れは 基本 を、EBNF は 文法 を見てください。
ソースファイル
Section titled “ソースファイル”- 拡張子は
.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の間に書いた##は、宣言に付きません。 - ブロックコメントはありません。
名前の字句クラス
Section titled “名前の字句クラス”名前は形によって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.0e9 | Float |
| 文字列 | "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)}文字列の細則
Section titled “文字列の細則”- 文字列は改行をまたげません(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]: サロゲートは書けない}複数行の文字列
Section titled “複数行の文字列”fn main() { id = 1 query = """ SELECT * FROM users WHERE id = #{id} """ dbg(query)}規則上、query は "SELECT *\nFROM users\nWHERE id = 1" と同じ値です。
- 開く
"""の直後は改行でなければなりません。 - 閉じる
"""はそれだけで1行に置きます。 - 閉じる
"""の行のインデントを、各行の先頭から取り除きます。 - 開く
"""の直後の改行と、閉じる"""の直前の改行は含めません。 - 閉じる
"""より浅くインデントした行(空行を除く)はエラーになります(E0109)。 - インデントは文字の並びとして比べます。空行を除く各行は閉じる
"""の行のインデントと同じ並びで始まらなければなりません。タブと空白が違う行も浅いとみなします。 - 補間とエスケープは、普通の文字列と同じく使えます。
行の継続規則
Section titled “行の継続規則”改行は文を終わらせます。ただし次の4つの場合は、前の行が続きます。
- 次の行が二項演算子、
.、escape、matchのいずれかで始まる。matchは、次の意味のあるトークンが{のときだけ続けます。後置の match は必ずmatch {なので、正しいプログラムの読み方は変わりません。そうでない行は前置の match として新しい文に読み、E0142 を出します。コンパイラは前置のまま読んで回復し、型の検査は続けます。 - 開いた
(または[がまだ閉じていない。 - 次の行が
else、->、|のいずれかで始まる。どれも文を始めることがないので、継続にしても曖昧さは生まれません。} else {には formatter がそろえます。 - シグネチャの中で、次の行が
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 の別名のための予約語です。初版の範囲では構文を持ちません。
文脈依存の語
Section titled “文脈依存の語”次の語は示した位置でだけキーワードとして働きます。それ以外の位置では普通の識別子です。
| 語 | 位置 |
|---|---|
implements | handler の宣言 |
for | impl A for B |
fails | 関数のシグネチャと関数型 |
derive | type と enum の宣言の末尾、trait の本体 |
init release | handler の本体 |
of default | ラベルの宣言(ラベル) |
by | with の項目(ラベル) |
fn main() { fails = 1 init = 2 by = 3 default = 4 dbg(fails + init + by + default)}将来のための予約
Section titled “将来のための予約”次の語は構文を持ちませんが、識別子にも使えません。
var return while loop let mut in where do try catch throw async yield defer macro mod
fn main() { var = 1}キーワードでないもの
Section titled “キーワードでないもの”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 }
@testfn adds() { assert add(1, 2) == 3}