Skip to content

Commit ad46cfe

Browse files
committed
feat: HIC 语言通用化 —— while 循环 / break-continue / set 赋值 / say 输出 / let 局部变量 / add-sub-mul-div 简写 / 列表字典字面量 / 11 个内置函数
引擎改造(ide/android/windows 三端 hic.js 同步,新增 ~230 行): - tokenize 新增 [ ] { } , : 六种 token,支持列表 [a,b,c] 和字典 {k:v} 字面量 - evalExpr 扩展:primary() 新增列表/字典构造 + 内置函数调用 (id(args)) 分支 - addSub 字符串安全拼接;applyCmp 的 in 操作符支持 Array.indexOf / Object.in / 回退子串 - classify 新增 7 种节点:while / break / continue / set / let / opset(add|sub|mul|div) / say - parse 新增 while 块收集(collectUntil + 递归 build) - collectVars 新增 while 递归、let 编译期求值声明、set/opset 编译期先 eval - renderNodes 新增 ctx.breakFlag/continueFlag 传播模型;while 循环(编译期展开,50 次安全上限);set/opset/let/say/break/continue 全链路;for 循环内 break/continue 传播 - renderItems 新增 say 节点 → hic-body 文本输出 新增 11 个内置函数: len / range(a,b,step) / max / min / abs / round / int / float / str / type / contains 三端测试全部通过 (145/145);离线单文件重建 (155.7 KB / 档)
1 parent 3b8cfb4 commit ad46cfe

8 files changed

Lines changed: 2993 additions & 12 deletions

File tree

‎android/js/hic.js‎

Lines changed: 231 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@
8080
if (FULLWIDTH_OPS[c]) { out.push({ t: FULLWIDTH_OPS[c], v: FULLWIDTH_OPS[c] }); i++; continue; }
8181
if (c === "(") { out.push({ t: "(", v: c }); i++; continue; }
8282
if (c === ")") { out.push({ t: ")", v: c }); i++; continue; }
83+
/* ---- H1.00 扩展:列表/字典字面量 token ---- */
84+
if (c === "[") { out.push({ t: "[", v: c }); i++; continue; }
85+
if (c === "]") { out.push({ t: "]", v: c }); i++; continue; }
86+
if (c === "{") { out.push({ t: "{", v: c }); i++; continue; }
87+
if (c === "}") { out.push({ t: "}", v: c }); i++; continue; }
88+
if (c === ",") { out.push({ t: ",", v: c }); i++; continue; }
89+
if (c === ":") { out.push({ t: ":", v: c }); i++; continue; }
8390
if ("+-*/%".indexOf(c) >= 0) { out.push({ t: c, v: c }); i++; continue; }
8491
const two = expr.substr(i, 2);
8592
if (two === "==" || two === "!=" || two === "<=" || two === ">=") { out.push({ t: two, v: two }); i += 2; continue; }
@@ -113,7 +120,12 @@
113120
const sL = String(l), sR = String(r);
114121
if (op === "==") return numL && numR ? l === r : sL === sR;
115122
if (op === "!=") return numL && numR ? l !== r : sL !== sR;
116-
if (op === "in") return sR.indexOf(sL) >= 0; // 「x in container」:右侧含左侧
123+
/* ---- H1.00 扩展:列表/字典 in 操作符 ---- */
124+
if (op === "in") {
125+
if (Array.isArray(r)) return r.indexOf(l) >= 0;
126+
if (r && typeof r === "object") return l in r;
127+
return sR.indexOf(sL) >= 0; // 「x in container」:右侧含左侧(子串匹配)
128+
}
117129
const a = numL && numR ? l : sL;
118130
const b = numL && numR ? r : sR;
119131
if (op === "<") return a < b;
@@ -123,6 +135,58 @@
123135
return false;
124136
}
125137

