refactor(AST): 两张 target 表合成一张,加节点类型漏配在结构上不再可能
契约的 AST_NODE_TARGETS_BY_LANGUAGE 是 target → 中文名,judge/ast.ts 的 mappings 是
target → tree-sitter 节点类型,同一批键分在两个包里,靠一句「两边必须同增同减」的注释
维持。只加一边是静默错判:老师给 C 题选到只有 Python 有的 list_comprehension,判题机
拿裸名去比节点类型,C 的语法树里永远不存在它,于是「必须使用列表推导式」永远失败、
「不能使用 f-string」永远通过,两头都不报错,只有学生受着。
现在一个 target 一条 { label, node }:label 给后台下拉和题目页,node 给判题机。加 target
而漏配节点类型在结构上就不可能了。judge/ast.ts 的 mappings 整张删掉,解析统一走契约的
astTargetNodeType()(节点查 node,运算符查运算符表 —— 那张表的值本身就是要比的 token,
判题机原来抄的 and→&& 三条取值逐个相同,纯属重复)。
顺带把同一份数据的四份拷贝收成一份:C 的 14 条原来在契约和判题机里各抄了两遍
(C 一份、C++ 一份),现在 C++ 逐条引用 C_NODE_TARGETS;运算符表的 C++ 改成
{ ...C_OPERATOR_TARGETS, "<<", ">>" }。C++ 那几条仍逐条列出而不是 spread,是为了保住
下拉框的显示顺序(C++ 独有的几条插在中间)。
## 验证
行为零变化,是逐个 target 机械比对过的:把 HEAD 版的两张表原样取出来,对三种语言的
全部 target 比对「label / 运算符文案 / tree-sitter 解析结果 / 下拉框顺序」四项 ——
C 37 个、C++ 47 个、Python3 43 个,全部一致,键集与顺序也一致。
实跑:给题目 1004 配两条 Python3 规则(必须有 for 循环、不能用 f-string),交一发没有
for 循环的正确答案,判成 AST_CHECK_FAILED(10),statistic_info.ast_results 为
「必须使用 for 循环 / 不通过」「不能使用 f-string / 通过」—— label 与 node 两半都走到了。
再交一发带 for 循环的,判成 ACCEPTED(0)。
tsc、vue-tsc、vite build、单二进制编译均通过。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012j1vgeDqay8wKCh8dPgPcH
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
astNodeLabel,
|
||||
astOperatorLabel,
|
||||
astRuleIsMeaningful,
|
||||
astTargetNodeType,
|
||||
astRuleSchema,
|
||||
AST_SUPPORTED_LANGUAGES,
|
||||
type AstRequirement,
|
||||
@@ -31,91 +32,11 @@ export interface AstResult {
|
||||
actual?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* target → tree-sitter 节点类型。恒等的条目(`+`、`==` 这些运算符)不列,
|
||||
* 走 `mapping[target] ?? target` 回落。
|
||||
*
|
||||
* **这里的键集是契约 AST_NODE_TARGETS_BY_LANGUAGE 的另一半**,两边必须同增同减:
|
||||
* 那边决定后台下拉能选什么,这边决定判题机认得什么。只加一边就是静默错判。
|
||||
*/
|
||||
const mappings: Record<string, Record<string, string>> = {
|
||||
C: {
|
||||
for_loop: "for_statement",
|
||||
while_loop: "while_statement",
|
||||
do_while: "do_statement",
|
||||
if_statement: "if_statement",
|
||||
else_clause: "else_clause",
|
||||
break: "break_statement",
|
||||
continue: "continue_statement",
|
||||
function_definition: "function_definition",
|
||||
return: "return_statement",
|
||||
switch_statement: "switch_statement",
|
||||
case_statement: "case_statement",
|
||||
assignment: "assignment_expression",
|
||||
struct: "struct_specifier",
|
||||
include: "preproc_include",
|
||||
and: "&&",
|
||||
or: "||",
|
||||
not: "!",
|
||||
},
|
||||
"C++": {
|
||||
// C 的那 14 条原样通用(tree-sitter-cpp 继承 tree-sitter-c 的语法)
|
||||
for_loop: "for_statement",
|
||||
while_loop: "while_statement",
|
||||
do_while: "do_statement",
|
||||
if_statement: "if_statement",
|
||||
else_clause: "else_clause",
|
||||
break: "break_statement",
|
||||
continue: "continue_statement",
|
||||
function_definition: "function_definition",
|
||||
return: "return_statement",
|
||||
switch_statement: "switch_statement",
|
||||
case_statement: "case_statement",
|
||||
assignment: "assignment_expression",
|
||||
struct: "struct_specifier",
|
||||
include: "preproc_include",
|
||||
// C++ 独有
|
||||
range_for_loop: "for_range_loop",
|
||||
class_definition: "class_specifier",
|
||||
try_except: "try_statement",
|
||||
throw: "throw_statement",
|
||||
namespace: "namespace_definition",
|
||||
template: "template_declaration",
|
||||
lambda: "lambda_expression",
|
||||
using: "using_declaration",
|
||||
and: "&&",
|
||||
or: "||",
|
||||
not: "!",
|
||||
},
|
||||
Python3: {
|
||||
for_loop: "for_statement",
|
||||
while_loop: "while_statement",
|
||||
if_statement: "if_statement",
|
||||
else_clause: "else_clause",
|
||||
elif_clause: "elif_clause",
|
||||
break: "break_statement",
|
||||
continue: "continue_statement",
|
||||
function_definition: "function_definition",
|
||||
return: "return_statement",
|
||||
try_except: "try_statement",
|
||||
with_statement: "with_statement",
|
||||
list_comprehension: "list_comprehension",
|
||||
list_literal: "list",
|
||||
dict_literal: "dictionary",
|
||||
set_literal: "set",
|
||||
f_string: "format_string",
|
||||
import: "import_statement",
|
||||
import_from: "import_from_statement",
|
||||
assignment: "assignment",
|
||||
class_definition: "class_definition",
|
||||
},
|
||||
}
|
||||
|
||||
let initPromise: Promise<void> | undefined
|
||||
const languages = new Map<string, Language>()
|
||||
|
||||
async function loadLanguage(language: string) {
|
||||
if (!(language in mappings)) return null
|
||||
if (!AST_SUPPORTED_LANGUAGES.includes(language)) return null
|
||||
// locateFile 指到内嵌的 tree-sitter.wasm:emscripten 默认按脚本所在目录找,
|
||||
// 单二进制里那个目录是 /$bunfs/root,它自己找不着
|
||||
if (!initPromise) initPromise = Parser.init({ locateFile: () => treeSitterWasmPath })
|
||||
@@ -240,7 +161,7 @@ export function astRequirements(value: unknown): AstRequirements | null {
|
||||
/**
|
||||
* AST 规则的语义校验。zod 只管形状(engine 在枚举里、min 是整数),管不了
|
||||
* 「给 C 题选了只有 Python 才有的 list_comprehension」这类组合 —— 那种规则存得进去,
|
||||
* 判题时 `mapping[target] ?? target` 拿裸名去比节点类型,永远失败或永远通过,
|
||||
* 判题时 astTargetNodeType() 找不到就回落成裸名去比节点类型,永远失败或永远通过,
|
||||
* 两头都不报错,只有学生受着。
|
||||
*
|
||||
* 放这儿而不是 astRulesSchema 的 refine 上:那个 schema 同时用于**读**后台题目详情,
|
||||
@@ -345,14 +266,9 @@ function methodCalls(root: Node, target: string, language: string) {
|
||||
})
|
||||
}
|
||||
|
||||
function evaluateRule(
|
||||
root: Node,
|
||||
rule: AstRule,
|
||||
language: string,
|
||||
mapping: Record<string, string>,
|
||||
): AstResult | null {
|
||||
function evaluateRule(root: Node, rule: AstRule, language: string): AstResult | null {
|
||||
const target = rule.target ?? ""
|
||||
const nodeType = mapping[target] ?? target
|
||||
const nodeType = astTargetNodeType(target, language)
|
||||
|
||||
switch (rule.engine) {
|
||||
case "must_exist_node":
|
||||
@@ -409,8 +325,8 @@ function evaluateRule(
|
||||
case "must_have_nesting": {
|
||||
const outer = rule.outer ?? ""
|
||||
const inner = rule.inner ?? ""
|
||||
const outerType = mapping[outer] ?? outer
|
||||
const innerType = mapping[inner] ?? inner
|
||||
const outerType = astTargetNodeType(outer, language)
|
||||
const innerType = astTargetNodeType(inner, language)
|
||||
const passed = collectNodes(root, outerType).some((node) =>
|
||||
node.children.some((child) => hasNode(child, innerType)),
|
||||
)
|
||||
@@ -437,10 +353,9 @@ export async function checkAst(
|
||||
const tree = parser.parse(code)
|
||||
if (!tree) return { passed: true, results: [] }
|
||||
try {
|
||||
const mapping = mappings[language] ?? {}
|
||||
const results = rules
|
||||
.filter(astRuleIsMeaningful)
|
||||
.map((rule) => evaluateRule(tree.rootNode, rule, language, mapping))
|
||||
.map((rule) => evaluateRule(tree.rootNode, rule, language))
|
||||
.filter((result): result is AstResult => result !== null)
|
||||
return { passed: results.every((result) => result.passed), results }
|
||||
} finally {
|
||||
|
||||
@@ -71,7 +71,7 @@ const ENGINE_OPTIONS: SelectOption[] = [
|
||||
// (或者反过来,「不能使用 f-string」永远通过),两头都不报错。
|
||||
function nodeTargetOptions(lang: string): SelectOption[] {
|
||||
return Object.entries(AST_NODE_TARGETS_BY_LANGUAGE[lang] ?? {}).map(
|
||||
([value, label]) => ({ label, value }),
|
||||
([value, entry]) => ({ label: entry.label, value }),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ function getTargetLabel(
|
||||
engine: string,
|
||||
target: string,
|
||||
): string | undefined {
|
||||
if (isNodeEngine(engine)) return AST_NODE_TARGETS_BY_LANGUAGE[lang]?.[target]
|
||||
if (isNodeEngine(engine)) return AST_NODE_TARGETS_BY_LANGUAGE[lang]?.[target]?.label
|
||||
// 运算符不写 label:判题结果的文案按语言翻译(astOperatorLabel),
|
||||
// 存一个固定 label 反而会把 C 的 && 钉死成 and
|
||||
return undefined
|
||||
|
||||
@@ -110,109 +110,122 @@ export const astRuleSchema = z.object({
|
||||
export const astRulesSchema = z.record(z.string(), z.array(astRuleSchema))
|
||||
|
||||
/**
|
||||
* 每种语言支持哪些节点 target,以及它的中文名。原来这里是一张 15 条的混合表,
|
||||
* C 和 Python 的节点混在一起铺成后台下拉(更早之前 AstRulesEditor.vue 和
|
||||
* ProblemContent.vue 还各手抄了一份)。
|
||||
* 一个 target 一条,`label` 给人看(后台下拉、题目页的「要求」),`node` 给判题机
|
||||
* 拿去比 tree-sitter 的节点类型。
|
||||
*
|
||||
* **键必须和 judge/ast.ts 的 mappings 逐一对齐** —— 那边是 target → tree-sitter
|
||||
* 节点类型,这边是 target → 中文名。少一边就是静默故障:老师给 C 题选到只有
|
||||
* Python 有的 `list_comprehension`,判题机 `mapping[target] ?? target` 拿裸名去比
|
||||
* 节点类型,C 的语法树里永远不存在它,于是「必须使用列表推导式」永远失败、
|
||||
* 「不能使用 f-string」永远通过,两头都不报错。
|
||||
* **这两半原来是分在两个包里的两张表** —— 这边 target → 中文名,
|
||||
* `apps/api/src/judge/ast.ts` 的 mappings 是 target → tree-sitter 节点类型,靠一句
|
||||
* 「两边必须同增同减」的注释维持。只加一边是静默错判:老师给 C 题选到只有 Python
|
||||
* 有的 `list_comprehension`,判题机 `mapping[target] ?? target` 拿裸名去比节点类型,
|
||||
* C 的语法树里永远不存在它,于是「必须使用列表推导式」永远失败、「不能使用
|
||||
* f-string」永远通过,两头都不报错,只有学生受着。并成一张之后,加 target 而漏配
|
||||
* 节点类型在结构上就不可能了。
|
||||
*
|
||||
* (更早之前 AstRulesEditor.vue 和 ProblemContent.vue 还各手抄过一份中文名,
|
||||
* 且 C 和 Python 的节点混在一张 15 条的表里铺成后台下拉。)
|
||||
*/
|
||||
export const AST_NODE_TARGETS_BY_LANGUAGE: Record<string, Record<string, string>> = {
|
||||
C: {
|
||||
for_loop: "for 循环",
|
||||
while_loop: "while 循环",
|
||||
do_while: "do-while 循环",
|
||||
if_statement: "if 条件",
|
||||
else_clause: "else 子句",
|
||||
switch_statement: "switch 语句",
|
||||
case_statement: "case 分支",
|
||||
break: "break 语句",
|
||||
continue: "continue 语句",
|
||||
return: "return 语句",
|
||||
function_definition: "函数定义",
|
||||
assignment: "赋值语句",
|
||||
struct: "结构体",
|
||||
include: "#include 指令",
|
||||
},
|
||||
// tree-sitter-cpp 继承 tree-sitter-c 的语法,C 那 14 条 target 在 C++ 树里
|
||||
// 逐个实测通用,所以这张表是「C 的全集 + C++ 独有的几条」
|
||||
export interface AstNodeTarget {
|
||||
/** 后台下拉和题目页展示的中文名 */
|
||||
label: string
|
||||
/** tree-sitter 里对应的节点类型,判题机按它 collectNodes */
|
||||
node: string
|
||||
}
|
||||
|
||||
const C_NODE_TARGETS = {
|
||||
for_loop: { label: "for 循环", node: "for_statement" },
|
||||
while_loop: { label: "while 循环", node: "while_statement" },
|
||||
do_while: { label: "do-while 循环", node: "do_statement" },
|
||||
if_statement: { label: "if 条件", node: "if_statement" },
|
||||
else_clause: { label: "else 子句", node: "else_clause" },
|
||||
switch_statement: { label: "switch 语句", node: "switch_statement" },
|
||||
case_statement: { label: "case 分支", node: "case_statement" },
|
||||
break: { label: "break 语句", node: "break_statement" },
|
||||
continue: { label: "continue 语句", node: "continue_statement" },
|
||||
return: { label: "return 语句", node: "return_statement" },
|
||||
function_definition: { label: "函数定义", node: "function_definition" },
|
||||
assignment: { label: "赋值语句", node: "assignment_expression" },
|
||||
struct: { label: "结构体", node: "struct_specifier" },
|
||||
include: { label: "#include 指令", node: "preproc_include" },
|
||||
} satisfies Record<string, AstNodeTarget>
|
||||
|
||||
export const AST_NODE_TARGETS_BY_LANGUAGE: Record<string, Record<string, AstNodeTarget>> = {
|
||||
C: C_NODE_TARGETS,
|
||||
/**
|
||||
* tree-sitter-cpp 继承 tree-sitter-c 的语法,C 那 14 条在 C++ 树里逐个实测通用,
|
||||
* 所以共用的条目一律**引用** C_NODE_TARGETS 而不是抄一遍 —— 原来 C 的 14 行在
|
||||
* 契约和判题机两个文件里各抄了两遍(C 一份、C++ 一份),同一份数据四份拷贝。
|
||||
* 这里逐条列出来是为了保住下拉框的显示顺序(C++ 独有的几条是插在中间的)。
|
||||
*/
|
||||
"C++": {
|
||||
for_loop: "for 循环",
|
||||
range_for_loop: "范围 for 循环",
|
||||
while_loop: "while 循环",
|
||||
do_while: "do-while 循环",
|
||||
if_statement: "if 条件",
|
||||
else_clause: "else 子句",
|
||||
switch_statement: "switch 语句",
|
||||
case_statement: "case 分支",
|
||||
break: "break 语句",
|
||||
continue: "continue 语句",
|
||||
return: "return 语句",
|
||||
function_definition: "函数定义",
|
||||
class_definition: "类定义",
|
||||
struct: "结构体",
|
||||
assignment: "赋值语句",
|
||||
include: "#include 指令",
|
||||
try_except: "try-catch",
|
||||
throw: "throw 语句",
|
||||
namespace: "namespace 定义",
|
||||
template: "模板定义",
|
||||
lambda: "lambda 表达式",
|
||||
using: "using 声明",
|
||||
for_loop: C_NODE_TARGETS.for_loop,
|
||||
range_for_loop: { label: "范围 for 循环", node: "for_range_loop" },
|
||||
while_loop: C_NODE_TARGETS.while_loop,
|
||||
do_while: C_NODE_TARGETS.do_while,
|
||||
if_statement: C_NODE_TARGETS.if_statement,
|
||||
else_clause: C_NODE_TARGETS.else_clause,
|
||||
switch_statement: C_NODE_TARGETS.switch_statement,
|
||||
case_statement: C_NODE_TARGETS.case_statement,
|
||||
break: C_NODE_TARGETS.break,
|
||||
continue: C_NODE_TARGETS.continue,
|
||||
return: C_NODE_TARGETS.return,
|
||||
function_definition: C_NODE_TARGETS.function_definition,
|
||||
class_definition: { label: "类定义", node: "class_specifier" },
|
||||
struct: C_NODE_TARGETS.struct,
|
||||
assignment: C_NODE_TARGETS.assignment,
|
||||
include: C_NODE_TARGETS.include,
|
||||
try_except: { label: "try-catch", node: "try_statement" },
|
||||
throw: { label: "throw 语句", node: "throw_statement" },
|
||||
namespace: { label: "namespace 定义", node: "namespace_definition" },
|
||||
template: { label: "模板定义", node: "template_declaration" },
|
||||
lambda: { label: "lambda 表达式", node: "lambda_expression" },
|
||||
using: { label: "using 声明", node: "using_declaration" },
|
||||
},
|
||||
Python3: {
|
||||
for_loop: "for 循环",
|
||||
while_loop: "while 循环",
|
||||
if_statement: "if 条件",
|
||||
elif_clause: "elif 子句",
|
||||
else_clause: "else 子句",
|
||||
break: "break 语句",
|
||||
continue: "continue 语句",
|
||||
return: "return 语句",
|
||||
function_definition: "函数定义",
|
||||
class_definition: "类定义",
|
||||
assignment: "赋值语句",
|
||||
try_except: "try-except",
|
||||
with_statement: "with 语句",
|
||||
import: "import 语句",
|
||||
import_from: "from-import 语句",
|
||||
list_comprehension: "列表推导式",
|
||||
list_literal: "列表",
|
||||
dict_literal: "字典",
|
||||
set_literal: "集合",
|
||||
f_string: "f-string",
|
||||
for_loop: { label: "for 循环", node: "for_statement" },
|
||||
while_loop: { label: "while 循环", node: "while_statement" },
|
||||
if_statement: { label: "if 条件", node: "if_statement" },
|
||||
elif_clause: { label: "elif 子句", node: "elif_clause" },
|
||||
else_clause: { label: "else 子句", node: "else_clause" },
|
||||
break: { label: "break 语句", node: "break_statement" },
|
||||
continue: { label: "continue 语句", node: "continue_statement" },
|
||||
return: { label: "return 语句", node: "return_statement" },
|
||||
function_definition: { label: "函数定义", node: "function_definition" },
|
||||
class_definition: { label: "类定义", node: "class_definition" },
|
||||
assignment: { label: "赋值语句", node: "assignment" },
|
||||
try_except: { label: "try-except", node: "try_statement" },
|
||||
with_statement: { label: "with 语句", node: "with_statement" },
|
||||
import: { label: "import 语句", node: "import_statement" },
|
||||
import_from: { label: "from-import 语句", node: "import_from_statement" },
|
||||
list_comprehension: { label: "列表推导式", node: "list_comprehension" },
|
||||
list_literal: { label: "列表", node: "list" },
|
||||
dict_literal: { label: "字典", node: "dictionary" },
|
||||
set_literal: { label: "集合", node: "set" },
|
||||
f_string: { label: "f-string", node: "format_string" },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 运算符 target → 该语言里的实际写法。逻辑名 `and` / `or` / `not` 在 C 里写作
|
||||
* `&&` / `||` / `!`,判题机按 mappings 翻译,文案这边也得翻 —— 否则 C 题的学生
|
||||
* 看到的要求是「必须使用 and 运算符」,而 C 里根本没有 `and` 这个词。
|
||||
* `&&` / `||` / `!`,文案要翻(否则 C 题的学生看到「必须使用 and 运算符」,而 C 里
|
||||
* 根本没有 `and` 这个词),判题机也正好拿同一个值去比节点类型 —— 所以这张表**一份
|
||||
* 两用**,不像节点那样需要两个字段。原来判题机的 mappings 里还抄了一份非恒等的
|
||||
* (`and`→`&&` 那三条),取值逐个相同,纯属重复。
|
||||
*
|
||||
* 恒等的那些条目(`+`、`==` …)判题机的 mappings 里已经删掉了,走 `?? target`
|
||||
* 回落到同一个值;这里保留完整列表是因为它同时是后台下拉的选项来源。
|
||||
* 恒等的条目(`+`、`==` …)写全是因为这张表同时是后台下拉的选项来源。
|
||||
*/
|
||||
const C_OPERATOR_TARGETS = {
|
||||
"+": "+", "-": "-", "*": "*", "/": "/", "%": "%",
|
||||
"+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=",
|
||||
"++": "++", "--": "--",
|
||||
"==": "==", "!=": "!=", ">": ">", ">=": ">=", "<": "<", "<=": "<=",
|
||||
and: "&&", or: "||", not: "!",
|
||||
"&": "&", "|": "|",
|
||||
}
|
||||
|
||||
export const AST_OPERATOR_TARGETS_BY_LANGUAGE: Record<string, Record<string, string>> = {
|
||||
C: {
|
||||
"+": "+", "-": "-", "*": "*", "/": "/", "%": "%",
|
||||
"+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=",
|
||||
"++": "++", "--": "--",
|
||||
"==": "==", "!=": "!=", ">": ">", ">=": ">=", "<": "<", "<=": "<=",
|
||||
and: "&&", or: "||", not: "!",
|
||||
"&": "&", "|": "|",
|
||||
},
|
||||
C: C_OPERATOR_TARGETS,
|
||||
// `<<` / `>>` 对 C++ 主要是 cout/cin 的流运算符(位移是同一个 token)
|
||||
"C++": {
|
||||
"+": "+", "-": "-", "*": "*", "/": "/", "%": "%",
|
||||
"+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=",
|
||||
"++": "++", "--": "--",
|
||||
"==": "==", "!=": "!=", ">": ">", ">=": ">=", "<": "<", "<=": "<=",
|
||||
and: "&&", or: "||", not: "!",
|
||||
"&": "&", "|": "|", "<<": "<<", ">>": ">>",
|
||||
},
|
||||
"C++": { ...C_OPERATOR_TARGETS, "<<": "<<", ">>": ">>" },
|
||||
Python3: {
|
||||
"+": "+", "-": "-", "*": "*", "/": "/", "//": "//", "%": "%", "**": "**",
|
||||
"+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=",
|
||||
@@ -230,14 +243,28 @@ export const AST_OPERATOR_TARGETS_BY_LANGUAGE: Record<string, Record<string, str
|
||||
export const AST_SUPPORTED_LANGUAGES = Object.keys(AST_NODE_TARGETS_BY_LANGUAGE)
|
||||
|
||||
/** 全语言的节点中文名并集,只给拿不到语言的场合做回落。有语言一律走 astNodeLabel() */
|
||||
export const AST_NODE_TARGET_LABELS: Record<string, string> = Object.assign(
|
||||
{},
|
||||
...Object.values(AST_NODE_TARGETS_BY_LANGUAGE),
|
||||
export const AST_NODE_TARGET_LABELS: Record<string, string> = Object.fromEntries(
|
||||
Object.values(AST_NODE_TARGETS_BY_LANGUAGE).flatMap((table) =>
|
||||
Object.entries(table).map(([target, entry]) => [target, entry.label]),
|
||||
),
|
||||
)
|
||||
|
||||
export function astNodeLabel(target: string, language?: string): string {
|
||||
const table = language ? AST_NODE_TARGETS_BY_LANGUAGE[language] : undefined
|
||||
return table?.[target] ?? AST_NODE_TARGET_LABELS[target] ?? target
|
||||
return table?.[target]?.label ?? AST_NODE_TARGET_LABELS[target] ?? target
|
||||
}
|
||||
|
||||
/**
|
||||
* target → tree-sitter 节点类型。判题机唯一的解析入口 ——
|
||||
* 节点走上面那张表的 `node`,运算符走运算符表(它的值本身就是要比的 token),
|
||||
* 都对不上就回落到裸 target(恒等的运算符 `+` / `==` 走的就是这条)。
|
||||
*/
|
||||
export function astTargetNodeType(target: string, language: string): string {
|
||||
return (
|
||||
AST_NODE_TARGETS_BY_LANGUAGE[language]?.[target]?.node ??
|
||||
AST_OPERATOR_TARGETS_BY_LANGUAGE[language]?.[target] ??
|
||||
target
|
||||
)
|
||||
}
|
||||
|
||||
export function astOperatorLabel(target: string, language?: string): string {
|
||||
|
||||
Reference in New Issue
Block a user