CQL 教程/CQL 工具/JavaScript 目标

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 中运行,两个目标语义保持一致。
  • 语法覆盖完整:类、继承、异常、推导式、切片、生成器(产量)、异步(异步函数 / 等待)等都能自动转译。
提示:生成的自包含 HTML 页面不依赖任何外部网络资源,js_runtime.js 运行时会内联进页面,离线也能运行。

18.2转译命令

JavaScript 目标相关的命令都在 cql 这个命令行工具上,用法如下:

bash
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 runjscql html 内部都先执行 JS 转译,再分别交给 Node.js 与浏览器运行。想先看看生成的 JS 长什么样,用 --stdout 直接打印到终端最方便:

bash
$ cql js examples/js_demo.cql --stdout
// 生成的自包含 JavaScript 源码
// ...(省略)

18.3运行示例

examples/js_demo.cql 为例,它同时兼容 Python 与 JavaScript 两个目标,演示了类、切片、推导式与递归:

cql
# 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 目标一致:

bash
$ 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,再引入转译后的程序即可:

html
<script src="js_runtime.js"></script>
<script src="your_program.js"></script>

如果不想手工组织这两个文件,直接用 cql html 文件.cql 生成自包含 HTML 页面,运行时会被内联进页面,双击 HTML 文件即可在浏览器中运行,连 Node 都不需要。

18.5语义映射表

CQL 关键字与语法在 JS 目标中的映射规则如下,均由转译器自动完成:

CQLJavaScript
如果 / 否则如果 / 否则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*
捕获 异常 作为 ecatch(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 两个目标都会自动转译:

cql
数据 = [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 自动完成。