|
1 | 1 | /* ============================================================ |
2 | | - * HC v0.02 —— HIC 语言引擎 |
| 2 | + * HC v0.021 —— HIC 语言引擎 |
3 | 3 | * 负责:HIC 词法/语法解析、HIC→HTML 转译、轻量 ZIP 写入、下载 |
4 | 4 | * 说明:本文件为纯逻辑,不依赖 DOM(localStorage/document), |
5 | 5 | * 唯一例外是 download()(仅浏览器调用);可用 Node 单元测试。 |
|
11 | 11 | })(typeof self !== 'undefined' ? self : null, function () { |
12 | 12 | "use strict"; |
13 | 13 |
|
14 | | - const APP = { version: "v0.02", name: "HiCode", lang: "HIC" }; |
| 14 | + const APP = { version: "v0.021", name: "HiCode", lang: "HIC" }; |
15 | 15 |
|
16 | 16 | /* ---------------- 工具 ---------------- */ |
17 | 17 | function esc(s) { |
|
25 | 25 | const NAME_RE = new RegExp("^[" + ID_START + "][" + ID_CHAR + "]*$"); |
26 | 26 | function isName(s) { return NAME_RE.test(String(s || "")); } |
27 | 27 | const PY_TYPES = ["int", "float", "str", "bool", "list", "dict", "tuple"]; |
| 28 | + // 全角/数学符号运算符 → 半角等价(人性化编译:兼容中文输入法全角符号、≥ ≤ ≠ 等) |
| 29 | + const FULLWIDTH_OPS = { |
| 30 | + "\uFF0B": "+", "\uFF0D": "-", "\u2212": "-", "\u00D7": "*", "\u00F7": "/", "\uFF05": "%", // + - − × ÷ % |
| 31 | + "\uFF1D": "==", "\uFF1C": "<", "\uFF1E": ">", // = < > |
| 32 | + "\u2265": ">=", "\u2264": "<=", "\u2260": "!=", // ≥ ≤ ≠ |
| 33 | + "\uFF08": "(", "\uFF09": ")" // ( ) |
| 34 | + }; |
28 | 35 |
|
29 | 36 | /* ---------------- 词法:表达式求值(安全,无 eval) ---------------- */ |
30 | 37 | function tokenize(expr) { |
31 | 38 | const out = []; let i = 0; const n = expr.length; |
32 | 39 | while (i < n) { |
33 | 40 | const c = expr[i]; |
34 | | - if (c === " " || c === "\t") { i++; continue; } |
| 41 | + if (c === " " || c === "\t" || c === "\u3000") { i++; continue; } |
| 42 | + if (FULLWIDTH_OPS[c]) { out.push({ t: FULLWIDTH_OPS[c], v: FULLWIDTH_OPS[c] }); i++; continue; } |
35 | 43 | if (c === "(") { out.push({ t: "(", v: c }); i++; continue; } |
36 | 44 | if (c === ")") { out.push({ t: ")", v: c }); i++; continue; } |
37 | 45 | if ("+-*/%".indexOf(c) >= 0) { out.push({ t: c, v: c }); i++; continue; } |
38 | 46 | const two = expr.substr(i, 2); |
39 | 47 | if (two === "==" || two === "!=" || two === "<=" || two === ">=") { out.push({ t: two, v: two }); i += 2; continue; } |
40 | 48 | if (c === "<" || c === ">") { out.push({ t: c, v: c }); i++; continue; } |
| 49 | + if (c === "=") { out.push({ t: "==", v: "==" }); i++; continue; } |
41 | 50 | if (c === '"' || c === "'") { |
42 | 51 | const q = c; let j = i + 1, s = ""; |
43 | 52 | while (j < n && expr[j] !== q) { s += expr[j]; j++; } |
|
409 | 418 | if (!it.cross) { |
410 | 419 | // 同项目内:目标页面在同项目 |
411 | 420 | const targetPage = it.toPage; |
412 | | - if (ctx.mode === "zip" || ctx.mode === "singlepage") { |
| 421 | + if (ctx.mode === "zip") { |
413 | 422 | const fn = ctx.projSlug + "-" + slugify(targetPage) + ".html"; |
414 | 423 | return { href: fn, label: targetPage }; |
415 | 424 | } |
| 425 | + if (ctx.mode === "singlepage") { |
| 426 | + // 单页导出不含其它页面,禁用跳转,避免生成不存在的 .html 造成 404 |
| 427 | + return { kind: "disabled" }; |
| 428 | + } |
416 | 429 | // 合并单 html:跳转同文档内区块 |
417 | 430 | const key = ctx.projSlug + "::" + targetPage; |
418 | 431 | return { href: "#" + key, sameDoc: true, pageKey: key, label: targetPage }; |
|
0 commit comments