138+
/* ---- H1.00 扩展:evalExpr 内置函数库 ---- */
139+
const BUILTINS = {
140+
len: function (x) {
141+
if (x == null) return 0;
142+
if (typeof x === "string") return x.length;
143+
if (Array.isArray(x)) return x.length;
144+
if (typeof x === "object") return Object.keys(x).length;
145+
return String(x).length;
146+
},
147+
range: function () {
148+
const args = [].slice.call(arguments);
149+
const arr = [];
150+
let start = 0, end = 0, step = 1;
151+
if (args.length === 1) { end = Number(args[0]); }
152+
else if (args.length === 2) { start = Number(args[0]); end = Number(args[1]); }
153+
else { start = Number(args[0]); end = Number(args[1]); step = Number(args[2]) || 1; }
154+
if (step > 0) { for (let v = start; v < end; v += step) arr.push(v); }
155+
else if (step < 0) { for (let v = start; v > end; v += step) arr.push(v); }
156+
return arr;
157+
},
158+
max: function () {
159+
const args = [].slice.call(arguments);
160+
if (!args.length) return 0;
161+
if (args.length === 1 && Array.isArray(args[0])) return Math.max.apply(null, args[0]);
162+
return Math.max.apply(null, args);
163+
},
164+
min: function () {
165+
const args = [].slice.call(arguments);
166+
if (!args.length) return 0;
167+
if (args.length === 1 && Array.isArray(args[0])) return Math.min.apply(null, args[0]);
168+
return Math.min.apply(null, args);
169+
},
170+
abs: function (x) { return Math.abs(Number(x)); },
171+
round: function (x) { return Math.round(Number(x)); },
172+
int: function (x) { return parseInt(x, 10); },
173+
float: function (x) { return parseFloat(x); },
174+
str: function (x) { return String(x == null ? "" : x); },
175+
type: function (x) {
176+
if (x === true || x === false) return "bool";
177+
if (Array.isArray(x)) return "list";
178+
if (typeof x === "object" && x !== null) return "dict";
179+
if (typeof x === "number") return "num";
180+
if (typeof x === "string") return "str";
181+
return "";
182+
},
183+
contains: function (s, sub) {
184+
if (typeof s !== "string" && !Array.isArray(s)) return false;
185+
if (typeof s === "string") return String(s).indexOf(String(sub)) >= 0;
186+
return s.indexOf(sub) >= 0;
187+
}
188+
};
189+
126190
function evalExpr(expr, vars) {
127191
const toks = tokenize(expr);
128192
let p = 0;
@@ -134,14 +198,62 @@
134198
if (typeof v === "number") return v !== 0;
135199
return String(v).length > 0;
136200
}
201+
/* ---- H1.00 扩展:primary 处理列表、字典、内置函数调用 ---- */
137202
function primary() {
138203
const t = next();
139204
if (!t) return "";
140205
if (t.t === "(") { const v = orExpr(); next(); return v; }
141206
if (t.t === "num") return t.v;
142207
if (t.t === "str") return t.v;
208+
if (t.t === "[") {
209+
// 列表字面量 [expr, expr, ...]
210+
const arr = [];
211+
if (!(peek() && peek().t === "]")) {
212+
arr.push(orExpr());
213+
while (peek() && peek().t === ",") { next(); arr.push(orExpr()); }
214+
}
215+
if (peek() && peek().t === "]") next();
216+
return arr;
217+
}
218+
if (t.t === "{") {
219+
// 字典字面量 { key: val, key: val }
220+
const obj = {};
221+
if (!(peek() && peek().t === "}")) {
222+
let keyTok = peek();
223+
if (keyTok && (keyTok.t === "str" || keyTok.t === "id" || keyTok.t === "num")) {
224+
next();
225+
const key = keyTok.t === "id" ? keyTok.v : keyTok.v;
226+
if (peek() && peek().t === ":") next();
227+
obj[key] = orExpr();
228+
}
229+
while (peek() && peek().t === ",") {
230+
next();
231+
keyTok = peek();
232+
if (!keyTok || keyTok.t === "}") break;
233+
if (keyTok.t === "str" || keyTok.t === "id" || keyTok.t === "num") {
234+
next();
235+
const key = keyTok.t === "id" ? keyTok.v : keyTok.v;
236+
if (peek() && peek().t === ":") next();
237+
obj[key] = orExpr();
238+
}
239+
}
240+
}
241+
if (peek() && peek().t === "}") next();
242+
return obj;
243+
}
143244
if (t.t === "id") {
144245
const w = t.v;
246+
// 内置函数调用:id(args)
247+
if (peek() && peek().t === "(" && BUILTINS[w]) {
248+
next(); // 消费 (
249+
const args = [];
250+
if (!(peek() && peek().t === ")")) {
251+
args.push(orExpr());
252+
while (peek() && peek().t === ",") { next(); args.push(orExpr()); }
253+
}
254+
if (peek() && peek().t === ")") next();
255+
try { return BUILTINS[w].apply(null, args); } catch (e) { return false; }
256+
}
145257
if (w === "true") return true;
146258
if (w === "false") return false;
147259
if (w === "None") return "";
@@ -163,11 +275,18 @@
163275
}
164276
return v;
165277
}
278+
/* ---- H1.00 扩展:字符串拼接(+ 操作数含字符串时用 String() 拼接)---- */
166279
function addSub() {
167280
let v = mulDiv();
168281
while (peek() && (peek().t === "+" || peek().t === "-")) {
169282
const op = next().t; const r = mulDiv();
170-
v = Number(v) + (op === "+" ? Number(r) : -Number(r));
283+
if (op === "+") {
284+
if (typeof v === "string" || typeof r === "string") v = String(v) + String(r);
285+
else v = Number(v) + Number(r);
286+
} else {
287+
if (typeof v === "string" || typeof r === "string") v = String(v) + String(r);
288+
else v = Number(v) - Number(r);
289+
}
171290
}
172291
return v;
173292
}
@@ -249,6 +368,25 @@
249368
// for 循环头:for 变量 in 迭代源:
250369
const form = line.match(/^for\s+([A-Za-z_\u4e00-\u9fff][A-Za-z0-9_\u4e00-\u9fff]*)\s+in\s+(.+?)\s*:\s*$/i);
251370
if (form) return { kind: "for", var: form[1], iter: form[2].trim() };
371+
/* ---- H1.00 扩展:新语句分类(while / break / continue / set / let / opset / say)---- */
372+
// while 条件: 循环头
373+
const whileM = line.match(/^while\s+(.+?)\s*:\s*$/i);
374+
if (whileM) return { kind: "while", cond: whileM[1].replace(/:\s*$/, "").trim() };
375+
// break / continue
376+
if (/^break\s*$/.test(line)) return { kind: "break" };
377+
if (/^continue\s*$/.test(line)) return { kind: "continue" };
378+
// set 名 = 表达式 — 运行时赋值
379+
const setM = line.match(/^set\s+([A-Za-z_\u4e00-\u9fff][A-Za-z0-9_\u4e00-\u9fff]*)\s*=\s*(.+?)\s*$/);
380+
if (setM) return { kind: "set", name: setM[1], expr: setM[2] };
381+
// let 名 be 值 — 局部临时变量
382+
const letM = line.match(/^let\s+([A-Za-z_\u4e00-\u9fff][A-Za-z0-9_\u4e00-\u9fff]*)\s+be\s+(.+?)\s*$/);
383+
if (letM) return { kind: "let", name: letM[1], value: letM[2] };
384+
// 简写运算:名 add/sub/mul/div 表达式
385+
const opsetM = line.match(/^([A-Za-z_\u4e00-\u9fff][A-Za-z0-9_\u4e00-\u9fff]*)\s+(add|sub|mul|div)\s+(.+)$/i);
386+
if (opsetM) return { kind: "opset", name: opsetM[1], op: opsetM[2].toLowerCase(), expr: opsetM[3].trim() };
387+
// say 内容 — 输出文本
388+
const sayM = line.match(/^say\s+(.*)$/);
389+
if (sayM) return { kind: "say", text: sayM[1].trim() };
252390
const cond = parseCondHeader(line);
253391
if (cond) return { kind: "cond", ...cond };
254392
// 导向 to / fr
@@ -365,6 +503,13 @@
365503
const bodyEndFor = collectUntil(i, end, indentGoal);
366504
out.push({ kind: "for", var: st.var, iter: st.iter, body: build(i, bodyEndFor, minIndent(i, bodyEndFor)) });
367505
i = bodyEndFor;
506+
/* ---- H1.00 扩展:while 块收集 ---- */
507+
} else if (st.kind === "while") {
508+
if (pendingIf) { out.push(pendingIf); pendingIf = null; }
509+
i++;
510+
const bodyEndW = collectUntil(i, end, indentGoal);
511+
out.push({ kind: "while", cond: st.cond, body: build(i, bodyEndW, minIndent(i, bodyEndW)) });
512+
i = bodyEndW;
368513
} else if (st.kind === "regionStart") {
369514
// 触发区域:cf NAME ... cf NAME stop。body 为区域内的代码块(点击/长按触发执行)
370515
if (pendingIf) { out.push(pendingIf); pendingIf = null; }
@@ -433,6 +578,35 @@
433578
collectVars(n.body, vars);
434579
n.chains.forEach(function (c) { collectVars(c.body, vars); });
435580
if (n.orphan) { /* orphan body already collected */ }
581+
/* ---- H1.00 扩展:while / let / set / opset 变量收集 ---- */
582+
} else if (n.kind === "while") {
583+
collectVars(n.body, vars);
584+
} else if (n.kind === "let") {
585+
let val = "";
586+
try { val = evalExpr(n.value, {}); } catch (e) { val = ""; }
587+
if (val !== "" && !isNaN(Number(val))) val = Number(val);
588+
vars[n.name] = { type: "ordinary", value: val, isImg: false };
589+
} else if (n.kind === "set" || n.kind === "opset") {
590+
let val = "";
591+
try {
592+
if (n.kind === "opset") {
593+
const cur = vars[n.name] ? vars[n.name].value : 0;
594+
const r = evalExpr(n.expr, vars);
595+
const cn = Number(cur), rn = Number(r);
596+
if (n.op === "add") val = cn + rn;
597+
else if (n.op === "sub") val = cn - rn;
598+
else if (n.op === "mul") val = cn * rn;
599+
else if (n.op === "div") val = cn / rn;
600+
else val = r;
601+
} else {
602+
val = evalExpr(n.expr, vars);
603+
}
604+
} catch (e) { val = ""; }
605+
if (val !== "" && typeof val !== "number" && !isNaN(Number(val))) val = Number(val);
606+
if (!vars[n.name]) vars[n.name] = { type: "ordinary", value: val, isImg: false };
607+
else vars[n.name].value = val;
608+
} else if (n.kind === "say" || n.kind === "break" || n.kind === "continue") {
609+
// 不产生变量
436610
}
437611
});
438612
return vars;
@@ -506,6 +680,8 @@
506680

