feat(语法要求): 能要求「调用 print() 时必须用 sep 参数」,题目说要用 sep/end 的不再被绕过
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
原来只能数 print 调用几次,写成 print("1-2-3***4") + print() 也算达标。
新加 must_use_keyword(只有 Python),后台「关键字参数」一组里配函数名和参数名。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0e05f7e3c7
commit
179d5fa67d
3 files changed
+52
-3
No files matched your search
@@ -115,6 +115,8 @@ export function describeAstRule(rule: AstRule, language?: string): string {
|
||||
return `不能调用 .${target}()`
|
||||
case 'must_use_operator':
|
||||
return `必须使用 ${astOperatorLabel(target, language)} 运算符`
|
||||
case 'must_use_keyword':
|
||||
return `调用 ${target}() 时必须用 ${rule.keyword ?? ''} 参数`
|
||||
case 'must_have_nesting': {
|
||||
// 这两个走 astNodeLabel 而不是裸值 —— 少了这一步文案就是
|
||||
// 「必须使用 for_loop 嵌套」,旧栈 ast_checker/engines/nesting.py 是翻的
|
||||
@@ -194,6 +196,9 @@ export function astRulesError(astRules: AstRules | null): string | null {
|
||||
}
|
||||
} else if (!target.trim()) {
|
||||
return `${at}:要检查的函数名/方法名不能为空`
|
||||
} else if (rule.engine === 'must_use_keyword') {
|
||||
if (language !== 'Python') return `${at}:${language} 没有关键字参数`
|
||||
if (!rule.keyword?.trim()) return `${at}:参数名不能为空`
|
||||
}
|
||||
if (!astRuleIsMeaningful(rule)) return `${at}:次数检查至少要填一个数字`
|
||||
}
|
||||
@@ -248,6 +253,13 @@ function functionCalls(root: Node, target: string, language: string) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 带了 `keyword=...` 的那些 target() 调用。`**kwargs` 展开的不算,看不出里面有没有 */
|
||||
function keywordCalls(root: Node, target: string, keyword: string, language: string) {
|
||||
return functionCalls(root, target, language).filter((call) =>
|
||||
call.childForFieldName('arguments')?.namedChildren.some((arg) => arg.type === 'keyword_argument' && arg.childForFieldName('name')?.text === keyword),
|
||||
)
|
||||
}
|
||||
|
||||
function methodCalls(root: Node, target: string, language: string) {
|
||||
// C++ 的 `a.push_back()` / `p->push_back()` 都是 call_expression + field_expression,
|
||||
// 和 Python 的 attribute 不是一回事 —— 少了这个分支,C++ 的「必须调用 .push_back()」
|
||||
@@ -325,6 +337,11 @@ function evaluateRule(root: Node, rule: AstRule, language: string): AstResult |
|
||||
description: describeAstRule(rule, language),
|
||||
passed: hasNode(root, nodeType),
|
||||
}
|
||||
case 'must_use_keyword':
|
||||
return {
|
||||
description: describeAstRule(rule, language),
|
||||
passed: keywordCalls(root, target, rule.keyword ?? '', language).length > 0,
|
||||
}
|
||||
case 'must_have_nesting': {
|
||||
const outer = rule.outer ?? ''
|
||||
const inner = rule.inner ?? ''
|
||||
|
||||
@@ -117,6 +117,18 @@ const ENGINE_OPTIONS: SelectOption[] = [
|
||||
},
|
||||
]
|
||||
|
||||
// 关键字参数只有 Python 有,C 题不给选
|
||||
const KEYWORD_OPTION: SelectOption = {
|
||||
label: '关键字参数',
|
||||
type: 'group',
|
||||
key: 'keyword_group',
|
||||
children: [{ label: '调用时必须带参数', value: 'must_use_keyword' }],
|
||||
}
|
||||
|
||||
function engineOptions(lang: string): SelectOption[] {
|
||||
return lang === 'Python' ? [...ENGINE_OPTIONS, KEYWORD_OPTION] : ENGINE_OPTIONS
|
||||
}
|
||||
|
||||
// 选项按语言生成。原来是一张 C/Python 混合的 15 条表整份铺开,给 C 题也能选到
|
||||
// 列表推导式、f-string 这些 C 根本没有的东西 —— 存得进去,判题时永远失败
|
||||
// (或者反过来,「不能使用 f-string」永远通过),两头都不报错。
|
||||
@@ -139,6 +151,7 @@ const NODE_ENGINES = ['must_exist_node', 'must_not_exist_node', 'count_node']
|
||||
const FUNCTION_ENGINES = ['must_call_function', 'must_not_call_function', 'count_function_call']
|
||||
const METHOD_ENGINES = ['must_call_method', 'must_not_call_method']
|
||||
const OPERATOR_ENGINES = ['must_use_operator']
|
||||
const KEYWORD_ENGINES = ['must_use_keyword']
|
||||
const COUNT_ENGINES = ['count_node', 'count_function_call']
|
||||
|
||||
function isNodeEngine(engine: string) {
|
||||
@@ -153,6 +166,9 @@ function isMethodEngine(engine: string) {
|
||||
function isOperatorEngine(engine: string) {
|
||||
return OPERATOR_ENGINES.includes(engine)
|
||||
}
|
||||
function isKeywordEngine(engine: string) {
|
||||
return KEYWORD_ENGINES.includes(engine)
|
||||
}
|
||||
function isCountEngine(engine: string) {
|
||||
return COUNT_ENGINES.includes(engine)
|
||||
}
|
||||
@@ -196,7 +212,7 @@ function needsTargetDropdown(engine: string) {
|
||||
return isNodeEngine(engine)
|
||||
}
|
||||
function needsTargetInput(engine: string) {
|
||||
return isFunctionEngine(engine) || isMethodEngine(engine)
|
||||
return isFunctionEngine(engine) || isMethodEngine(engine) || isKeywordEngine(engine)
|
||||
}
|
||||
function needsOperatorDropdown(engine: string) {
|
||||
return isOperatorEngine(engine)
|
||||
@@ -260,6 +276,8 @@ function updateRule(lang: string, index: number, field: string, value: any) {
|
||||
delete rule.min
|
||||
delete rule.max
|
||||
delete rule.exact
|
||||
delete rule.keyword
|
||||
if (isKeywordEngine(value)) rule.keyword = ''
|
||||
// 次数引擎不给默认值的话,存下去就是一条恒真规则
|
||||
if (isCountEngine(value)) rule.exact = 1
|
||||
} else if (field === 'target') {
|
||||
@@ -273,6 +291,8 @@ function updateRule(lang: string, index: number, field: string, value: any) {
|
||||
} else if (field === 'max') {
|
||||
if (value === null || value === undefined) delete rule.max
|
||||
else rule.max = value
|
||||
} else if (field === 'keyword') {
|
||||
rule.keyword = value
|
||||
} else if (field === 'message') {
|
||||
rule.message = value
|
||||
}
|
||||
@@ -322,7 +342,7 @@ watch(supportedLanguages, (langs) => {
|
||||
<div v-for="(rule, index) in getRulesForLang(lang)" :key="index" class="rule">
|
||||
<div class="ruleRow">
|
||||
<n-select
|
||||
:options="ENGINE_OPTIONS"
|
||||
:options="engineOptions(lang)"
|
||||
:value="rule.engine"
|
||||
size="small"
|
||||
class="engine"
|
||||
@@ -342,9 +362,17 @@ watch(supportedLanguages, (langs) => {
|
||||
:value="rule.target"
|
||||
size="small"
|
||||
class="target"
|
||||
placeholder="函数 / 方法名"
|
||||
:placeholder="isKeywordEngine(rule.engine) ? '函数名,如 print' : '函数 / 方法名'"
|
||||
@update:value="(v: string) => updateRule(lang, index, 'target', v)"
|
||||
/>
|
||||
<n-input
|
||||
v-if="isKeywordEngine(rule.engine)"
|
||||
:value="rule.keyword"
|
||||
size="small"
|
||||
class="target"
|
||||
placeholder="参数名,如 sep"
|
||||
@update:value="(v: string) => updateRule(lang, index, 'keyword', v)"
|
||||
/>
|
||||
<n-select
|
||||
v-if="needsOperatorDropdown(rule.engine)"
|
||||
:options="operatorTargetOptions(lang)"
|
||||
|
||||
@@ -84,6 +84,8 @@ export const astRuleEngineSchema = z.enum([
|
||||
'must_call_method',
|
||||
'must_not_call_method',
|
||||
'must_use_operator',
|
||||
// 只有 Python 有关键字参数:「调用 print() 时必须用 sep 参数」
|
||||
'must_use_keyword',
|
||||
// 判题机实现了,但后台编辑器还没有对应的选项,目前只能手工造数据用上
|
||||
'must_have_nesting',
|
||||
])
|
||||
@@ -95,6 +97,8 @@ export const astRuleSchema = z.object({
|
||||
/** must_have_nesting 专用:外层、内层节点 */
|
||||
outer: z.string().optional(),
|
||||
inner: z.string().optional(),
|
||||
/** must_use_keyword 专用:参数名(target 是函数名) */
|
||||
keyword: z.string().optional(),
|
||||
/** 展示用的中文名,缺省回落到 target */
|
||||
label: z.string().optional(),
|
||||
/** 自定义提示。生产库里存的是空串而不是缺键,判题机按 `||` 回落到默认文案 */
|
||||
|
||||
Reference in new issue
Block a user