JavaScript 目标
把同一份 .cql 中文源码转译为 JavaScript,让程序彻底脱离 Python 运行时,在浏览器与 Node 中直接运行。
18.1为什么需要 JS 目标
CQL 默认把 .cql 中文源码转译为等价的 Python 3 代码,运行依赖 Python 运行时。而在日常开发中,很多场景并没有 Python 环境,例如浏览器页面、前端工程、Node.js 服务等。为此 CQL 提供了JavaScript 目标:把同一份源码转译为 JavaScript,从而脱离 Python 运行时,直接在浏览器或 Node.js 中运行。
- 浏览器直接运行:
cql html生成自包含 HTML 页面,浏览器双击即运行,连 Node 都不需要。 - Node 直接运行:
cql runjs转译为 JS 后直接交给 Node.js 执行。 - 一份源码,两个目标:同一份
.cql既可以cql run用 Python 运行,也可以转译为 JS 在浏览器 / Node 中运行,两个目标语义保持一致。 - 语法覆盖完整:类、继承、异常、推导式、切片、生成器(产量)、异步(异步函数 / 等待)等都能自动转译。
18.2转译命令
JavaScript 目标相关的命令都在 cql 这个命令行工具上,用法如下:
cql js 文件.cql # 转译为同名 .js
cql js 文件.cql -o out.js # 转译为指定文件
cql js 文件.cql --stdout # 只打印生成的 JS 源码,不写文件
cql runjs 文件.cql # 转译为 JS 并用 Node.js 执行
cql html 文件.cql # 生成自包含 HTML 页面,浏览器双击即运行
cql html 文件.cql -o out.html # 生成到指定 HTML 文件其中 cql runjs 和 cql html 内部都先执行 JS 转译,再分别交给 Node.js 与浏览器运行。想先看看生成的 JS 长什么样,用 --stdout 直接打印到终端最方便:
$ cql js examples/js_demo.cql --stdout
// 生成的自包含 JavaScript 源码
// ...(省略)18.3运行示例
以 examples/js_demo.cql 为例,它同时兼容 Python 与 JavaScript 两个目标,演示了类、切片、推导式与递归:
# js_demo.cql
# 本示例演示 CQL 的 JavaScript 目标(脱离 Python 运行时):
# cql js js_demo.cql 转译为 .js
# cql js js_demo.cql --stdout 查看生成的 JS 源码
# cql runjs js_demo.cql 直接让 Node.js 执行
# 同时兼容 Python 目标:cql run js_demo.cql
类 计数器:
函数 __init__(自身, 起始 = 0):
自身.值 = 起始
函数 增加(自身, 步 = 1):
自身.值 += 步
返回 自身.值
计数 = 计数器(1)
打印("计数器:", 计数.增加(), 计数.增加(2))
数据 = [0, 1, 2, 3, 4, 5, 6]
打印("隔二取一:", 数据[::2])
打印("反转:", 数据[::-1])
平方 = [x * x 循环 x 在 范围(5) 如果 x % 2 == 1]
打印("奇数平方:", 平方)
函数 斐波那契(n):
如果 n <= 1:
返回 n
返回 斐波那契(n - 1) + 斐波那契(n - 2)
打印("斐波那契(10):", 斐波那契(10))用 Node.js 直接运行,输出与 Python 目标一致:
$ cql runjs examples/js_demo.cql
计数器: 2 4
隔二取一: [0, 2, 4, 6]
反转: [6, 5, 4, 3, 2, 1, 0]
奇数平方: [1, 9, 25]
斐波那契(10): 55这个例子里的 数据[::2](隔二取一)与 数据[::-1](反转)演示了带步长的切片,转译器会自动映射为 .slice() 与 .filter() / .reverse() 的组合;推导式映射为 .map() / .filter();递归函数映射为普通 function。
18.4浏览器中使用
生成的 JS 依赖随包分发的 js_runtime.js(提供打印 / 长度 / 范围等中文内置函数与 12 个中文异常类,浏览器与 Node.js 双兼容)。在浏览器页面中,先引入 js_runtime.js,再引入转译后的程序即可:
<script src="js_runtime.js"></script>
<script src="your_program.js"></script>如果不想手工组织这两个文件,直接用 cql html 文件.cql 生成自包含 HTML 页面,运行时会被内联进页面,双击 HTML 文件即可在浏览器中运行,连 Node 都不需要。
18.5语义映射表
CQL 关键字与语法在 JS 目标中的映射规则如下,均由转译器自动完成:
| CQL | JavaScript |
|---|---|
| 如果 / 否则如果 / 否则 | if / else if / else |
| 循环 x 在 容器 | for (const x of 容器) |
| 函数 / 类 / 继承 | function / class / extends |
| 自身 / 超类 | this / super |
| 真 / 假 / 空 | true / false / null |
| 与 / 或 / 非 | && / || / ! |
| 等于 / 不等于 / 是 / 不是 | === / !== / === / !== |
| 整除 | Math.floor(a / b) |
| x 在 容器 / x 不在 容器 | 容器.includes(x) / !容器.includes(x) |
| 产量 | yield(函数自动变为 function*) |
| 捕获 异常 作为 e | catch(e) { if (e instanceof 异常) {...} } |
| 三引号字符串 | 模板字符串(反引号) |
| 列表 / 字典推导式 | .map() / .filter() / .flatMap() / Object.fromEntries() |
| 异步函数 / 等待 | async function / await(顶层等待自动包 async IIFE) |
此外,内置函数与容器方法在 JS 目标中也有对应实现:例如 地图 / 过滤器 / 归约 映射为 js_runtime.js 运行时函数(返回数组,归约支持初始值),排序(x, key=, reverse=)、求和(x, start=)、字符串表示(x) 等增强参数也由 js_runtime.js 完整实现,双目标输出逐行一致。
18.6中文方法名别名双目标支持
除 Python 风格的方法名(.append()、.keys()、.strip() 等)外,CQL 同时支持中文方法名,Python 与 JavaScript 两个目标都会自动转译:
数据 = [3, 1, 2]
数据.追加(4) # 等价于 数据.append(4)
数据.排序() # 等价于 数据.sort()
学生 = {"小明": 90}
打印(学生.键()) # 等价于 学生.keys()
集合甲 = 集合([1, 2, 3])
集合乙 = 集合([3, 4, 5])
打印(集合甲.并集(集合乙)) # 等价于 集合甲.union(集合乙)
打印(集合甲.交集(集合乙)) # 等价于 集合甲.intersection(集合乙)
打印(集合甲.差集(集合乙)) # 等价于 集合甲.difference(集合乙)
集合甲.添加(9) # 等价于 集合甲.add(9)
打印("ab".居中(5, "_")) # 等价于 "ab".center(5, "_")
打印("aBC".首字母大写()) # 等价于 "aBC".capitalize()
打印("123".是否数字()) # 等价于 "123".isdigit()
打印("pre_x".删除前缀("pre_")) # 等价于 "pre_x".removeprefix("pre_")两种目标的处理方式不同:
- Python 目标生成原生方法调用:
数据.append(4)、集合甲.union(集合乙)、"ab".center(5, "_")。 - JS 目标改写为 js_runtime.js 的运行时函数:
数组追加(数据, 4)、集合并集(集合甲, 集合乙)、字符串居中("ab", 5, "_")。
| 中文方法调用 | Python 目标 | JS 目标运行时函数 |
|---|---|---|
数据.追加(4) | 数据.append(4) | 数组追加(数据, 4) |
数据.排序() | 数据.sort() | 数组排序(数据) |
学生.键() | 学生.keys() | 对象键(学生) |
集合甲.并集(集合乙) | 集合甲.union(集合乙) | 集合并集(集合甲, 集合乙) |
"ab".居中(5, "_") | "ab".center(5, "_") | 字符串居中("ab", 5, "_") |
"aBC".首字母大写() | "aBC".capitalize() | 字符串首字母大写("aBC") |
"123".是否数字() | "123".isdigit() | 字符串是否数字("123") |
"pre_x".删除前缀("pre_") | "pre_x".removeprefix("pre_") | 改写为对应运行时函数 |
字符串增强方法(居中、左填充、右填充、首字母大写、每个词大写、左去空白、右去空白、是否数字、是否字母、是否空白、删除前缀、删除后缀,以及大小写切换、分区、右分区、行分割、零填充、出现次数、右查找、是否数字字母、是否大写、是否小写等)双目标均自动转译,语义与 Python 完全对齐,例如 .sort() 对数字按数值排序。
18.7注意事项
- 以数组 / 对象语义为准:JS 目标以数组 / 对象语义为准,与 Python 的差异(如
==变为===、整除向下取整)由转译器自动处理,代码无需改动。 - 依赖 js_runtime.js:转译出的
.js依赖随包分发的 js_runtime.js 提供中文内置函数与中文异常类;cql html会把运行时内联进页面。 - 暂不支持极少数特性:目前 JS 目标暂不支持的仅为元类等极少数高级特性,日常语法均可正常转译。
.js 时需先引入 js_runtime.js;Node 中运行则可用 cql runjs 自动完成。