feat(vipeye-internal): union/fn 别名/extern 无函数体及 builtins 6 函数签名
- union 与 enum/rnum 对齐(union class / union A: 单独转 class) - fn/fun/func/function 行尾单独使用按 def 处理并报缺函数名, 后跟 :/=/ 保持标识符,避免误伤 func: 注解、func = x、func(x) - extern def/fn 声明可省略函数体(无冒号无 pass),: pass 方式保留 - TextMate 高亮与 language-configuration 缩进规则同步(含 inline fn 修饰关键字) - typeshed builtins.vpi 新增 Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut, 隐式内置直接可用(签名照抄 vipershed c.vpi) - viperGenericTypeVarCall 9/9、tokenizer 91 测试通过
This commit is contained in:
1 parent
817bc47ee9
commit
bbd8b1022e
6 files changed
+235
-15
No files matched your search
@@ -2111,11 +2111,22 @@ export class Parser {
|
||||
this._isInFinallyBlock = false;
|
||||
this._isInFinallyLoop = false;
|
||||
|
||||
const suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
|
||||
if (!functionTypeAnnotationToken) {
|
||||
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
|
||||
let suite: SuiteNode;
|
||||
const possibleSuiteStart = this._peekToken();
|
||||
if (viperModifier === 'extern' && possibleSuiteStart.type !== TokenType.Colon) {
|
||||
// Viper extern 声明允许省略函数体(`extern def f(...) -> T`,无冒号、无 pass)。
|
||||
// 创建空 suite 并消费行尾换行;`: pass` 方式仍走 _parseSuite 正常解析。
|
||||
suite = SuiteNode.create(possibleSuiteStart);
|
||||
if (this._consumeTokenIfType(TokenType.NewLine)) {
|
||||
extendRange(suite, this._tokenizerOutput!.tokens.getItemAt(this._tokenIndex - 1));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
suite = this._parseSuite(/* isFunction */ true, this._parseOptions.skipFunctionAndClassBody, () => {
|
||||
if (!functionTypeAnnotationToken) {
|
||||
functionTypeAnnotationToken = this._getTypeAnnotationCommentText();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._isInExceptionGroup = wasInExceptionGroup;
|
||||
this._isInFinallyBlock = wasInFinallyBlock;
|
||||
|
||||
@@ -675,18 +675,24 @@ function getViperStatementKeywordAction(
|
||||
return 'viperModifier';
|
||||
}
|
||||
if (name === 'fn' || name === 'fun' || name === 'func' || name === 'function') {
|
||||
// def 别名:语句起始(或紧跟在修饰关键字后,如 `inline fn f()`)且后跟
|
||||
// 标识符(函数名)时按 def 关键字处理。
|
||||
// def 别名:语句起始(或紧跟修饰关键字后,如 `inline fn f()`)时:
|
||||
// 后跟标识符(函数名)→ def;
|
||||
// 行尾单独使用(后面无任何内容)→ def(parser 会报缺函数名);
|
||||
// 后跟 ':' / '=' / '(' 等 → 保持标识符(保护 TransPyC 现有
|
||||
// `func:` 注解、`func = x` 赋值、`func(x)` 调用等合法用法)。
|
||||
if (!isViperAtStatementStart(text, identifierStart) && !precededByViperModifier) {
|
||||
return undefined;
|
||||
}
|
||||
return nextWord.length > 0 && isIdentifierStartChar(nextWord.charCodeAt(0)) ? 'def' : undefined;
|
||||
if (nextWord.length > 0) {
|
||||
return isIdentifierStartChar(nextWord.charCodeAt(0)) ? 'def' : undefined;
|
||||
}
|
||||
return peekViperNextNonWsChar(text, identifierEnd) === undefined ? 'def' : undefined;
|
||||
}
|
||||
if (name === 'enum' || name === 'rnum') {
|
||||
if (name === 'enum' || name === 'rnum' || name === 'union') {
|
||||
if (nextWord === 'class' || nextWord === 'struct') {
|
||||
return 'skip';
|
||||
}
|
||||
// `enum A:` / `rnum A:` 单独使用(等价 class A:)。
|
||||
// `enum A:` / `rnum A:` / `union A:` 单独使用(等价 class A:)。
|
||||
return isViperAtStatementStart(text, identifierStart) &&
|
||||
nextWord.length > 0 &&
|
||||
isIdentifierStartChar(nextWord.charCodeAt(0))
|
||||
@@ -734,6 +740,19 @@ function isViperAtStatementStart(text: string, identifierStart: number): boolean
|
||||
return pos === 0 || text[pos - 1] === '\n' || text[pos - 1] === '\r';
|
||||
}
|
||||
|
||||
// 跳过空白后返回首个非空白字符;若到行尾(换行)或文件结束则返回 undefined。
|
||||
function peekViperNextNonWsChar(text: string, from: number): string | undefined {
|
||||
let pos = from;
|
||||
const len = text.length;
|
||||
while (pos < len && (text[pos] === ' ' || text[pos] === '\t')) {
|
||||
pos++;
|
||||
}
|
||||
if (pos < len && text[pos] !== '\r' && text[pos] !== '\n') {
|
||||
return text[pos];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class Tokenizer {
|
||||
private _cs = new CharacterStream('');
|
||||
private _tokens: Token[] = [];
|
||||
|
||||
@@ -364,6 +364,15 @@ rnum B:
|
||||
class Inner:
|
||||
x: t.CInt
|
||||
|
||||
# union + class(等价 t.CUnion 风格)
|
||||
union class Value:
|
||||
i: t.CInt
|
||||
f: t.CFloat
|
||||
|
||||
# union 单独使用(等价 class)
|
||||
union D:
|
||||
x: t.CInt
|
||||
|
||||
def swap(p: IntPair | t.CPtr):
|
||||
tmp: t.CInt = c.Deref(p).a
|
||||
c.Deref(p).a = c.Deref(p).b
|
||||
@@ -372,6 +381,7 @@ def swap(p: IntPair | t.CPtr):
|
||||
def test_usage():
|
||||
r: t.CInt = double_export(21)
|
||||
g: t.CInt = Color.GREEN
|
||||
u: t.CFloat = Value.f
|
||||
`;
|
||||
const testDir = path.join(__dirname, '__viper_keywords_test__');
|
||||
const fs = require('fs');
|
||||
@@ -518,6 +528,12 @@ extern def e_ok2(x: t.CInt) -> t.CInt:
|
||||
extern def e_bad(x: t.CInt) -> t.CInt:
|
||||
return x
|
||||
|
||||
# extern 声明可以省略函数体(无冒号、无 pass),": pass" 方式保留
|
||||
extern def e_no_body(x: t.CInt) -> t.CInt
|
||||
|
||||
# extern + fn 别名,同样可以省略函数体
|
||||
extern fn e_fn_no_body(x: t.CInt) -> t.CInt
|
||||
|
||||
def use():
|
||||
a: t.CInt = f1(1) + f2(2) + f3(3) + f4(4)
|
||||
`;
|
||||
@@ -556,4 +572,147 @@ def use():
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('Viper fn/fun/func/function 单独使用报错,后跟 :/=/ 保持标识符', () => {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const code = `import t
|
||||
|
||||
# fn 行尾单独使用 → 视为 def,报缺函数名
|
||||
fn
|
||||
|
||||
# fun 后跟 '=' → 保持标识符(赋值,不是关键字)
|
||||
fun = 5
|
||||
|
||||
# func 后跟 '(' → 保持标识符(调用,不是关键字)
|
||||
func(1)
|
||||
|
||||
# function 后跟 ':' → 保持标识符(注解声明,不是关键字)
|
||||
function: t.CInt = 0
|
||||
|
||||
# 正常用法不受影响
|
||||
fn ok(x: t.CInt) -> t.CInt:
|
||||
return x
|
||||
`;
|
||||
const testDir = path.join(__dirname, '__viper_fn_keyword_test__');
|
||||
const fs = require('fs');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'fn_keyword.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
try {
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== fn_keyword.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
// 期望恰好 1 个错误级诊断:fn 行尾单独使用报"缺函数名";
|
||||
// fun=/func(/function: 保持标识符不应产生语法/类型错误。
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
1,
|
||||
`expected exactly 1 error (fn alone), got: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
assert.ok(
|
||||
errors[0].includes('函数名') || errors[0].includes('function name'),
|
||||
`expected missing-function-name error: ${JSON.stringify(errors)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
test('builtins.vp 中 Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut 签名可用且不报未定义', () => {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const code = `import t
|
||||
|
||||
def test_builtins_asm_llvmir():
|
||||
r: t.CInt = 0
|
||||
# Asm:基础 + 输出/输入操作数 + 破坏列表
|
||||
Asm("nop")
|
||||
Asm(f"mov {AsmOut(r, t.ASM_DESCR.OUTPUT_REG)}, rdi", op=[t.ASM_DESCR.CLOBBER_MEMORY])
|
||||
Asm(f"mov eax, {AsmInp(r, t.ASM_DESCR.REG_ANY)}")
|
||||
# LLVMIR:LInp/LOut 标记操作数
|
||||
LLVMIR(f"{LOut(r)} = add i32 {LInp(r)}, {LInp(r)}")
|
||||
`;
|
||||
const testDir = path.join(__dirname, '__viper_builtins_asm_test__');
|
||||
const fs = require('fs');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'builtins_asm.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
try {
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== builtins_asm.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
// 期望没有任何错误级诊断:Asm/LLVMIR/AsmInp/AsmOut/LInp/LOut 由隐式
|
||||
// builtins(builtins.vpi)提供,无需 import 即可直接使用。
|
||||
const errors = diagnostics.filter((d) => d.startsWith('[category=0]'));
|
||||
assert.strictEqual(
|
||||
errors.length,
|
||||
0,
|
||||
`unexpected error diagnostics: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
|
||||
program.dispose();
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
@@ -2560,3 +2560,34 @@ if sys.version_info >= (3, 11):
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
class PythonFinalizationError(RuntimeError): ...
|
||||
|
||||
# --- Viper 扩展:C 内联汇编 / LLVM IR 内联设施 ---
|
||||
# 签名照抄 vipershed-fallback/stdlib/c.vpi(c.Asm / c.LLVMIR / c.AsmInp / c.AsmOut /
|
||||
# c.LInp / c.LOut),供 Viper 代码直接以内置函数形式使用。
|
||||
|
||||
def Asm(asm: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
|
||||
"""内联汇编(等价 c.Asm)。inp 为输入操作数描述符列表,out 为输出操作数
|
||||
描述符列表,op 为破坏描述符列表。"""
|
||||
...
|
||||
|
||||
def LLVMIR(ir: str, *args: Any, inp: Any = None, out: Any = None, op: Any = None) -> Any:
|
||||
"""LLVM IR 内联(等价 c.LLVMIR)。inp/out/op 为操作数描述符列表。"""
|
||||
...
|
||||
|
||||
def AsmInp(expr: Any, constraint: Any = None) -> Any:
|
||||
"""汇编输入操作数标记(等价 c.AsmInp)。constraint 为寄存器约束
|
||||
(如 t.ASM_DESCR.REG_ANY)。"""
|
||||
...
|
||||
|
||||
def AsmOut(expr: Any, constraint: Any = None) -> Any:
|
||||
"""汇编输出操作数标记(等价 c.AsmOut)。constraint 为寄存器约束
|
||||
(如 t.ASM_DESCR.OUTPUT_REG)。"""
|
||||
...
|
||||
|
||||
def LInp(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输入操作数标记(等价 c.LInp)。"""
|
||||
...
|
||||
|
||||
def LOut(expr: Any, constraint: Any = None) -> Any:
|
||||
"""LLVM IR 输出操作数标记(等价 c.LOut)。"""
|
||||
...
|
||||
@@ -28,7 +28,7 @@
|
||||
],
|
||||
"onEnterRules": [
|
||||
{
|
||||
"beforeText": "^\\s*(?:(?:inline|export|extern|enum|rnum)\\s+)?(?:def|fn|fun|func|function|class|for|if|elif|else|while|try|finally|with|except|async|struct)\\b.*?:\\s*$",
|
||||
"beforeText": "^\\s*(?:(?:inline|export|extern|enum|rnum|union)\\s+)?(?:def|fn|fun|func|function|class|for|if|elif|else|while|try|finally|with|except|async|struct)\\b.*?:\\s*$",
|
||||
"action": { "indent": "indent" }
|
||||
},
|
||||
{
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"wordPattern": "(?x)\n \\b\\w*\\b \n | (?<=\\.)\\w+\n | (?<=@)\\w+\n | \\p{Lu}[\\p{Ll}_\\p{Lu}\\p{Nd}]*\n | [[:alpha:]_][[:alpha:]_\\p{Nd}]*\n | \\p{Lu}[\\p{Ll}_\\p{Lu}\\p{Nd}]*f?\\b",
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "^\\s*(?:(?:inline|export|extern|enum|rnum)\\s+)?(?:class|def|fn|fun|func|function|elif|else|except|finally|for|if|try|while|with|struct|async\\s+def|async\\s+for|async\\s+with)\\b.*?:\\s*$",
|
||||
"increaseIndentPattern": "^\\s*(?:(?:inline|export|extern|enum|rnum|union)\\s+)?(?:class|def|fn|fun|func|function|elif|else|except|finally|for|if|try|while|with|struct|async\\s+def|async\\s+for|async\\s+with)\\b.*?:\\s*$",
|
||||
"decreaseIndentPattern": "^\\s*(elif|else|except|finally)\\b.*?:\\s*$"
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "storage.type.function.viper",
|
||||
"match": "\\b(fn|fun|func|function)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*)"
|
||||
"match": "\\b(fn|fun|func|function)\\b(?=\\s+[A-Za-z_][A-Za-z0-9_]*|\\s*$)"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.class.viper",
|
||||
@@ -47,11 +47,11 @@
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.viper",
|
||||
"match": "\\b(inline|export|extern)\\b(?=\\s+def\\b|[ \\t]*$)"
|
||||
"match": "\\b(inline|export|extern)\\b(?=\\s+(?:def|fn|fun|func|function)\\b|[ \\t]*$)"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.class.viper",
|
||||
"match": "\\b(enum|rnum)\\b(?=\\s+(class|struct)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*)"
|
||||
"match": "\\b(enum|rnum|union)\\b(?=\\s+(class|struct)\\b|\\s+[A-Za-z_][A-Za-z0-9_]*)"
|
||||
},
|
||||
{
|
||||
"name": "storage.type.class.viper",
|
||||
|
||||
Reference in new issue
Block a user