|
1 | 1 | /* ============================================================ |
2 | | - * HC v2.00 —— HIC 语言引擎 |
| 2 | + * HC v3.01 —— 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: "v2.00", name: "HiCode", lang: "HIC" }; |
| 14 | + const APP = { version: "v3.01", name: "HiCode", lang: "HIC" }; |
15 | 15 |
|
16 | 16 | /* ---- 标准库(use 指令):原生/后端运行时可用模块白名单 ---- */ |
17 | 17 | // 说明:这些 Python 标准库模块在「网页/HTML 输出」中为可识别不报错的占位; |
|
193 | 193 | function classify(line) { |
194 | 194 | // 返回 {kind, ...} |
195 | 195 | if (!line || line.trim() === "" ) return { kind: "blank" }; |
| 196 | + // html 原生块:html:( 起始 / )end 结束(括号内为原样 HTML,HIC 不编译、直接注入最终导出) |
| 197 | + if (/^html\s*:\s*\(\s*$/i.test(line)) return { kind: "htmlBlockStart" }; |
| 198 | + if (/^\)\s*end\s*$/i.test(line)) return { kind: "htmlBlockEnd" }; |
196 | 199 | // 标准库导入:use 模块[, 模块...] |
197 | 200 | const usem = line.match(/^use\s+(.+)$/i); |
198 | 201 | if (usem) return { kind: "use", modules: usem[1].split(/[,\uFF0C\s]+/).filter(Boolean).map(function (m) { return m.trim(); }) }; |
|
273 | 276 | } |
274 | 277 |
|
275 | 278 | /* ---------------- 缩进块解析 ---------------- */ |
| 279 | + // html:( ... )end 块内容去缩进:去掉与块起始行相同的最多 base 个前导空格/制表符 |
| 280 | + function dedentHtml(lines, base) { |
| 281 | + return lines.map(function (l) { |
| 282 | + if (l.trim() === "") return ""; |
| 283 | + let n = 0; |
| 284 | + for (let k = 0; k < l.length; k++) { |
| 285 | + if (l[k] === " ") n += 1; |
| 286 | + else if (l[k] === "\t") n += 2; |
| 287 | + else break; |
| 288 | + } |
| 289 | + const cut = n > base ? base : n; |
| 290 | + return l.slice(cut); |
| 291 | + }); |
| 292 | + } |
| 293 | + |
276 | 294 | function parse(code) { |
277 | | - const ls = String(code || "").split("\n").map(function (raw) { |
| 295 | + // 先把多行 html:( ... )end 块折叠为单个原子节点;括号内为原样 HTML,HIC 不编译、按声明顺序直接注入 |
| 296 | + const rawLines = String(code || "").split("\n"); |
| 297 | + const ls = []; |
| 298 | + for (let li = 0; li < rawLines.length; li++) { |
| 299 | + const raw = rawLines[li]; |
278 | 300 | const mt = raw.match(/^[ \t]*/)[0]; |
| 301 | + const lt = raw.replace(/(^|[ \t])#.*$/, "").trim(); |
| 302 | + if (/^html\s*:\s*\(\s*$/i.test(lt)) { |
| 303 | + const baseIndent = mt.replace(/\t/g, " ").length; |
| 304 | + const inner = []; |
| 305 | + let j = li + 1, closed = false; |
| 306 | + for (; j < rawLines.length; j++) { |
| 307 | + if (/^\)\s*end\s*$/i.test(rawLines[j].replace(/(^|[ \t])#.*$/, "").trim())) { closed = true; break; } |
| 308 | + inner.push(rawLines[j]); |
| 309 | + } |
| 310 | + ls.push({ indent: baseIndent, kind: "htmlBlock", html: dedentHtml(inner, baseIndent).join("\n") }); |
| 311 | + li = closed ? j : (j - 1 > li ? j - 1 : li); |
| 312 | + continue; |
| 313 | + } |
| 314 | + if (!lt) continue; // 纯空白/注释行跳过 |
279 | 315 | const indent = mt.replace(/\t/g, " ").length; |
280 | | - // 代码备注:# 整行注释,或行内空白后的 # 到行尾为注释(Python 风格) |
281 | | - const line = raw.replace(/(^|[ \t])#.*$/, "").trim(); |
282 | | - return { indent, line }; |
283 | | - }).filter(function (x) { return x.line; }); |
| 316 | + ls.push({ indent, line: lt }); |
| 317 | + } |
284 | 318 |
|
285 | 319 | // 取 [start,end) 范围内行缩进的最小值(子块的实际根缩进) |
286 | 320 | function minIndent(start, end) { |
|
298 | 332 | const cur = ls[i]; |
299 | 333 | if (cur.indent < indentGoal) break; // 退回上层 |
300 | 334 | if (cur.indent > indentGoal) { i++; continue; } // 更深缩进由父层子块统一处理 |
| 335 | + if (cur.kind === "htmlBlock") { // 原样 HTML 块:原子节点,按声明顺序放置 |
| 336 | + if (pendingIf) { out.push(pendingIf); pendingIf = null; } |
| 337 | + out.push(cur); |
| 338 | + i++; |
| 339 | + continue; |
| 340 | + } |
301 | 341 | const st = classify(cur.line); |
302 | 342 | if (st.kind === "cond" && st.type === "if") { |
303 | 343 | if (pendingIf) out.push(pendingIf); |
|
495 | 535 | } else if (n.kind === "region") { |
496 | 536 | // 触发区域:渲染为可点击/长按的色块,其 body 子项在执行时展开显示 |
497 | 537 | out.push({ kind: "region", name: n.name, bodyItems: renderNodes(n.body, vars, ctx, []) }); |
| 538 | + } else if (n.kind === "htmlBlock") { |
| 539 | + // 原样 HTML:不编译,直接注入最终导出 |
| 540 | + out.push({ kind: "htmlBlock", html: n.html }); |
498 | 541 | } else if (n.kind === "textline") { |
499 | 542 | out.push({ kind: "textline", text: n.text }); |
500 | 543 | } |
|
610 | 653 | '<button type="button" class="hic-region-btn" data-name="' + esc(it.name) + '">' + esc(it.name) + ' 触发区域</button>' + |
611 | 654 | '<div class="hic-region-body" hidden>' + inner + "</div></div>"; |
612 | 655 | } |
| 656 | + if (it.kind === "htmlBlock") { |
| 657 | + // 原样 HTML:按声明顺序原样注入最终导出(不做转义与编译) |
| 658 | + return it.html; |
| 659 | + } |
613 | 660 | if (it.kind === "display" && it.mode === "point") { |
614 | 661 | const x = Number(it.x) || 0, y = Number(it.y) || 0; |
615 | 662 | const src = it.value ? String(it.value) : ""; |
|
0 commit comments