230 lines
7.5 KiB
Markdown
230 lines
7.5 KiB
Markdown
|
|
# ANCP Transcoder v1 トークン仕様(EBNF+運用ルール)
|
|||
|
|
|
|||
|
|
Author: ChatGPT5
|
|||
|
|
Date: 2025-09-03
|
|||
|
|
Version: 1.0
|
|||
|
|
|
|||
|
|
> まずは「P=Pretty(正典)→C=Compact(ANCP 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` | `m` |
|
|||
|
|
| 局所変数 | `local` | `~l` | `ˡ` |
|
|||
|
|
| 戻り | `return` | `~r` | `↩` |
|
|||
|
|
| 継承/委譲 | `from` | `@` | `@` |
|
|||
|
|
| 初期化 | `init` | `#` | `#` |
|
|||
|
|
| コンストラクタ | `birth` | `b` | `ᵇ` |
|
|||
|
|
| 静的 | `static` | `S` | `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) EBNF(P→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-A(Week1)** の「P↔C 可逆・安全」まで一気に行けるにゃ。
|
|||
|
|
|
|||
|
|
次にやるなら:この仕様をそのまま基に**トークナイザのテストケース**(OK/NG 30本)を並べよう。
|