文档中的步骤
结构步骤(非实际执行时序)
marqdo
lib/外联 用本机解释器运行代码围栏和内联源码。v2 不改 Markdown 表面,新增结构化记录、JSON 进出、并行扇出、Python/Node 持久内核和显式状态桥。
文件仍然是一篇文档:绑定的代码围栏是可读的 Markdown,外联.* 把它变成可执行单元。
先探测,再运行。
access(`node`, "ok")access(`node`, "version")探测 返回 {lang, cmd, path, ok, version, error}。省略语言时探测所有已知语言。
结构化记录,而不只是 stdout。
console.log("来自外联内核的问候");
来自外联内核的问候 — 20ms
access(`记录`, "ok")access(`记录`, "stdout")access(`记录`, "code")执行 在进程失败时不抛错,而是在一条记录里给出 ok、code、stdout、stderr、ms、timed_out、truncated、result 与 error。
JSON 进,JSON 出。
let input = "";
process.stdin.on("data", chunk => { input += chunk; });
process.stdin.on("end", () => {
console.log(JSON.stringify(JSON.parse(input) * 2));
});
42 — 30ms
输入编码成 JSON 写到 stdin;stdout 的最后一个值再解析成 JSON。想同时保留原始记录时,用 执行 的 输出="json"。
并行扇出。
| 数值 |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
批量调用 对每个输入独立运行同一程序,保持输入顺序并返回解析后的 JSON 列表;批量运行 对完整 invoke 选项表做同样的事。
持久内核。
access(`第二次`, "result")第一次求值创建的变量会保留到第二次。内核不是 shell:用 会话求值、会话取值、会话变量,最后 会话关闭。
显式状态桥。
这是 JSON 拷贝,不是共享内存:内核收到一个值,Marqdo 取回一个值。没有隐式镜像,也没有隐藏的全局命名空间。
view 中的单元。
foreign.session 拥有文档的内核状态。内核和单元顺序都是作为参数传入的普通表格,名字可以自由选择:
`分析内核` =
| 名称 | 语言 | 命令 | 说明 |
|------|------|------|------|
| 分析 | python | | 共享分析 |
| 复核 | python | | 独立复核 |
`分析单元` =
| 单元 | 内核 | 说明 |
|------|------|------|
| 准备数据 | 分析 | 写入状态 |
| 计算指标 | 分析 | 读取状态 |
| 独立复核 | 复核 | 隔离内核 |
`会话` = > foreign.session kernels=`分析内核` cells=`分析单元`
view 消费 会话 句柄,提供 Run All、Step、切换内核、journal 和单元状态。没有 会话 时,view 的 cells 是无状态的一次性运行:同一套外壳,但没有持久内核。没有第二个 CLI 模式。完整 Python 示例见 features/19-外联视图单元.mq.md。
安全默认值。
| 边界 | 默认 |
|---|---|
| 执行 | 需要宿主执行能力 |
| Shell | 不经过 shell;参数显式切分 |
| 输出 | 有界,带 truncated |
| 超时 | Unix 杀进程组 / Windows taskkill /T |
| 工作目录 | 文件沙箱根目录;不隐式越界 |
| 并行 | 扇出有上限;每个任务隔离 |
完整 API 见 ../../lib/外联.mq.md,设计见 ../../doc/design/foreign-kernel.md。
| lang | node |
| cmd | node -v |
| path | /usr/local/bin/node |
| ok | True |
| version | v22.23.4 |
| error |
| key | value |
|---|---|
| lang | node |
| cmd | node -v |
| path | /usr/local/bin/node |
| ok | True |
| version | v22.23.4 |
| error |
| # | value |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| # | value |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| ok | True |
| code | 0 |
| stdout | |
| stderr | |
| ms | 20 |
| lang | node |
| … | |
| key | value |
|---|---|
| ok | True |
| code | 0 |
| stdout | |
| stderr | |
| ms | 20 |
| lang | node |
| cmd | node |
| timed_out | False |
| truncated | False |
| result | 15 |
| result_error | |
| error | |
| script | |
| cached | False |
| alive | True |
| id | s1 |
| ok | True |
| code | 0 |
| stdout | |
| stderr | |
| ms | 0 |
| lang | node |
| … | |
| key | value |
|---|---|
| ok | True |
| code | 0 |
| stdout | |
| stderr | |
| ms | 0 |
| lang | node |
| cmd | node |
| timed_out | False |
| truncated | False |
| result | 30 |
| result_error | |
| error | |
| script | |
| cached | False |
| alive | True |
| id | s1 |
| # | value |
|---|---|
| 1 | 2 |
| 2 | 4 |
| 3 | 6 |
| 4 | 8 |
| # | value |
|---|---|
| 1 | 2 |
| 2 | 4 |
| 3 | 6 |
| 4 | 8 |
| ok | True |
| code | 0 |
| stdout | 来自外联内核的问候 |
| stderr | |
| ms | 20 |
| lang | node |
| … | |
| key | value |
|---|---|
| ok | True |
| code | 0 |
| stdout | 来自外联内核的问候 |
| stderr | |
| ms | 20 |
| lang | node |
| cmd | node /home/runner/work/marqdo/marqdo/public/.marqdo/tmp/marqdo-foreign-4543-1793515725466021619.js |
| timed_out | False |
| truncated | False |
| result | None |
| result_error | |
| error | |
| script | |
| cached | False |
---
markup: occlusion
title: lib/外联 — 外联内核 v2
description: 结构化执行、JSON 调用、并行扇出、持久内核与显式状态桥
导入:
外联: lib/外联.mq.md
stdin: 21
---
# main
`lib/外联` 用本机解释器运行代码围栏和内联源码。v2 不改 Markdown 表面,新增结构化记录、JSON 进出、并行扇出、Python/Node 持久内核和显式状态桥。
文件仍然是一篇文档:绑定的代码围栏是可读的 Markdown,`外联.*` 把它变成可执行单元。
---
先探测,再运行。
**node = > 外联.探测 语言="node"**
> 打印 内容=[ok](access(`node`, "ok"))
> 打印 内容=[version](access(`node`, "version"))
`探测` 返回 `{lang, cmd, path, ok, version, error}`。省略语言时探测所有已知语言。
---
结构化记录,而不只是 stdout。
`标记` =
```node
console.log("来自外联内核的问候");
```
**记录 = > 外联.执行 代码=`标记`**
> 打印 内容=[ok](access(`记录`, "ok"))
> 打印 内容=[stdout](access(`记录`, "stdout"))
> 打印 内容=[code](access(`记录`, "code"))
`执行` 在进程失败时不抛错,而是在一条记录里给出 `ok`、`code`、`stdout`、`stderr`、`ms`、`timed_out`、`truncated`、`result` 与 `error`。
---
JSON 进,JSON 出。
`双倍` =
```node
let input = "";
process.stdin.on("data", chunk => { input += chunk; });
process.stdin.on("end", () => {
console.log(JSON.stringify(JSON.parse(input) * 2));
});
```
**单个 = > 外联.调用 代码=`双倍` 输入=21**
> 打印 内容=`单个`
输入编码成 JSON 写到 stdin;stdout 的最后一个值再解析成 JSON。想同时保留原始记录时,用 `执行` 的 `输出="json"`。
---
并行扇出。
`数值表` =
| 数值 |
|------|
| 1 |
| 2 |
| 3 |
| 4 |
**翻倍 = > 外联.批量调用 代码=`双倍` 输入表=`数值表` 并发=4**
> 打印 内容=`翻倍`
`批量调用` 对每个输入独立运行同一程序,保持输入顺序并返回解析后的 JSON 列表;`批量运行` 对完整 invoke 选项表做同样的事。
---
持久内核。
**会话 = > 外联.会话开启 语言="node"**
**第一次 = > 外联.会话求值 标识=`会话` 源码="let total = 0; for (let i = 1; i <= 5; i += 1) total += i;"**
**第二次 = > 外联.会话求值 标识=`会话` 源码="total * 2"**
> 打印 内容=[result](access(`第二次`, "result"))
第一次求值创建的变量会保留到第二次。内核不是 shell:用 `会话求值`、`会话取值`、`会话变量`,最后 `会话关闭`。
---
显式状态桥。
**> 外联.会话设值 标识=`会话` 名称="rate" 值=0.08**
**利率 = > 外联.会话取值 标识=`会话` 名称="rate"**
> 打印 内容=`利率`
**> 外联.会话关闭 标识=`会话`**
这是 JSON 拷贝,不是共享内存:内核收到一个值,Marqdo 取回一个值。没有隐式镜像,也没有隐藏的全局命名空间。
---
view 中的单元。
`foreign.session` 拥有文档的内核状态。内核和单元顺序都是作为参数传入的普通表格,名字可以自由选择:
```text
`分析内核` =
| 名称 | 语言 | 命令 | 说明 |
|------|------|------|------|
| 分析 | python | | 共享分析 |
| 复核 | python | | 独立复核 |
`分析单元` =
| 单元 | 内核 | 说明 |
|------|------|------|
| 准备数据 | 分析 | 写入状态 |
| 计算指标 | 分析 | 读取状态 |
| 独立复核 | 复核 | 隔离内核 |
`会话` = > foreign.session kernels=`分析内核` cells=`分析单元`
```
`view` 消费 `会话` 句柄,提供 Run All、Step、切换内核、journal 和单元状态。没有 `会话` 时,view 的 cells 是无状态的一次性运行:同一套外壳,但没有持久内核。没有第二个 CLI 模式。完整 Python 示例见 [`features/19-外联视图单元.mq.md`](../features/19-外联视图单元.mq.md)。
---
安全默认值。
| 边界 | 默认 |
|---|---|
| 执行 | 需要宿主执行能力 |
| Shell | 不经过 shell;参数显式切分 |
| 输出 | 有界,带 truncated |
| 超时 | Unix 杀进程组 / Windows taskkill /T |
| 工作目录 | 文件沙箱根目录;不隐式越界 |
| 并行 | 扇出有上限;每个任务隔离 |
完整 API 见 [../../lib/外联.mq.md](../../lib/外联.mq.md),设计见 [../../doc/design/foreign-kernel.md](../../doc/design/foreign-kernel.md)。