Files
hakorune/docs/development/roadmap/phases/phase-12.7/ancp-specs/ANCP-Token-Specification-v1.md

230 lines
7.5 KiB
Markdown
Raw Normal View History

# ANCP Transcoder v1 トークン仕様EBNF運用ルール
Author: ChatGPT5
Date: 2025-09-03
Version: 1.0
> まずは「P=Pretty正典→C=CompactANCP ASCII/Unicode」の**トークン変換**が安全に往復できる最小コア。
## 1) レクサの前提
* 入力は UTF-8。**ASCIIモード**と**Unicodeモード**を切替(既定=ASCII
* 変換は**トークン列→トークン列**。**文字列/コメント/正規表現**内部は**絶対に変換しない**。
* 空白/改行/コメントは**隣接トークン間のメタに付与**して保持ソースマップ2.0)。
### 1.1 トークンクラス(共通)
```ebnf
Identifier = IDStart IDContinue* ;
IDStart = Letter | "_" ;
IDContinue = IDStart | Digit ;
Digit = "0"…"9" ;
Letter = "A"…"Z" | "a"…"z" | NonAsciiLetter ;
IntLiteral = Digit+ ;
FloatLiteral = Digit+ "." Digit+ (ExponentPart)? | "." Digit+ (ExponentPart)? ;
ExponentPart = ("e"|"E") ("+"|"-")? Digit+ ;
StringLit = '"' ( Escape | ~["\r\n] )* '"'
| "'" ( Escape | ~['\r\n] )* "'" ;
Escape = "\\" . ;
RegexLit = "/" ( Escape | ~[/\r\n] )+ "/" [a-z]* ; // PのみCでは素通し
CommentLine = "//" ~[\r\n]* ;
CommentBlock = "/*" .*? "*/" ; // ネスト不可Phase1
WS = Space | Tab ;
NL = "\r"? "\n" ;
```
## 2) 予約語と記号マップP→C
**衝突しないASCII記号**を採用。Unicodeモードは `→` の右側を `uni` 欄で置換。
**識別子と区別**するため、`~x` 形は**先頭に `~`**を付ける通常のIDに現れにくい
| 機能 | Pretty(P) | Compact(C ascii) | Compact(C uni) |
|------|-----------|------------------|----------------|
| Box定義 | `box` | `$` | `` |
| 新規生成 | `new` | `~n` | `ⁿ` |
| 自参照 | `me` | `m` | `` |
| 局所変数 | `local` | `~l` | `ˡ` |
| 戻り | `return` | `~r` | `↩` |
| 継承/委譲 | `from` | `@` | `` |
| 初期化 | `init` | `#` | `` |
| コンストラクタ | `birth` | `b` | `ᵇ` |
| 静的 | `static` | `S` | `` |
| 条件 | `if` | `?` | `` |
| else | `else` | `:` | `` |
| ループ | `loop` | `~L` | `ᴸ` |
| 継続 | `continue` | `~c` | `↻` |
| 分岐peek | `peek` | `~p` | `ᵖ` |
> 予約域:`~[A-Za-z]` は**将来予約**で識別子に使えないことを仕様化。
## 3) 演算子・糖衣P↔C 等価)
* パイプ |>: `a |> f(x)`**そのまま**(記号は等価、空白最小化のみ)
* セーフアクセス ?.: `o?.f`**そのまま**
* ディレクティブ /:: `/: name`**そのまま**(意味を壊さず最小化)
## 4) セパレータ・自動挿入規約
* **C出力**時、**記号トークンの左右に英数IDが隣接**する場合は**1スペース**を強制挿入(`m$X` の誤読防止)。
* セミコロンは P 側の規約準拠。C では**危険箇所のみ挿入**§6の「ASI判定」参照
## 5) 変換アルゴリズム(疑似コード)
```text
encode(P → C):
lex P → tokens[]
for t in tokens:
if t in (StringLit, Comment*, RegexLit): emit t (verbatim); continue
if t is Keyword and t.lexeme in MAP: emit MAP[t.lexeme] as SymbolToken
else emit t (with WS-minify rules)
apply ASI (only-when-necessary)
attach inter-token trivia to sourcemap
decode(C → P):
lex C → tokens[]
for t in tokens:
if t is SymbolToken and t.lexeme in INV_MAP: emit INV_MAP[t.lexeme] as Keyword
else emit t
restore WS/comments by sourcemap if available
```
## 6) ASIセミコロン自動挿入判定最小
**挿入する**条件(どれか):
1. 次トークンが `}` / EOF
2. 現トークンが `return (~r) / continue (~c) / break` 等で、**直後が行末**
3. 構文上、次トークンが**先頭に来るべき**(例えば次が `box/$` 定義)
**挿入しない**
* 行末でも、次トークンが `(` `[` `{` `.` `?.` `/:` のとき
## 7) EBNFP→C 変換で必要なサブセット)
**目的**:可逆のための**字句と一部構文の境界**を定義。完全文法ではなく、トークン接合規則に必要な核のみ。
```ebnf
Program = WS_NL* (Stmt WS_NL*)* ;
Stmt = BoxDecl
| LocalDecl
| ReturnStmt
| ExprStmt
;
BoxDecl = "box" Identifier BoxBody ;
BoxBody = "{" (MemberDecl WS_NL*)* "}" ;
MemberDecl = ( FieldDecl | MethodDecl | StaticDecl ) ;
FieldDecl = ( "init" | "#" ) Identifier ( "=" Expr )? ";"? ;
MethodDecl = Identifier ParamList Block ;
StaticDecl = ( "static" | "S" ) MethodDecl ;
LocalDecl = ( "local" | "~l" ) Identifier ( "=" Expr )? ";"? ;
ReturnStmt = ( "return" | "~r" ) Expr? ";"? ;
ExprStmt = Expr ";"? ;
Expr = AssignExpr ;
AssignExpr = OrExpr ( AssignOp OrExpr )? ;
AssignOp = "=" | "+=" | "-=" | "*=" | "/=" ;
OrExpr = AndExpr ( "||" AndExpr )* ;
AndExpr = PipeExpr ( "&&" PipeExpr )* ;
PipeExpr = TernaryExpr ( "|>" CallLike )* ;
TernaryExpr = NullsafeExpr ( "?" Expr ":" Expr )? ;
NullsafeExpr = MemberExpr | MemberExpr "?." Identifier | MemberExpr "/:" Identifier ;
MemberExpr = Primary ( ("." | "[") ... )? ; // 省略(可逆に影響しない部分)
CallLike = Identifier | Call ;
Call = Identifier "(" ArgList? ")" ;
ArgList = Expr ("," Expr)* ;
Primary = Identifier
| Literal
| "(" Expr ")"
;
Literal = IntLiteral | FloatLiteral | StringLit | RegexLit ;
Identifier = see §1.1 ;
```
> **ポイント**
> * `FieldDecl` は `init` と `#` を等価扱いCでは `#` に寄せる)
> * `StaticDecl` は `static` と `S` を等価
> * `LocalDecl` は `local` と `~l` を等価
> * `ReturnStmt` は `return` と `~r` を等価
> * `box` は `$` と等価(`BoxDecl`
## 8) ソースマップ2.0(トークン粒度)
* **単一フォーマットJSON Lines 推奨)**:各出力トークンに**元トークン範囲**と**トリビア**を付与。
```json
{"out_i":42,"out_span":[l1,c5,l1,c6],"in_file":"foo.ny","in_span":[l10,c1,l10,c3],"trivia":{"lead":" ","trail":""}}
```
* 例外/ログは**BoxID + トークン範囲**で P へ逆引き。
## 9) 衝突回避ルール(最重要)
* **ASCIIモード**`~[A-Za-z]` は**保留記号**。Identifier と**絶対に一致しない**。
* **記号の周囲**`$ m` のように**必要時1スペース**前後が英数IDの場合
* **文字列/コメント/Regex****一切変換せず** verbatim。
## 10) 例(往復保証)
**P (Pretty)**
```nyash
box NyashCompiler {
compile(source) {
local ast = me.parse(source)
local mir = me.lower(ast)
return me.codegen(mir)
}
}
```
**C (Compact ASCII)**
```
$ NyashCompiler{
compile(src){
~l ast=m.parse(src)
~l mir=m.lower(ast)
~r m.codegen(mir)
}
}
```
**decode(C) → P** は上記Pと**等価**(空白/改行はソースマップで復元)。
---
## 実装メモ(すぐ書ける骨組み)
* レクサは **状態機械**`DEFAULT / STRING / REGEX / COMMENT`
* 置換は**辞書マッチ → 最長一致**`box``$``Identifier` と衝突させない)
* 出力時に**区切り挿入規則**を適用:`need_space(prev, next)`
* ASI は §6 の規則のみ実装Phase1。曖昧時は**セミコロン挿入を選ぶ**。
---
これで **Phase 12.7-AWeek1** の「P↔C 可逆・安全」まで一気に行けるにゃ。
次にやるなら:この仕様をそのまま基に**トークナイザのテストケース**OK/NG 30本を並べよう。