marimo-team/smart-cellsmarimo 智能单元格Markdown/SQL与 Python 代码双向转换的纯解析库【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo导读marimo 的核心体验之一是让 Markdown 单元格与 SQL 单元格以智能单元格smart cell的形式存在——用户在编辑器中直接书写 Markdown 或 SQL底层却以mo.md(...)、mo.sql(...)这类纯 Python 代码持久化与执行。marimo-team/smart-cells正是负责这两种语言与 Python 代码互转的纯解析库它不依赖 React 或 CodeMirror 运行时只做解析 Python → 抽取目标语言内容和目标语言内容 → 回写 Python这两件事并在此过程中完整保留引号前缀、变量名、引擎、输出开关等元数据。读完本文你将掌握该库的核心接口契约、Markdown/SQL 两个解析器的双向转换原理、字符串与引号处理细节以及它在 marimo 前端编辑器中的真实挂载位置。一、为什么需要 smart-cells智能单元格的外挂语言机制在 marimo 中一个单元格的源码始终是 PythonMarkdown 内容写进mo.md(...)SQL 查询写进mo.sql(...)。但对用户而言直接编辑 Markdown 或 SQL 文本远比编辑带引号包裹的 Python 字符串更自然。smart-cells 要解决的问题就是编辑器中展示的目标语言内容 ↔ 磁盘上持久化的 Python 代码之间的无损双向转换。为此该库定义了解析器这一抽象并在 package.json 中以marimo-team/smart-cells为包名发布版本 0.1.0描述为 Pure parsing library for marimo smart cells (markdown, SQL, etc.)。它的关键工程约束体现在三处纯解析、零副作用sideEffects: false可在任意 JS 环境中安全引用无框架依赖接口层不引入 React / CodeMirror仅依赖lezer/pythonPython 语法树、codemirror/lang-pythonmarkdown 校验复用其 Python 语法与string-dedent缩进归一化零构建步骤build: echo No build step源码即产物main: src/index.ts配合vitest与tsgo分别承担测试与类型检查。二、核心接口契约LanguageParser 与元数据往返所有解析器都实现同一个框架无关的接口LanguageParserTMetadata定义在 types.tsinterface LanguageParserTMetadata Recordstring, unknown { readonly type: string; // 解析器唯一标识如 markdown / sql / python readonly defaultCode: string; // 该语言的默认 Python 代码模板 readonly defaultMetadata: ReadonlyTMetadata; transformIn(pythonCode: string): ParseResultTMetadata; // Python → 目标语言 transformOut(code: string, metadata: TMetadata): FormatResult; // 目标语言 → Python isSupported(pythonCode: string): boolean; // 当前 Python 代码是否可识别 }两个方向的数据结构同样在 types.ts 定义ParseResult包含提取出的目标语言代码code、该代码在原始 Python 字符串中的字符偏移量offset供编辑器精确定位光标以及解析过程中收集的metadataFormatResult包含回写好的 Python 代码code以及目标语言内容在其中的起始偏移offset。metadata是双向转换的记忆transformOut拿到的是之前transformIn产出的元数据因此引号前缀、SQL 引擎、dataframe 变量名等信息可以在编辑往返中被完整保留不会在保存时悄悄丢失。接口中还定义了 Python 字符串的引号前缀枚举types.tsexport const QUOTE_PREFIX_KINDS [, f, r, fr, rf] as const; export type QuotePrefixKind (typeof QUOTE_PREFIX_KINDS)[number]; export type QuoteType | | | ;QuotePrefixKind覆盖了空前缀普通字符串、f-string、raw string 以及组合形式fr/rfQuoteType则区分单引号、双引号与三引号。这八种前缀 × 四种引号形态的组合正是 Markdown 与 SQL 解析器需要逐一匹配的字符串形态。三、MarkdownParsermo.md(...)与纯 Markdown 互转MarkdownParsermarkdown-parser.ts负责mo.md(r# Hello)这类 Python 调用与纯 Markdown 文本之间的转换其type为markdown默认代码为mo.md(r\n)默认元数据使用r前缀raw string避免 Markdown 中的反斜杠被 Python 转义。3.1 Python → MarkdowntransformIntransformIn先对输入做trim()然后按顺序尝试所有前缀 引号组合构建的正则见 markdown-parser.ts每个正则匹配形如mo.md(\s*prefixquote.../quote\s*)的整行调用。命中后用splitQuotePrefix从起始引号中分离出前缀如rf→rf写入metadata.quotePrefix调用unescapeQuotes还原字符串内被转义的引号借助string-dedent去除多行字符串的公共缩进dedent 要求首尾行为空行因此先用\n补齐再.trim()通过pythonCode.indexOf(innerCode)计算内容在原文中的偏移量offset。值得注意的一个细节f-string 内的复杂表达式也能被正确抽取。测试用例markdown-parser.test.ts验证了mo.md(f# Count: {,.join(data[items])})这类表达式里再嵌引号的写法抽取后仍能还原为# Count: {,.join(data[items])}。这得益于外层用三引号 s标志dotAll的正则匹配以及 dedent 对整体块的统一处理。若所有正则都不命中例如内容根本不是mo.md(...)调用transformIn会原样返回输入offset为 0——这是一种安全的降级策略。3.2 Markdown → PythontransformOuttransformOut总是用三引号回写并特意处理了引号转义问题markdown-parser.tsconst escapedCode code.replaceAll(, String.raw\); const start mo.md(${quotePrefix}\n; const end \n); return { code: start escapedCode end, offset: start.length 1 };注释解释了这里的精妙之处只转义连续两个双引号而非全部双引号因为四个连续引号会提前终止字符串且转义的是第二个引号避免反斜杠紧贴首引号造成转义失效。回写后的起始偏移为start.length 1即三引号后内容的第一行位置。源码中明确标注此逻辑须与 Python 端 marimo/_convert/utils.py 的markdown_to_marimo保持行为一致NB. Must be kept consistent这保证了前端编辑与 Python 转换器两个入口产出的代码完全兼容。3.3 isSupported基于 Lezer 语法树的严格校验与 SQL 解析器不同isSupported并不只是字符串匹配而是用codemirror/lang-python的语法树对mo.md(...)的调用签名做逐节点精确比对markdown-parser.ts要求 AST 依次为Script → ExpressionStatement → CallExpression → MemberExpression( VariableName . PropertyName ) → ArgList → ( → String|FormatString → )的严格序列。这意味着mo.md()空调用与空字符串被显式视为支持非mo.md(开头的代码直接判定不支持只要调用形态多出任何节点例如存在多个调用、参数不是字符串、传入关键字参数isSupported即返回false调用方就会回退到普通 Python 单元格从而避免误把普通代码当作 Markdown 处理。四、SQLParsermo.sql(...)与纯 SQL 互转SQLParsersql-parser.ts是三个解析器中元数据最丰富的一个其SQLMetadata结构如下interface SQLMetadata { dataframeName: string; // 结果绑定的变量名默认 _df quotePrefix: QuotePrefixKind; // 引号前缀默认 fSQL 可内嵌 {变量} 参数化 commentLines: readonly string[]; // 单元格顶部的 # 注释行原样保留 showOutput: boolean; // 是否显示输出默认 true engine: string; // SQL 引擎默认 __marimo_duckdb }默认代码模板为_df mo.sql(fSELECT * FROM )并提供了静态工厂fromQuery(query)快速生成带缩进的 SQL 单元格。4.1 Python → SQLtransformIn基于 lezer/python 的 AST 解析transformIn的流程sql-parser.ts先通过extractCommentLines收集单元格顶部的#注释行——它们是 SQL 单元格的文档前缀会在回写时放回原处调用isSupported快速过滤必须包含且只能包含一次mo.sql调用交由parseSQLStatementsql-parser.ts做真正的 AST 解析。parseSQLStatement用lezer/python将代码解析为语法树后用TreeCursor遍历先查找赋值语句AssignStatement读取左侧VariableName得到dataframeName再定位右侧的CallExpression确认成员表达式文本精确等于mo.sql随后进入ArgList节点借助 python-ast.ts 的parseArgsKwargs分离位置参数与关键字参数。值得强调的两处严谨性设计拒绝条件表达式包裹如果右值是ConditionalExpression、BinaryExpression或UnaryExpression例如x mo.sql(...) if cond else ...直接返回null不会误判为 SQL 单元格源码注释引用 issue #7386赋值语句之后不允许有多余代码code.slice(assignStmt.to).trim().length 0即拒绝保证单元格是纯粹的 SQL 赋值。关键字参数解析sql-parser.ts支持engine...与outputTrue/Falseengine原样记录字符串值output按True字面量解析为布尔值。SQL 字符串内容则通过getStringContent抽取并交给safeDedent归一化缩进。最终transformIn返回去缩进的纯 SQL 文本、起始偏移与完整元数据。4.2 SQL → PythontransformOut还原完整调用签名transformOutsql-parser.ts依据元数据重建 Python 代码其中参数化输出逻辑很典型const start ${dataframeName} mo.sql(\n ${quotePrefix}\n; const escapedCode code.replaceAll(, String.raw\); const showOutputParam showOutput ? : ,\n outputFalse; const engineParam engine DEFAULT_ENGINE ? : ,\n engine${engine}; const end \n ${showOutputParam}${engineParam}\n); return { code: [...commentLines, start].join(\n) indentOneTab(escapedCode) end, offset: start.length 1, };从中可以提取出参数化的两条关键规则仅当偏离默认值时才写参数showOutput为false时追加outputFalseengine不等于默认的__marimo_duckdb时才追加engine...。这保证了生成的 Python 代码最小化、可读性强SQL 内容整体缩进一个 Tab4 空格由indentOneTabsql-parser.ts实现与mo.sql(后的对齐。4.3 处理边界SQL 解析器同样包含稳健的降级路径空字符串直接返回isSupported失败时原样返回输入并携带已提取的注释元数据。此外transformIn在 AST 解析异常时通过try/catch捕获并console.warn返回null走降级分支不会让编辑器崩溃。五、PythonParser恒等变换的默认语言PythonParserpython-parser.ts实现了一个平凡的但重要的语义Python 单元格不需要任何转换。它的transformIn/transformOut都是直通pass-throughoffset恒为 0isSupported恒返回true。它的价值在于让编辑器可以用同一套LanguageParser接口统一调度三种语言——Markdown、SQL、Python——而不必为普通 Python 单元格写特例逻辑。六、字符串与缩进的工具层包内utils目录提供了四个被解析器复用的基础工具工具文件核心能力python-ast.tsparsePythonASTlezer/python 语法树解析、parseArgsKwargs位置参数/关键字参数分离、getStringContent从String/FormatString节点抽取字符串内容覆盖r、f、rf等十余种前缀组合、getPrefixLength计算引号前缀字符数用于偏移定位string-escaper.tsescapeQuotes/unescapeQuotes按四种QuoteType分别处理引号转义与还原quote-parser.tssplitQuotePrefix按长度降序匹配前缀避免f误吞fr、isTripleQuote、getClosingQuotededent.tssafeDedent用\n补齐首尾行后调用string-dedent失败时安全返回原字符串其中getPrefixLengthpython-ast.ts是偏移计算的关键rf为 5、f/r为 4、普通三引号为 3、rf/fr为 3、f/r为 2、单引号为 1与getStringContent的切片长度严格对应保证抽取内容与偏移量始终一致。七、测试验证双向往返的强约束该包配有四个 vitest 测试套件__tests__对转换正确性做了系统验证markdown-parser.test.ts222 行覆盖三引号、单引号、f-string含嵌套引号、方括号下标、方法调用、r-string如 LaTeX$\nu ...$保留反斜杠、rf组合前缀、引号反转义等场景逐条断言抽取内容与offsetsql-parser.test.ts验证 SQL 抽取、engine/output参数回写、注释行保留与默认值省略规则python-parser.test.ts验证恒等变换python-ast.test.ts验证 AST 参数解析工具的正确性。测试运行方式为包内pnpm testvitest类型检查为pnpm typechecktsgo。八、在 marimo 前端中的实际应用smart-cells 并不是孤立存在的库它在 marimo 前端编辑器中被多处直接引用frontend/src/core/codemirror/language/languages/markdown.ts编辑器持有new MarkdownParser()实例并在创建 Markdown 单元格时调用静态方法MarkdownParser.fromMarkdown(markdown)生成初始 Python 代码frontend/src/core/codemirror/language/languages/sql/sql.ts导入SQLParser与SQLMetadata类型驱动 SQL 单元格的编辑体验frontend/src/core/cells/readonly-code-display.ts、frontend/src/core/codemirror/language/panel/panel.tsx 与 panel/markdown.tsx在只读展示与语言面板中复用解析能力frontend/src/components/dependency-graph/utils/cell-preview.ts依赖图中预览单元格内容时同样依赖该包。这印证了该库单一职责、多处复用的定位编辑器内的语言切换、单元格创建、依赖图预览等场景共享同一份解析逻辑保证任何入口产出的 Python 代码格式一致。由于仓库中frontend通过 workspace 依赖引用该包见 frontend/package.json修改smart-cells源码后无需构建即可被前端直接消费这也是package.json中刻意省略构建步骤的原因。九、总结marimo-team/smart-cells以约十个源文件、四个工具模块和四个测试套件完整实现了 marimo 智能单元格的Python ↔ 目标语言双向转换能力统一的LanguageParser接口让 Markdown、SQL、Python 三种语言共享同一套转换调度与元数据往返机制transformIn/transformOut的对称设计配合offset偏移量兼顾代码正确性与编辑器光标定位丰富的SQLMetadata元数据变量名、引擎、输出开关、注释行、引号前缀保证 SQL 单元格的参数化设置不会在往返中丢失基于 lezer/python 的 AST 级校验严格区分可识别的智能单元格与普通 Python 代码宁可降级也绝不误判。对于需要扩展 marimo 编辑器语言能力例如新增一种智能单元格类型的开发者而言实现一个LanguageParser子类、注册到前端语言面板即可复用整套机制对于只使用 marimo 的用户而言理解这个库有助于弄清mo.md(...)与mo.sql(...)在编辑体验与持久化格式之间的映射关系从而更自信地手写或迁移这类单元格。【免费下载链接】marimoA reactive notebook for Python — run reproducible experiments, query with SQL, execute as a script, deploy as an app, and version with git. Stored as pure Python. All in a modern, AI-native editor.项目地址: https://gitcode.com/GitHub_Trending/ma/marimo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考