# xl 产物映射:`-t ts` > 本文是 **ts 直出通道**(`xl build -t ts`)的产物映射规范:把 `*.xl.md` 的每个构造映射成 TypeScript 的哪一段代码。 > 语法以 [`xl-syntax.md`](./xl-syntax.md)(下称「语法」)为唯一事实来源;命令行接口见 [`xl-cli.md`](./xl-cli.md) §3.3;**完整输入与期望产物见 [`xl-base-case.md`](./xl-base-case.md)**(下称「基准样例」)。 > 与基准样例冲突时以基准样例为准——它就是本文的验收标准。 > 诊断码见 [`xl-check.md`](./xl-check.md)。 --- ## 1. 通道契约 ```text *.xl.md ──► 解析 ──► IR ──► 内置打印器(本文) ──► .ts ``` | 契约 | 内容 | | --- | --- | | 输入 | 一个 `*.xl.md` 的 IR + 原文 | | 输出 | 一个 `.ts` 文件;`layout` 恒为 `file`(ts 没有 `type` 布局) | | 生成者 | `xl` 内置打印器;**不联网、不调用模型、不读 provider 配置** | | 确定性 | 同一 IR 必得同一字节:无时间戳、无随机、无平台相关换行与编码 | | 编码 | UTF-8 无 BOM、LF;文件末尾恰好一个换行 | | 注释 | 源文件里的说明散文**不进产物**(只作计划通道的生成上下文,语法 §1) | | 语言覆盖段 | `## ` / `### ` / `#### ` 一律**不进** ts 产物,只供计划通道(`xl-cli.md` §3.4) | > `-t ts -t csharp` 同时给出时,ts 部分照旧离线直出并写盘;非 ts 目标只产出计划(`xl-cli.md` §3.4),不落盘、不调用任何模型。 --- ## 2. 文件骨架 产物 = 产物头 + 正文,两者之间**空一行**;正文内部顶层构造之间空一行: ```ts // @generated by xl from pkg/demo.xl.md // xl:sha256: xl:target:ts xl:ver:0.1.0 // DO NOT EDIT — 修改请改 xl.md 并重新生成 <1> # dependencies 的默认语言(ts)代码块 <2> 一级声明与 # statement 段,按源文件出现顺序 ``` | 段 | 来源 | 规则 | | --- | --- | --- | | 产物头 3 行 | `xl` 统一追加 | `xl.json` 的 `build.header: false` 时不写;ts 头用 `//`(`xl-cli.md` §3.6);ts 头第 2 行只有 `xl:sha256` / `xl:target` / `xl:ver` 三个字段,非 ts 产物另加 `xl:model` 与 `xl:prompt` | | 正文 `<1>` | `# dependencies` 的 ```` ```ts ```` 块 | 按块出现顺序拼接,块之间以空行分隔;没有该块则不产生首段 | | 正文 `<2>` | 一级声明与 `# statement` | `# type` / `# const` / `# method` / `# enum` / `# interface` / `# class` / `# statement`(§14),**顺序与源文件一致**,不重排、不分组 | | 顶层空行 | 打印器 | 相邻两段之间恰好一个空行 | * `# namespace` 的名称与说明只作为非 ts 目标的生成上下文(`xl_context` 的 `language.namespace`),**不产生 ts 代码**(ts 的模块边界是文件,不是 namespace)。 * `# dependencies` 的 ```` ```xl ```` 块(跨文件引用)转成 import(§3);其余语言子标题下的块被忽略。 --- ## 3. 依赖导入 `# dependencies` 下按「块类型」分流: | 块 | 处理 | | --- | --- | | ```` ```ts ````(默认语言) | 原样成为产物正文首段(仅做 §11 的最小规范化) | | ```` ```xl ```` | 每行 `import { … } from "./x.xl.md"` → 同名相对模块的 import(见下) | | 其它 ```` ``` ````(无子标题) | 非法:`# dependencies` 只允许 `ts` 与 `xl` 两种围栏(`xl-check.md` 的 `E1302`) | | `## ` 下的块 | 忽略(给计划通道) | **import 路径改写**:`"./x.xl.md"` → `"./x"`——去掉 `.xl.md` 后缀并省略目标扩展名(ESM / bundler 惯例),`import` / `import type` / 重命名导入都只改模块说明符。 ````md # dependencies ```xl import { level } from "./util.xl.md" ``` ```ts import _ from "lodash"; ``` ```` ```ts import _ from "lodash"; import { level } from "./util"; ``` * 块内**已手工书写**同一模块的 import 时,不再生成重复行;此时原样保留手工写法。 * 依赖文件自身**不**参与产物生成,也不会被自动编译(`xl-cli.md` §3.2)。 * 引用的目标不存在、或文件里没有对应导出 → 见 [`xl-check.md`](./xl-check.md) 的 `E1006`。 --- ## 4. 类型映射 | 源类型 | ts | 例 | | --- | --- | --- | | `int` / `float` / `double` / `number` | `number` | `int` → `number` | | `bool` | `boolean` | | | `string` / `any` / `void` | 同名 | `V = any` → `V = any` | | `Array` / `T[]` | `T[]` | `Array>` → `number[][]` | | `ReadonlyArray` | `readonly T[]` | 恒带 `readonly` 前缀 | | `Map` / `Set` / `Promise` / `Generator` | 同名,实参递归映射 | `Map` → `Map` | | 其它具名类型 | 原样保留 | `level` → `level`(依赖导入见 §3) | | 字面量类型 | 原样保留 | `"printable"` / `2` / `true` | | 联合类型 `A \| B` | 原样保留 | | | 函数类型 `(a:int)=>void` | `(a: number) => void` | 参数名与类型递归映射 | | `T?`(成员 / 参数后缀) | **位置相关**,见下 | | **可选类型 `T?` 的两个位置**(语法 §12、§8): | 位置 | 写法 | 例 | | --- | --- | --- | | 字段 / 属性声明 | 属性名后加 `?`,类型不加 `\| undefined` | `## field note?:string` → `note?: string;` | | 参数 | 参数名后加 `?`、或给默认值 | `encoding?:string` → `encoding?: string`;`x?:int = 0` → `x: number = 0` | * 显式写成联合的 `T \| undefined` 不走 `?`,按联合原样映射。 * 元素类型自身含 `|` 或 `=>` 时,`T[]` 需加括号:`Array` → `(string \| null)[]`。 --- ## 5. 模块级声明 | 源(一级标题) | ts | 例 | | --- | --- | --- | | `# type = <原文>` | `export type = <原文>;` | §5.1 | | `# const : = <初始值>` | `export const : = <初始值>;` | §5.2 | | `# const :` + 默认语言块 | `export const : = <块体>;` | §5.2 | | `# method :(<参数>)=><返回>` | `export function (<参数>): <返回> { … }` | §5.3 | 一级声明的可见性(语法 §3): | 源 | ts | | --- | --- | | 省略可见性 / `# public` | 加 `export` | | `# private` | 不加 `export`(文件内可见;`private` 不是 ts 顶层声明的合法修饰符) | | `# protected` | 非法(`xl-check.md` 的 `E1202`) | ### 5.1 `# type` 等号右侧是**原文**,不参与 xl 本体的类型解析(语法 §6),因此逐字保留: ```md # type MemberKind = "field" | "property" | "method" ``` ```ts export type MemberKind = "field" | "property" | "method"; ``` ### 5.2 `# const` 初始值两种给法(语法 §7),产物形态一致: ````md # const MAX_DEPTH:int = 8 # const RULES:ReadonlyArray ```ts [ "E1001", "E1002", ] ``` ```` ```ts export const MAX_DEPTH: number = 8; export const RULES: readonly string[] = [ "E1001", "E1002", ]; ``` ### 5.3 `# method` 模板(语法 §8):`# [modifiers] method :(<参数>)=><返回类型>` ````md # method pickFirst:(items:Array)=>T ```ts return items[0]!; ``` ```` ```ts export function pickFirst(items: T[]): T { return items[0]!; } ``` | 源特征 | 产物签名 | | --- | --- | | 泛型 `:` | 名字后 `` | | 泛型约束 / 默认值 `:` / `` | 原样保留 `` / `` | | `async`(写在冒号之后) | `async (…): Promise<返回>` | | 体内出现 `yield` | `*(…): Generator<返回>`(`async` 与 `yield` 同时出现时 `async *` 并返回 `Generator`) | | `# private method …` | 不加 `export`(`private function` 不是合法 ts,产物是文件内可见的函数) | --- ## 6. 一级类型声明 | 源 | ts 头部 | | --- | --- | | `# enum ` | `export enum {` | | `# interface ` | `export interface {` | | `# interface extends ` | `export interface extends {`(单继承,语法 §10) | | `# class ` | `export class {` | | `# class ` | `export class {` | | `# class extends ` | `export class extends {` | | `# class implements ` | `export class implements {` | | `# class extends point implements printable` | `export class extends point implements printable {` | * 三段顺序固定为 `泛型 → extends → implements`(语法 §11),产物同序。`# class` / `# interface` 的成员(含 `readonly` 字段)按 §6.2、§7–§9 渲染。 * 可见性:`# private` 不加 `export`(产物是文件内可见的声明,而不是 ts 的 `private` 关键字);`# public` 与省略等价。 * 空声明(源文件中没有任何成员)输出两行:`export class cache {` 与 `}`。 ### 6.1 `# enum` 成员列表项 `- case ` → 枚举成员;`- case green = 2` 的右侧是**原文**,逐字保留: ```md # enum color - case red - case green = 2 - case blue ``` ```ts export enum color { red, green = 2, blue } ``` * 成员之间**不空行**,也不补尾随逗号;只写 `- case` 列表项,成员说明散文(`红色` 等)不进产物。 * `## ` 段(如 `## python`)是别的目标的实现说明,不进 ts 产物。 ### 6.2 `# interface` 成员只声明、不带函数体、不带初始值(语法 §10): ```md # interface printable ## readonly field kind:"printable" ## readonly field id:string ## field note?:string ## method print:()=>string ``` ```ts export interface printable { readonly kind: "printable"; readonly id: string; note?: string; print(): string; } ``` | 源成员 | ts | | --- | --- | | `## field :` | `: ;` | | `## field ?:` | `?: ;` | | `## readonly field :` | `readonly : ;` | | `## method :(<参数>)=><返回>` | `(<参数>): <返回>;` | | `## property :` | `: ;`(属性签名) | | `## property :` + 只挂 `### get` | `readonly : ;` | * 接口头与 class 同形:`# interface name extends Base` → `export interface name extends Base {`。 * 接口成员**不写可见性**(ts 接口成员恒为公开)。写在上面的 `public` / `private` / `protected` **不会被拒绝**(`xl-check.md` §3.2 C 的实现说明),产物同样忽略它们;`readonly` 只对 `## field` 有意义(property 的只读性由「有没有 `### set`」决定)。 * 成员之间**不空行**;空接口输出 `export interface x {\n}`。 * interface 里的 `## method` / `## property` 的访问器必须无函数体;带体会被 `xl-check.md` 的 `E1304` 拒掉。 --- ## 7. 类成员:字段 模板(语法 §12):`## [modifiers] field : = <初始值>` 产物模板:`<可见性> [static] [readonly] [?]: <类型> [= <初始值>];` | 源 | ts | | --- | --- | | `## field x:int = 0` | `public x: number = 0;` | | `## static readonly field ORIGIN:string = "0,0"` | `public static readonly ORIGIN: string = "0,0";` | | `## private field raw:int = 0` | `private raw: number = 0;` | | `## protected field buf:string = ""` | `protected buf: string = "";` | | `## field note?:string` | `public note?: string;` | | `## field data:Map` + 默认语言块 `new Map()` | `public data: Map = new Map();` | * 修饰符顺序固定为 `可见性 → static → readonly`,与源文件里的书写顺序无关(语法 §3)。 * 省略可见性即 `public`,且**显式打印** `public`(与基准样例一致)。 * 初始值:内联 `= ` 优先;否则取该成员默认语言代码块的正文作为初始化表达式(§11)。两者都没有 → 只输出声明,不带 `=`。 * 字段的说明散文不进产物。 --- ## 8. 类成员:property 与访问器 `property` 与 `field` 的差别是它可以挂 `### get` / `### set`(语法 §13、§14)。ts 侧一律展开成**私有字段 `#` + 访问器**: | 源形态 | 产物 | | --- | --- | | `## property label:string = "unnamed"` + `### get` + `### private set`(两个空标记) | `#label: string = "unnamed";` + `public get label(): string { return this.#label; }` + `private set label(value: string) { this.#label = value; }` | | `## property score:int` + `### get`(带体)+ `### set`(带体) | `public get score(): number {…}` + `public set score(value: number) {…}` | | `## property score:int` + `### get`(带体) | 只生成 getter,**不合成 setter**(只读计算属性) | | `## property score:int` + `### set`(带体) | `#score: number;` + `public set score(value: number) {…}` | | `## property tag:string` + `### get`(空)+ `### set`(空) | `#tag: string;` + 直通 getter / setter | | `## property tag:string`(**不挂访问器**) | 等价于上一行:`#tag: string;` + 直通 getter / setter | | `### get` 或 `### set` 体内出现 `yield` | 该访问器变成 `public *(): Generator<> {…}` | 规则汇总(由基准样例反推): 1. **后备字段 `#`**:有初始值、或 getter 没有可直出的函数体(只有 `### set`、getter 是生成器、或没有 `### get`)时生成;getter 带函数体且无初始值时视为计算属性,不生成后备字段。后备字段行与其后的访问器之间空一行(基准样例的 `#label` 之后即空行)。 2. **访问器只有标题没有代码块** → 合成直通实现:`get` 为 `return this.#;`,`set` 为 `this.# = value;`。 3. **访问器可见性**:`### private set` / `### protected set` 写进 setter 前;`### get` 的可见性同理;省略即 `public`。 4. **不挂访问器**:合法,等价于挂了一对空标记(读写都支持)。只挂 `### get` 或只挂 `### set` 同样合法。 5. 访问器顺序任意书写(`set` 在 `get` 之前也合法),**产物恒按 get 在前、set 在后**(语法 §14)。 6. `property` 的成员名不加 `?`:可选性是访问器语义,不是 `#` 字段的语义。 7. 基准样例没有覆盖「默认值 + 带体 getter」这一组合(两者同时存在时以后备字段承载初始值,getter 体照常展开);别把它当作已冻结的字节契约。 --- ## 9. 类成员:method 与 constructor ### 9.1 `method` 产物模板:`<可见性> [static] [async] [*](<参数>): <返回> { … }` | 源 | ts | | --- | --- | | `## method print:()=>string` | `public print(): string { … }` | | `## private method clamp:(v:int)=>int` | `private clamp(v: number): number { … }` | | `## static method create:()=>int` | `public static create(): number { … }` | | `## method load:async (url:string)=>string` | `public async load(url: string): Promise` | | `## method tick:()=>int` + 体内 `yield` | `public *tick(): Generator` | | `## method pick:(items:Array)=>U` | `public pick(items: U[]): U { … }` | * `async` 写在冒号之后(语法 §8),不是 `method` 之前。 * **generator 判定**:默认语言代码块体内出现 `yield` 关键字(忽略字符串与注释里的 `yield`)即按生成器直出,加 `*` 并把返回类型包成 `Generator`;`T` 已是 `Generator<…>` 时不重复包装。 * **async 判定**:`async` 修饰即把返回类型包成 `Promise`;`T` 已是 `Promise<…>` 时不重复包装。 * 返回类型 `void` 原样保留。 * 参数表:`name: T`;可选参数 `name?: T`;带默认值 `name: T = <默认值>`;泛型参数与约束原样保留。 ### 9.2 `constructor` 模板(语法 §15):`## [modifiers] constructor:(<参数>)=>void` ````md ## constructor:(x?:int, y?:int)=>void ```ts this.x = x ?? 0; this.y = y ?? 0; ``` ```` ```ts constructor(x?: number, y?: number) { this.x = x ?? 0; this.y = y ?? 0; } ``` * `constructor` 没有成员名与返回类型标注,产物固定 `constructor(<参数>) { … }`。 * 可见性写在 `constructor` 之前,产物前一并打印(没写可见性时保留裸 `constructor(...)`);`static` / `readonly` 报 `E1202`,返回类型不是 `void` 或一个类里出现多个 `constructor` 报 `E1206`。写成 `## async constructor:…` 之类会掉进未知成员种类(`E1201`)。 * 类内的 `constructor` 位置与源文件一致,**不**挪到成员首位。 --- ## 10. 成员排列与空行 同一 class 内成员**按源文件顺序**输出,打印器只决定空行。基准样例里逐行可验证的规则: | 情形 | 空行 | | --- | --- | | 相邻的两个「单行紧凑块」之间(数据字段、压成一行的访问器) | 不空行 | | 上一块是紧凑块、下一块是「展开的块」(多行体方法、多行访问器) | 空一行 | | 展开的块之后接新成员 | 空一行 | | 同一 `property` 内:后备字段 → 访问器 | 空一行(基准样例 `#label` 之后即空行) | | 类头与第一个成员、最后一个成员与 `}` 之间 | 不空行 | **紧凑块**指只占一行的成员行:字段(`public x: number = 0;`)、枚举式的数据行、以及 §11 里被压成一行的访问器(`public get label(): string { return this.#label; }`)。 基准样例可逐条对照:`kind`/`id`/`tags`/`logLevel`/`ORIGIN`/`raw`/`count` 连排不空行;`move` 之后接 `constructor` 不空行(展开块 → 展开块);`constructor` 之后接 `print` 空一行;`print`/`chunk2`/`load`/`tick`/`clamp` 之间各空一行。 同类规则适用于 `# interface`:接口成员一律是单行声明,所以**成员之间不空行**(基准样例的 `kind`/`id`/`note`/`print`)。 --- ## 11. 代码块的最小规范化 `ts` 代码块**原样**成为方法体 / 初始化表达式,只做以下规范化(`xl-cli.md` §3.3): 1. 行尾 `CRLF` / `CR` → `LF`;去行尾空白。 2. 去掉首尾空行。 3. 去缩进:以块内最小缩进为基准归零(空行不参与计算);块内各行的**相对**缩进原样保留。 4. 块体以外的内容不改一个字符。 打印器把规范化后的块按**位置**缩进,不做重新排版: * 方法体 / 访问器体 / `# statement` 段:整体再缩进一级(2 空格),包在 `{` / `}` 之间(`# statement` 没有花括号)。 * 字段 / `# const` 的初始化表达式:首行接在 `= ` 之后,其余行按原相对缩进输出(类成员整体再加一级成员缩进)。 * 因此「块内每级 2 空格」是**源文件的写法**决定的,不是打印器重排的结果:源里写 4 空格缩进,产物里就是 4 空格。 **体的排布**(`{ … }` 的位置): | 体 | 产物 | | --- | --- | | 有体(多行或复杂表达式) | `头 {` + 缩进体 + `}`(`}` 与声明头同级) | | 空体 | `头 {}`(如 interface 之外的无体方法;本次目标里含非 ts 目标时 `W3010` 会提示补体) | | getter 体恰好是 `return this.;` 单行 | 压成一行 `public get score(): number { return this.raw; }` | | 其它单行体 | 仍展开成 `{` … `}` 三行 | > 单行压缩**只**用于访问器,且只用于「返回单个成员」这一种形状——基准样例的合成 getter(`get label`)压成一行,getter 体 `return this.raw;` 也压成一行,而 `set score` 因含 `throw` 仍展开成三行。 --- ## 12. 完整产出对照 输入取 [`xl-base-case.md`](./xl-base-case.md) 的完整 case(`demo.xl.md`),产物头之后的部分应逐字节等于基准样例的「输出 ts 完整代码」(去掉头三行): ```ts export type MemberKind = "field" | "property" | "method"; export const MAX_DEPTH: number = 8; export const RULES: readonly string[] = [ "E1001", "E1002", ]; export function loadText(path: string, encoding?: string): string { return rf(path, encoding ?? "utf8"); } export enum color { red, green = 2, blue } export interface printable { readonly kind: "printable"; readonly id: string; note?: string; print(): string; } export class box extends point implements printable { public readonly kind: "printable" = "printable"; public id: string = "box"; public tags: string[] = []; public logLevel: level = level.info; public static readonly ORIGIN: string = "0,0"; private raw: number = 0; public static count: number = 0; #label: string = "unnamed"; public get label(): string { return this.#label; } private set label(value: string) { this.#label = value; } … } ``` (上例为节选;完整产物见 [`xl-base-case.md`](./xl-base-case.md)。) --- ## 13. 不属于 ts 直出通道的内容 | 源构造 | 说明 | | --- | --- | | `## ` / `### ` / `#### ` | 只作为计划通道的生成依据(`xl-cli.md` §3.4) | | `# namespace` 的名称 | 只影响非 ts 目标的包 / 命名空间(`targets..namespace`) | | 说明散文 / 成员注释 | 只作生成上下文,**不**转成 tsdoc | | `# dependencies` 的 ```` ```xl ```` 块 | 只用于跨文件校验与 import 生成(§3) | **非 ts 目标(计划通道)对 ts 侧的要求**:所有可执行成员(方法、访问器、带初始值的字段、构造函数)都应有默认语言(ts)代码块或 `### ` 说明,否则目标语言生成缺依据(`xl-check.md` 的 `W3010`)。`# statement` 同此,只是覆盖段的层级是 `## `(语法 §16)。 --- ## 14. `# statement` `# statement`(语法 §17)的默认语言(`ts`)代码块成为产物正文里的一个段落,**不加 `export`**、不加任何前缀: ````md # statement 进程入口。 ```ts Main(process.argv.slice(2)); ``` ```` ```ts Main(process.argv.slice(2)); ``` | 源特征 | 产物 | | --- | --- | | 默认语言块 | 该段落原样(经 §11 的最小规范化) | | 多个 `# statement` 段 | 各成一个段落,位置与源文件一致,段间恰好一个空行 | | 说明散文 | 不进产物(§1) | | `## ` 段 | 不进产物,只作非 ts 通道的生成依据(§13) | | 没有默认语言块 | 该段不进产物正文;本次目标里含 `ts` 时提示 `W3010`(那一段会从 ts 产物里整段消失) | | 默认语言块里出现静态 `import` / 带 `from` 的 `export` | 提示 `W3013`(`xl-check.md`):会被提升到产物头之前 | * 段内没有非空的默认语言块、也没有带代码块的 `## ` 段时,源文件本身就是错的(`xl-check.md` 的 `E1110`)。 * `# statement` 不声明名字:不出现在产物的导出清单里,也不参与结构回读校验(类型集合 / 成员名 / 参数个数)。