check variable define

This commit is contained in:
codetypes committed 2025-03-18 00:34:45 +08:00
1 parent deaa12ff4b
commit e8ae056461
7 files changed
+158 -36

No files matched your search

+1
View File
@@ -116,6 +116,7 @@
"node.subtree": "Subtree", "node.subtree": "Subtree",
"node.subtreePathError": "You should save the subtree inside the current workspace", "node.subtreePathError": "You should save the subtree inside the current workspace",
"node.type": "Node type", "node.type": "Node type",
"node.undefinedVariable": "Variable '{{variable}}' is not defined",
"node.unknown.desc": "New node" "node.unknown.desc": "New node"
} }
+1
View File
@@ -116,6 +116,7 @@
"node.subtree": "子树", "node.subtree": "子树",
"node.subtreePathError": "子树必须保存在当前工作区的目录内", "node.subtreePathError": "子树必须保存在当前工作区的目录内",
"node.type": "节点类型", "node.type": "节点类型",
"node.undefinedVariable": "变量 “{{variable}}” 未定义",
"node.unknown.desc": "新建节点" "node.unknown.desc": "新建节点"
} }
+11 -3
View File
@@ -15,6 +15,14 @@
{ {
"name": "target", "name": "target",
"desc": "目标" "desc": "目标"
},
{
"name": "target1",
"desc": "其它目标"
},
{
"name": "test",
"desc": "测试"
} }
] ]
}, },
@@ -32,21 +40,21 @@
"name": "FindEnemy", "name": "FindEnemy",
"args": {}, "args": {},
"output": [ "output": [
"target" "target1"
] ]
}, },
{ {
"id": 5, "id": 5,
"name": "Attack", "name": "Attack",
"input": [ "input": [
"target" "target1"
] ]
}, },
{ {
"id": 6, "id": 6,
"name": "Check", "name": "Check",
"args": { "args": {
"value": "target.x > 0" "value": "target1.x > 0"
} }
}, },
{ {
+49 -5
View File
@@ -21,8 +21,9 @@ import {
EditTree, EditTree,
useWorkspace, useWorkspace,
} from "../contexts/workspace-context"; } from "../contexts/workspace-context";
import { isExprType, NodeModel, TreeGraphData, TreeModel } from "../misc/b3type"; import { ImportDef, isExprType, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
import * as b3util from "../misc/b3util"; import * as b3util from "../misc/b3util";
import { loadSubtreeVarDef, loadVarDef } from "../misc/b3util";
import { message } from "../misc/hooks"; import { message } from "../misc/hooks";
import i18n from "../misc/i18n"; import i18n from "../misc/i18n";
import { Hotkey, isMacos, useKeyDown } from "../misc/keys"; import { Hotkey, isMacos, useKeyDown } from "../misc/keys";
@@ -47,6 +48,13 @@ interface FilterOption {
isFocused: boolean; isFocused: boolean;
} }
const collectVarDefs = (editor: EditorStore) => {
const vars: VarDef[] = editor.declare.vars.slice();
loadVarDef(editor.declare.imports).forEach((v) => vars.push(v));
loadSubtreeVarDef(editor.data).forEach((v) => vars.push(v));
return vars;
};
const createTreeData = (node: NodeModel, parent?: string) => { const createTreeData = (node: NodeModel, parent?: string) => {
return b3util.createTreeData(node, parent, calcTreeDataSize); return b3util.createTreeData(node, parent, calcTreeDataSize);
}; };
@@ -60,6 +68,42 @@ const refreshTreeData = (data: TreeGraphData) => {
} }
}; };
const isTreeUpdated = (editor: EditorStore, editTree: EditTree) => {
const data = editTree.data;
if (
editor.firstid !== data.firstid ||
editor.export !== data.export ||
editor.name !== data.name ||
editor.desc !== data.desc
) {
return true;
}
for (let i = 0; i < Math.max(editor.declare.vars.length, data.declare.vars.length); i++) {
const v1: VarDef | undefined = editor.declare.vars[i];
const v2: VarDef | undefined = data.declare.vars[i];
if (v1?.name !== v2?.name || v1?.desc !== v2?.desc) {
return true;
}
}
for (let i = 0; i < Math.max(editor.group.length, data.group.length); i++) {
if (editor.group[i] !== data.group[i]) {
return true;
}
}
for (let i = 0; i < Math.max(editor.declare.imports.length, data.declare.imports.length); i++) {
const v1: ImportDef | undefined = editor.declare.imports[i];
const v2: ImportDef | undefined = data.declare.imports[i];
if (v1?.path !== v2?.path) {
return true;
}
}
return false;
};
const isSubtreeUpdated = (data: TreeGraphData) => { const isSubtreeUpdated = (data: TreeGraphData) => {
if (data.path) { if (data.path) {
try { try {
@@ -491,17 +535,16 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
}; };
const updateTree = (editTree: EditTree) => { const updateTree = (editTree: EditTree) => {
if (editor.desc !== editTree.data.desc || editor.export !== editTree.data.export) { if (isTreeUpdated(editor, editTree)) {
editor.desc = editTree.data.desc || ""; editor.desc = editTree.data.desc || "";
editor.export = editTree.data.export !== false; editor.export = editTree.data.export !== false;
editor.group = editTree.data.group || []; editor.group = editTree.data.group || [];
b3util.updateUsingGroups(editor.group);
if (editor.data.firstid !== editTree.data.firstid) {
editor.firstid = editTree.data.firstid ?? 1; editor.firstid = editTree.data.firstid ?? 1;
editor.declare.vars = editTree.data.declare.vars || []; editor.declare.vars = editTree.data.declare.vars || [];
editor.declare.imports = editTree.data.declare.imports || []; editor.declare.imports = editTree.data.declare.imports || [];
b3util.updateUsingGroups(editor.group);
b3util.updateUsingVars(collectVarDefs(editor));
refresh(); refresh();
}
onChange(); onChange();
} }
}; };
@@ -1207,6 +1250,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
if (workspace.editing === editor) { if (workspace.editing === editor) {
checkSubtree(); checkSubtree();
b3util.updateUsingGroups(editor.group); b3util.updateUsingGroups(editor.group);
b3util.updateUsingVars(collectVarDefs(editor));
} }
}, [workspace.editing]); }, [workspace.editing]);
+63 -5
View File
@@ -43,7 +43,9 @@ import {
isValidVariableName, isValidVariableName,
isVariadic, isVariadic,
loadVarDef, loadVarDef,
parseExpr,
usingGroups, usingGroups,
usingVars,
} from "../misc/b3util"; } from "../misc/b3util";
import { Hotkey, isMacos } from "../misc/keys"; import { Hotkey, isMacos } from "../misc/keys";
import { mergeClassNames } from "../misc/util"; import { mergeClassNames } from "../misc/util";
@@ -108,7 +110,7 @@ const TreeInspector: FC = () => {
workspace.editing?.dispatch("updateTree", { workspace.editing?.dispatch("updateTree", {
data: { data: {
name: values.name, name: values.name,
desc: values.desc || undefined, desc: values.desc,
export: values.export, export: values.export,
firstid: Number(values.firstid), firstid: Number(values.firstid),
group: (values.group as GroupDef[]) group: (values.group as GroupDef[])
@@ -568,6 +570,7 @@ const NodeInspector: FC = () => {
// auto complete for input and output // auto complete for input and output
const inoutVarOptions = useMemo(() => { const inoutVarOptions = useMemo(() => {
const options: OptionType[] = []; const options: OptionType[] = [];
const filter: Record<string, boolean> = {};
const collect = (node?: TreeGraphData) => { const collect = (node?: TreeGraphData) => {
if (node) { if (node) {
node.input?.forEach((v, i) => { node.input?.forEach((v, i) => {
@@ -578,8 +581,9 @@ const NodeInspector: FC = () => {
} else { } else {
desc = inputDef?.[i] ?? "<unknown>"; desc = inputDef?.[i] ?? "<unknown>";
} }
if (v && !options.find((option) => option.value === v)) { if (!filter[v]) {
options.push({ label: `${v}(${desc})`, value: v }); options.push({ label: `${v}(${desc})`, value: v });
filter[v] = true;
} }
}); });
node.output?.forEach((v, i) => { node.output?.forEach((v, i) => {
@@ -590,16 +594,26 @@ const NodeInspector: FC = () => {
} else { } else {
desc = outputDef?.[i] ?? "<unknown>"; desc = outputDef?.[i] ?? "<unknown>";
} }
if (v && !options.find((option) => option.value === v)) { if (!filter[v]) {
options.push({ label: `${v}(${desc})`, value: v }); options.push({ label: `${v}(${desc})`, value: v });
filter[v] = true;
} }
}); });
node.children?.forEach((child) => collect(child)); node.children?.forEach((child) => collect(child));
} }
}; };
if (usingVars) {
Object.values(usingVars ?? {}).forEach((v) => {
if (!filter[v.name]) {
options.push({ label: `${v.name}(${v.desc})`, value: v.name });
filter[v.name] = true;
}
});
} else {
collect(workspace.editing?.data); collect(workspace.editing?.data);
}
return options; return options;
}, [workspace.editing]); }, [workspace.editing, usingVars]);
// auto complete for subtree // auto complete for subtree
const subtreeOptions = useMemo(() => { const subtreeOptions = useMemo(() => {
@@ -753,7 +767,7 @@ const NodeInspector: FC = () => {
rules={[ rules={[
{ {
validator() { validator() {
if (def.group && !usingGroups[def.group]) { if (def.group && usingGroups && !usingGroups[def.group]) {
return Promise.reject(new Error(t("node.invalidGroup", { group: def.group }))); return Promise.reject(new Error(t("node.invalidGroup", { group: def.group })));
} }
return Promise.resolve(); return Promise.resolve();
@@ -833,6 +847,13 @@ const NodeInspector: FC = () => {
rules={[ rules={[
{ {
validator(_, value) { validator(_, value) {
if (value && usingVars && !usingVars[value]) {
return Promise.reject(
new Error(
t("node.undefinedVariable", { variable: value })
)
);
}
if (value && !isValidVariableName(value)) { if (value && !isValidVariableName(value)) {
return Promise.reject( return Promise.reject(
new Error(t("node.invalidVariableName")) new Error(t("node.invalidVariableName"))
@@ -895,6 +916,11 @@ const NodeInspector: FC = () => {
{ required, message: t("fieldRequired", { field: desc }) }, { required, message: t("fieldRequired", { field: desc }) },
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({ ({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
validator(_, value) { validator(_, value) {
if (value && usingVars && !usingVars[value]) {
return Promise.reject(
new Error(t("node.undefinedVariable", { variable: value }))
);
}
if (value && !isValidVariableName(value)) { if (value && !isValidVariableName(value)) {
return Promise.reject(new Error(t("node.invalidVariableName"))); return Promise.reject(new Error(t("node.invalidVariableName")));
} }
@@ -975,6 +1001,17 @@ const NodeInspector: FC = () => {
}, },
() => ({ () => ({
validator(_, value) { validator(_, value) {
if (usingVars && isExprType(type) && value) {
for (const v of parseExpr(value)) {
if (!usingVars![v]) {
return Promise.reject(
new Error(
t("node.undefinedVariable", { variable: v })
)
);
}
}
}
if (isJsonType(type)) { if (isJsonType(type)) {
try { try {
if (value !== "null") { if (value !== "null") {
@@ -1088,6 +1125,15 @@ const NodeInspector: FC = () => {
{ required, message: t("fieldRequired", { field: arg.desc }) }, { required, message: t("fieldRequired", { field: arg.desc }) },
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({ ({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
validator(_, value) { validator(_, value) {
if (usingVars && isExprType(type) && value) {
for (const v of parseExpr(value)) {
if (!usingVars![v]) {
return Promise.reject(
new Error(t("node.undefinedVariable", { variable: v }))
);
}
}
}
if (value && isJsonType(type)) { if (value && isJsonType(type)) {
try { try {
if (value !== "null") { if (value !== "null") {
@@ -1204,6 +1250,13 @@ const NodeInspector: FC = () => {
rules={[ rules={[
{ {
validator(_, value) { validator(_, value) {
if (value && usingVars && !usingVars[value]) {
return Promise.reject(
new Error(
t("node.undefinedVariable", { variable: value })
)
);
}
if (value && !isValidVariableName(value)) { if (value && !isValidVariableName(value)) {
return Promise.reject( return Promise.reject(
new Error(t("node.invalidVariableName")) new Error(t("node.invalidVariableName"))
@@ -1266,6 +1319,11 @@ const NodeInspector: FC = () => {
{ required, message: t("fieldRequired", { field: desc }) }, { required, message: t("fieldRequired", { field: desc }) },
{ {
validator(_, value) { validator(_, value) {
if (value && usingVars && !usingVars[value]) {
return Promise.reject(
new Error(t("node.undefinedVariable", { variable: value }))
);
}
if (value && !isValidVariableName(value)) { if (value && !isValidVariableName(value)) {
return Promise.reject(new Error(t("node.invalidVariableName"))); return Promise.reject(new Error(t("node.invalidVariableName")));
} }
+12 -3
View File
@@ -1,7 +1,7 @@
import G6 from "@antv/g6"; import G6 from "@antv/g6";
import { useWorkspace } from "../contexts/workspace-context"; import { useWorkspace } from "../contexts/workspace-context";
import { TreeGraphData, getNodeType } from "../misc/b3type"; import { TreeGraphData, getNodeType, isExprType } from "../misc/b3type";
import { checkTreeData, nodeDefs, usingGroups } from "../misc/b3util"; import { checkTreeData, nodeDefs, parseExpr, usingGroups, usingVars } from "../misc/b3util";
import i18n from "../misc/i18n"; import i18n from "../misc/i18n";
import { isMacos } from "../misc/keys"; import { isMacos } from "../misc/keys";
@@ -145,11 +145,20 @@ G6.registerNode(
const nodeDef = nodeDefs.get(data.name); const nodeDef = nodeDefs.get(data.name);
let classify = getNodeType(nodeDef); let classify = getNodeType(nodeDef);
let color = nodeDef.color || NODE_COLORS[classify] || NODE_COLORS["Other"]; let color = nodeDef.color || NODE_COLORS[classify] || NODE_COLORS["Other"];
if ( if (
!nodeDefs.has(data.name) || !nodeDefs.has(data.name) ||
(data.path && !data.children?.length) || (data.path && !data.children?.length) ||
!checkTreeData(data) || !checkTreeData(data) ||
(nodeDef.group && !usingGroups[nodeDef.group]) (nodeDef.group && usingGroups && !usingGroups[nodeDef.group]) ||
(usingVars && data.input?.some((v) => !usingVars![v])) ||
(usingVars && data.output?.some((v) => !usingVars![v])) ||
(usingVars &&
nodeDef.args?.some(
(v) =>
isExprType(v.type) &&
parseExpr(data.args?.[v.name] ?? "").some((vv) => !usingVars![vv])
))
) { ) {
classify = "Error"; classify = "Error";
color = NODE_COLORS[classify]; color = NODE_COLORS[classify];
+21 -20
View File
@@ -26,10 +26,10 @@ export class NodeDefs extends Map<string, NodeDef> {
export let nodeDefs: NodeDefs = new NodeDefs(); export let nodeDefs: NodeDefs = new NodeDefs();
export let groupDefs: string[] = []; export let groupDefs: string[] = [];
export let usingGroups: Record<string, boolean> | null = null;
export let usingVars: Record<string, VarDef> | null = null;
export let usingGroups: Record<string, boolean> = {}; const parsedExprs: Record<string, string[]> = {};
export let usingVars: Record<string, boolean> = {};
let workdir: string = ""; let workdir: string = "";
let alertError: (msg: string, duration?: number) => void = () => {}; let alertError: (msg: string, duration?: number) => void = () => {};
@@ -58,16 +58,18 @@ export const initWorkdir = (path: string, handler: typeof alertError) => {
}; };
export const updateUsingGroups = (group: string[]) => { export const updateUsingGroups = (group: string[]) => {
usingGroups = {}; usingGroups = null;
for (const g of group) { for (const g of group) {
usingGroups ??= {};
usingGroups[g] = true; usingGroups[g] = true;
} }
}; };
export const updateUsingVars = (vars: VarDef[]) => { export const updateUsingVars = (vars: VarDef[]) => {
usingVars = {}; usingVars = null;
for (const v of vars) { for (const v of vars) {
usingVars[v.name] = true; usingVars ??= {};
usingVars[v.name] = v;
} }
}; };
@@ -711,6 +713,7 @@ export const isTreeFile = (path: string) => {
}; };
export const loadVarDef = (list: ImportDef[]) => { export const loadVarDef = (list: ImportDef[]) => {
const all: Set<VarDef> = new Set();
for (const entry of list) { for (const entry of list) {
// TODO: check file date // TODO: check file date
// TODO: use cache // TODO: use cache
@@ -743,7 +746,9 @@ export const loadVarDef = (list: ImportDef[]) => {
return false; return false;
}) })
.sort((a, b) => a.name.localeCompare(b.name)); .sort((a, b) => a.name.localeCompare(b.name));
entry.vars.forEach((v) => all.add(v));
} }
return Array.from(all);
}; };
export const loadSubtreeVarDef = (data: TreeGraphData) => { export const loadSubtreeVarDef = (data: TreeGraphData) => {
@@ -757,21 +762,17 @@ export const loadSubtreeVarDef = (data: TreeGraphData) => {
} }
}; };
traverse(data); traverse(data);
loadVarDef(list); return loadVarDef(list);
const filter: Map<string, boolean> = new Map();
const result: VarDef[] = [];
list.forEach((entry) => {
entry.vars.forEach((v) => {
if (!filter.has(v.name)) {
filter.set(v.name, true);
result.push(v);
}
});
});
return result;
}; };
export const parseExpr = (expr: string) => { export const parseExpr = (expr: string) => {
const result = expr.split(/[^a-zA-Z0-9_.]/); if (parsedExprs[expr]) {
return result.map((v) => v.split(".")[0]).filter((v) => v); return parsedExprs[expr];
}
const result = expr
.split(/[^a-zA-Z0-9_.]/)
.map((v) => v.split(".")[0])
.filter((v) => isValidVariableName(v));
parsedExprs[expr] = result;
return result;
}; };