507681
function renderNodes(nodes, vars, ctx, out) {
508682
nodes.forEach(function (n) {
683+
/* ---- H1.00 扩展:break/continue 传播 ---- */
684+
if (ctx && (ctx.breakFlag || ctx.continueFlag)) return;
509685
if (n.kind === "it") {
510686
out.push(n); // 声明保留(供变量面板)
511687
} else if (n.kind === "in") {
@@ -526,10 +702,13 @@
526702
// 逐个取值渲染循环体,循环变量按普通变量注入
527703
const vals = iterVals(n.iter, vars);
528704
vals.forEach(function (val) {
705+
if (ctx && ctx.breakFlag) { ctx.breakFlag = false; return; }
529706
const prev = vars[n.var];
530707
vars[n.var] = { type: "ordinary", value: val, isImg: false };
531708
renderNodes(n.body, vars, ctx, out);
532709
if (prev) vars[n.var] = prev; else delete vars[n.var];
710+
if (ctx && ctx.breakFlag) { ctx.breakFlag = false; return; }
711+
if (ctx && ctx.continueFlag) { ctx.continueFlag = false; /* continue → 下一轮 */ }
533712
});
534713
} else if (n.kind === "cond") {
535714
// 求值 if;未匹配则依次 orif;都不匹配取 else
@@ -559,6 +738,52 @@
559738
out.push({ kind: "cppBlock", lang: "cpp", code: n.code });
560739
} else if (n.kind === "textline") {
561740
out.push({ kind: "textline", text: n.text });
741+
/* ---- H1.00 扩展:break / continue / while / set / let / opset / say ---- */
742+
} else if (n.kind === "break") {
743+
if (ctx) ctx.breakFlag = true;
744+
return;
745+
} else if (n.kind === "continue") {
746+
if (ctx) ctx.continueFlag = true;
747+
return;
748+
} else if (n.kind === "while") {
749+
// 编译期 while 展开:条件动态求值,最多 50 次(安全上限,不是语言限制)
750+
let safety = 0;
751+
while (safety++ < 50) {
752+
if (ctx && ctx.breakFlag) { ctx.breakFlag = false; break; }
753+
let condVal = false;
754+
try { condVal = evalExpr(n.cond, vars); } catch (e) {}
755+
if (!condVal) break;
756+
renderNodes(n.body, vars, ctx, out);
757+
if (ctx && ctx.breakFlag) { ctx.breakFlag = false; break; }
758+
if (ctx && ctx.continueFlag) { ctx.continueFlag = false; /* 继续下一轮 */ }
759+
}
760+
} else if (n.kind === "set" || n.kind === "opset") {
761+
let val;
762+
try {
763+
if (n.kind === "opset") {
764+
const cur = vars[n.name] ? vars[n.name].value : 0;
765+
const r = evalExpr(n.expr, vars);
766+
const cn = Number(cur), rn = Number(r);
767+
if (n.op === "add") val = cn + rn;
768+
else if (n.op === "sub") val = cn - rn;
769+
else if (n.op === "mul") val = cn * rn;
770+
else if (n.op === "div") val = cn / rn;
771+
else val = r;
772+
} else {
773+
val = evalExpr(n.expr, vars);
774+
}
775+
} catch (e) { val = ""; }
776+
if (!vars[n.name]) vars[n.name] = { type: "ordinary", value: val, isImg: false };
777+
else vars[n.name].value = val;
778+
// set 本身不输出 HTML
779+
} else if (n.kind === "let") {
780+
let val;
781+
try { val = evalExpr(n.value, vars); } catch (e) { val = ""; }
782+
vars[n.name] = { type: "ordinary", value: val, isImg: false };
783+
// let 本身不输出 HTML
784+
} else if (n.kind === "say") {
785+
const text = interpolate(n.text, vars);
786+
out.push({ kind: "say", text: text });
562787
}
563788
});
564789
return out;
@@ -715,6 +940,10 @@
715940
if (it.kind === "textline") {
716941
return '<p class="hic-item hic-body">' + esc(it.text) + "</p>";
717942
}
943+
/* ---- H1.00 扩展:say 输出 ---- */
944+
if (it.kind === "say") {
945+
return '<p class="hic-item hic-body">' + esc(it.text) + "</p>";
946+
}
718947
return "";
719948
}).join("\n");
720949
}

0 commit comments

Comments
 (0)