playbook/docs/tsl/syntax/05_functions_and_calls.md

991 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TSL 函数与调用
文档类型:语法主线
是否可直接用于生成代码:是
是否含可直接照写示例:是
是否含不可照写反例:是
遇到不确定时:先按本页候选页继续判断;[02_core_model.md](02_core_model.md)(优先)、[09_units_and_scope.md](09_units_and_scope.md)、[16_lexical_structure_and_compile_options.md](16_lexical_structure_and_compile_options.md)、[18_external_calls_and_threads.md](18_external_calls_and_threads.md)、[11_pitfalls.md](11_pitfalls.md);仍不命中时回到语法路由中心 [index.md](index.md);如果问题已经超出语法层,回到 TSL 总入口 [../index.md](../index.md)
这一篇只负责 `function` / `procedure` 的定义、调用、参数传递和值返回。跨 `unit` 声明边界、外部系统交互和业务函数库只在本页保留路由或最小边界。
## 本篇职责
回答“如何正确声明 `function``procedure`、`.tsl` 脚本语句区如何调用后置函数声明、怎样使用参数修饰、普通函数默认参数与可变参数,以及哪些函数写法会直接编译失败”。
## 智能体函数/调用判断流程
1. 先判断目标文件是 `.tsl` 还是 `.tsf`;文件形态不明确时回 [02_core_model.md](02_core_model.md)。
2. `.tsl` 中先写语句区,再把 `function` / `procedure` 声明放在后面;不要在声明区后面追加脚本语句。
3. `.tsf` 中把顶层 `function` / `procedure` 当成模块 / 函数扩展声明;不要写成顺序执行入口。
4. 用户只说“写一个函数”且没有指定 `procedure` 时,默认用 `function`
5. 只有用户明确要求 `procedure` / 过程时,才用 `procedure`;即使任务没有返回值,也默认用 `function`
6. 调用普通 TSL 函数时,命名参数只写 `name: value`;不要把 `name = value` 当成命名参数。
7. 参数是否写回调用方要看 `const` / `var` / `{$varByRef-}` / `in` / `out`,不要默认按其他语言习惯推断。
8. 默认参数先按普通函数规则处理;涉及 `unit interface``unit const` 默认值时,跳到 [09_units_and_scope.md](09_units_and_scope.md)。
9. 匿名函数和 TSL 函数值只按本页明确的 `call(f, ...)` / `##f(...)` 生成;`external`、原生函数指针包装、C 回调、线程和系统交互跳到 [18_external_calls_and_threads.md](18_external_calls_and_threads.md)。
10. 没有对应代码块时不要发明函数/调用写法尤其不要把二进制函数、系统函数、TSL 函数值、原生函数指针和匿名函数都套成同一种调用语法。
## 核心规则
- 最稳妥的函数骨架仍然是 `function Name(...); begin ... end;`
- 用户提示词里的”函数”默认对应 `function`,不要自动改写成 `procedure`
- `procedure Name(...); begin ... end;` 只在用户明确要求 `procedure` / 过程时生成;不要因为没有返回值就自动改用 `procedure`
-`.tsl` 文件模型层,脚本语句后可以接函数声明;语句区在前顺序执行,声明区在后提供函数/过程定义。见 [02_core_model.md](02_core_model.md)。
-`.tsf` 文件模型层,顶层 `function` / `procedure` 是模块/函数扩展声明;部署到解释器 `funcext` 后可被脚本直接调用。
- 函数头后默认保留分号;不要为了简写主动省略。
- 函数体内部的普通语句照常用分号结尾;顶层函数/过程声明的 `end` 后必须加分号(写成 `end;`)。
- 函数体内的控制流块(`if`/`while`/`for` 等)的 `end` 不加分号;控制流分号规则见 [07_control_flow.md](07_control_flow.md)。
- 一个函数定义体里可以同时出现主函数和子函数。
- 函数支持参数类型注解和返回值类型注解。
- 不带类型注解时,多个参数用逗号分隔。
- 带类型注解时,多个参数用分号分隔。
- 没有明确类型名证据时,不要为了“看起来更完整”而发明参数类型或返回类型。
- `const``var` 形参修饰属于文档明确写法。
- 按文档运行时边界,未修饰参数可以写回调用方;这是运行时默认行为,不是语言规范保证。
- `{$varByRef-}` 会把未修饰参数切换成按值传递;`var` 形参仍保持引用语义。
-`{$varByRef-}` 下,调用时可以用 `in` / `out` 前缀逐个参数覆盖默认传递方式。
- 如果不确定任务是否需要写回语义,优先显式用 `const` 形参,或先切到 `{$varByRef-}`;不要依赖未修饰参数的运行时默认行为。
- `return expr;` 会直接返回当前函数结果。
- `exit;` 会立即结束当前函数;在本页最小样例里,如果此前没有写入返回结果,调用方观察到的是默认值 `0`
- 调用时支持命名参数,写法是 `name: value`
- 命名参数也支持 `call(...)` 这类按函数名或函数指针转调的模型。
- 一旦某次调用里开始使用命名参数,后面的参数就不能再退回位置参数。
- 对二进制函数 / 系统函数直接使用命名参数,会报 `named parameter mode can't support here`;这类函数要先用 TSL 再封一层。
- 函数参数支持默认值。
- 普通函数的默认值规则不要直接等同到 `unit interface` 声明;跨 `unit` 的默认参数边界只照本页最小反例和 [09_units_and_scope.md](09_units_and_scope.md) 处理。
- 尾部 `...` 形式的可变参数属于文档明确写法。
- 在可变参数函数体里,`Params`、`ParamCount`、`RealParamCount` 都可用。
- 可变参数组可以通过 `...` 转发给另一个函数调用。
- 可变参数组也可以通过 `call(fc, ...)`、`##fc(...)`、`invoke(obj, name, 0, ...)` 转发;这些属于调用转发边界,只在有对应文档明确样例时生成。
- `a := function(...) begin ... end;` 这种匿名函数写法属于文档明确写法。
- 匿名函数可以直接作为参数传入另一个函数。
- `thisFunction(FuncName)` 可以把已知函数绑定成函数值。
- 匿名函数和 TSL 函数值的稳定调用方式仍是 `call(f, ...)``##f(...)`
- `f(...)` 这种“函数变量直接调用”写法不作为可写事实;无论 `f` 是匿名函数、`findFunction(...)` 还是 `thisFunction(...)` 返回的函数指针,都不要默认写成直调。
- `::FuncName(...)` 可以指向全局/系统函数,用来绕过当前作用域里的同名局部函数。
- `external`、原生函数指针包装、`makeInstance` / C 回调和线程调用统一移到 [18_external_calls_and_threads.md](18_external_calls_and_threads.md)。
- 不要在 `.tsl` 的函数声明区之后继续追加脚本语句。
## 可直接照写示例
使用这些示例时遵守:
- 普通运行示例默认按 `.tsl` 脚本语句区书写;入口语句放前面,函数 / 过程 / 类型声明放在后置声明区。
- `.tsf` 函数 / 过程示例只按可复用顶层声明理解,不要在 `.tsf` 里追加顺序执行入口语句。
- `procedure` 示例只在用户明确要求 `procedure` / 过程时复制;普通“写函数”任务不要用。
- `external`、原生函数指针包装、C 回调、线程、系统交互和二进制函数边界不在本页硬推断;命中这些任务时跳到 [18_external_calls_and_threads.md](18_external_calls_and_threads.md) 或函数库文档。
### 基础函数 / 过程骨架
`.tsl` 语句区调用后置函数声明:
代码块身份:可直接照写示例
```tsl
a := 1;
test();
function test();
begin
echo "test";
end;
```
代码块身份:输出片段
```text
test
```
最短函数骨架:
代码块身份:可直接照写示例
```tsl
function Add(a, b);
begin
return a + b;
end;
```
主函数加子函数:
代码块身份:可直接照写示例
```tsl
function MultiFunc();
begin
return Twice(3);
end;
function Twice(x);
begin
return x * 2;
end;
```
显式要求 `procedure` 时的最短骨架:
代码块身份:可直接照写示例
```tsl
procedure LogDone();
begin
end;
```
显式要求 `procedure` 且需要写回参数时的最小运行样例:
代码块身份:可直接照写示例
```tsl
a := 1;
Bump(a);
writeLn(a);
procedure Bump(var x);
begin
x := x + 1;
end;
```
代码块身份:输出片段
```text
2
```
### 签名增强
带参数类型和返回值类型:
代码块身份:可直接照写示例
```tsl
function Demo(a: integer): integer;
begin
return a;
end;
```
带类型时的多参数分隔:
代码块身份:可直接照写示例
```tsl
function Demo(a: integer; b: integer);
begin
return a + b;
end;
```
参数修饰:
代码块身份:可直接照写示例
```tsl
a := 10;
writeLn(ReadConst(a));
SetVar(a);
writeLn(a);
function ReadConst(const x);
begin
return x + 1;
end;
function SetVar(var x);
begin
x := x + 5;
end;
```
结果说明:
- `ReadConst(a)` 输出 `11`
- `SetVar(a)` 之后,`a` 输出 `15`
- 直接给 `const` 形参赋值会编译失败
代码块身份:输出片段
```text
11
15
```
### 参数传递方式
未修饰参数默认写回调用方:
代码块身份:可直接照写示例
```tsl
x := 1;
TouchDefault(x);
writeLn(x);
function TouchDefault(a);
begin
a := 9;
end;
```
结果说明:
- `TouchDefault(x)` 之后,`x` 输出 `9`
代码块身份:输出片段
```text
9
```
`{$varByRef-}``var` 形参:
代码块身份:可直接照写示例
```tsl
x := 1;
TouchDefault(x);
writeLn(x);
y := 1;
TouchValue(y);
writeLn(y);
z := 1;
TouchForcedVar(z);
writeLn(z);
function TouchDefault(a);
begin
a := 9;
end;
{$varByRef-}
function TouchValue(a);
begin
a := 8;
end;
function TouchForcedVar(var a);
begin
a := 7;
end;
{$varByRef+}
```
结果说明:
- 默认模式下,`TouchDefault(x)` 后 `x` 输出 `9`
- `{$varByRef-}` 下,未修饰参数版本 `TouchValue(y)` 之后,`y` 仍输出 `1`
- `{$varByRef-}` 下,`var` 形参版本 `TouchForcedVar(z)` 之后,`z` 输出 `7`
代码块身份:输出片段
```text
9
1
7
```
`in` / `out` 调用前缀:
代码块身份:可直接照写示例
```tsl
a := 0;
b := 0;
c := 0;
Touch3(a, b, c);
writeLn(a);
writeLn(b);
writeLn(c);
Touch3(in a, out b, c);
writeLn(a);
writeLn(b);
writeLn(c);
{$varByRef-}
function Touch3(a, b, c);
begin
a := 1;
b := 2;
c := 3;
end;
```
结果说明:
-`{$varByRef-}` 下,直接调用 `Touch3(a, b, c)` 后依次输出 `0`、`0`、`0`
- 同样在 `{$varByRef-}` 下,`Touch3(in a, out b, c)` 后依次输出 `0`、`2`、`0`
- 说明 `in` / `out` 可以在调用点逐个参数覆盖默认传递方式
代码块身份:输出片段
```text
0
0
0
0
2
0
```
### `return` 与 `exit`
代码块身份:可直接照写示例
```tsl
writeLn(Demo(1));
writeLn(Demo(0));
function Demo(x);
begin
if x > 0 then
exit;
return 99;
end;
```
结果说明:
- `Demo(1)` 输出 `0`
- `Demo(0)` 输出 `99`
- 说明 `exit;` 会立即结束当前函数体
- 在这个最小例子里,因为 `exit;` 之前没有写入返回结果,调用方观察到的是默认值 `0`
代码块身份:输出片段
```text
0
99
```
### 调用增强
命名参数调用:
代码块身份:可直接照写示例
```tsl
writeLn(Pack(a: 1, b: 2));
function Pack(a, b);
begin
return a * 10 + b;
end;
```
代码块身份:输出片段
```text
12
```
结果说明:
- `Pack(a: 1, b: 2)` 返回 `12`
- `Pack(b: 2, a: 1)` 返回 `12`
- `Pack(1, b: 2)` 返回 `12`
跳过中间参数时,未命中的参数保持 `nil`
代码块身份:可直接照写示例
```tsl
TestFunc(1, c: 3);
function TestFunc(a, b, c);
begin
writeLn(ifNil(b));
return 0;
end;
```
结果说明:
- 输出 `1`
- 说明 `TestFunc(1, c: 3)` 这种调用里,中间参数 `b` 会保持 `nil`
代码块身份:输出片段
```text
1
```
通过 `call(...)` 也支持命名参数:
代码块身份:可直接照写示例
```tsl
writeLn(call("TestFunc", a: 1, c: 2, b: 3));
function TestFunc(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
```
结果说明:
- 输出 `132`
- 说明 `call(...)` 也支持按参数名传值
- 说明命名参数传入后不再按位置解释,而是按名字绑定到形参
代码块身份:输出片段
```text
132
```
### 默认参数
默认值参数:
代码块身份:可直接照写示例
```tsl
function AddOne(a = 1);
begin
return a + 1;
end;
```
带类型时也支持默认值:
代码块身份:可直接照写示例
```tsl
function TypedAdd(a: integer = 1): integer;
begin
return a + 1;
end;
```
多个参数时,后面的参数可以带默认值:
代码块身份:可直接照写示例
```tsl
function Pack(a, b = 2);
begin
return a * 10 + b;
end;
```
代码块身份:可直接照写示例
```tsl
function Pack(a: integer; b: integer = 2): integer;
begin
return a * 10 + b;
end;
```
默认值也可以写成表达式:
代码块身份:可直接照写示例
```tsl
function ExprDefault(a = 1 + 2);
begin
return a;
end;
```
结果说明:
- `AddOne()` 返回 `2`
- `AddOne(5)` 返回 `6`
- `TypedAdd()` 返回 `2`
- `TypedAdd(5)` 返回 `6`
- `Pack(1)` 返回 `12`
- `Pack(a: 1)` 返回 `12`
- `ExprDefault()` 返回 `3`
`unit interface` 声明下的默认参数要单独看。普通函数默认参数可用,不等于跨 `unit` 声明边界也同样可靠。
代码块身份:反例 / 不可照写
```text
unit UnitConst;
interface
const default_value = 888;
function F(a, b = 100, c = default_value);
```
边界说明:
- `F(1)` 输出 `101``F(1, 2)` 输出 `3`
- 同一组文件下,`UnitConst.default_value` 可读到 `888`,而 `F(1, 2, 3)` 输出 `6`
- 因此不要把“普通函数默认参数可用”直接泛化成“`unit interface` 里引用 `unit const` 的默认参数也同样可靠”
- 这类跨 `unit` 的声明边界,统一回看 [09_units_and_scope.md](09_units_and_scope.md)
### 可变参数 `...`
尾部可变参数:
代码块身份:可直接照写示例
```tsl
function SumAll(...);
begin
s := 0;
for i, v in Params do
s := s + v;
return s;
end;
```
可变参数转发:
代码块身份:可直接照写示例
```tsl
function Forward(...);
begin
return SumAll(...);
end;
function SumAll(...);
begin
s := 0;
for i, v in Params do
s := s + v;
return s;
end;
```
`ParamCount``RealParamCount`
代码块身份:可直接照写示例
```tsl
function CountArgs(a, b, ...);
begin
return ParamCount * 10 + RealParamCount;
end;
```
结果说明:
- `SumAll(1, 2, 3, 4)` 返回 `10`
- `Forward(1, 2, 3, 4)` 返回 `10`
- `CountArgs(1, 2, 3, 4)` 返回 `44`
- `CountArgs(1, 2)` 返回 `22`
通过 `call``##` 转发可变参数:
代码块身份:可直接照写示例
```tsl
writeLn(DoFunc("Sum3", 1, 2, 3));
writeLn(DoFunc2(thisFunction(Sum3), 1, 2, 3));
function Sum3(a, b, c);
begin
return a + b + c;
end;
function DoFunc(fc, ...);
begin
return call(fc, ...);
end;
function DoFunc2(fc, ...);
begin
return ##fc(...);
end;
```
结果说明:
- `DoFunc("Sum3", 1, 2, 3)` 输出 `6`
- `DoFunc2(thisFunction(Sum3), 1, 2, 3)` 输出 `6`
代码块身份:输出片段
```text
6
6
```
通过 `invoke` 转发可变参数:
代码块身份:可直接照写示例
```tsl
writeLn(DoInvoke("Dispatch", "Int", 2, 20, 200));
writeLn(DoInvoke("Dispatch", "Other", 1, 2, 3));
type TestC = class
public
function FuncA(a, b, c);
begin
return a + b + c;
end;
function FuncB(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
function Dispatch(kind, ...);
begin
if kind = "Int" then
begin
return FuncA(...);
end
else
begin
return FuncB(...);
end
end;
end;
function DoInvoke(fc_name, ...);
begin
obj := new TestC();
return invoke(obj, fc_name, 0, ...);
end;
```
结果说明:
- `DoInvoke("Dispatch", "Int", 2, 20, 200)` 输出 `222`
- `DoInvoke("Dispatch", "Other", 1, 2, 3)` 输出 `123`
代码块身份:输出片段
```text
222
123
```
### 匿名函数与函数指针
匿名函数变量:
代码块身份:可直接照写示例
```tsl
a := function(x, y)
begin
return x + y;
end;
writeLn(call(a, 1, 2));
writeLn(##a(5, 6));
```
结果说明:
- `call(a, 1, 2)` 输出 `3`
- `##a(5, 6)` 输出 `11`
代码块身份:输出片段
```text
3
11
```
匿名函数也可以直接作为参数传入:
代码块身份:可直接照写示例
```tsl
writeLn(Apply(function(x, y)
begin
return x * y;
end));
function Apply(fun);
begin
return call(fun, 2, 3);
end;
```
结果说明:
- 输出 `6`
代码块身份:输出片段
```text
6
```
函数指针变量也可以通过 `findFunction(...)` 拿到:
代码块身份:可直接照写示例
```tsl
f := findFunction("Add");
writeLn(##f(1, 2));
function Add(a, b);
begin
return a + b;
end;
```
结果说明:
- 输出 `3`
代码块身份:输出片段
```text
3
```
也可以通过 `thisFunction(...)` 从已知函数名直接拿到:
代码块身份:可直接照写示例
```tsl
f := thisFunction(Add);
writeLn(Call(f, 3, 4));
function Add(a, b);
begin
return a + b;
end;
```
结果说明:
- 输出 `7`
- 说明 `thisFunction(Add)` 可以得到稳定可调用的函数值
代码块身份:输出片段
```text
7
```
直接 `f(...)` 不要当成可靠写法:
代码块身份:反例 / 不可照写
```text
// 匿名函数变量直接调用
a := function(x, y)
begin
return x + y;
end;
writeLn(a(7, 8));
```
结果说明:
- 上面这种匿名函数变量直调会报 `function:<name> compile error or not found`
- `findFunction(...)``thisFunction(...)` 返回的函数值也不要写成 `f(...)` 直调
- 因此本页只把 `call(f, ...)``##f(...)` 写成稳定规则
### `::` 指向全局函数
当当前作用域里有同名局部函数时,可以用 `::FuncName(...)` 指定去调全局/系统函数:
代码块身份:可直接照写示例
```tsl
r := Demo();
writeLn(r[0]);
writeLn(r[1]);
function Demo();
begin
return array(strToInt("123"), ::strToInt("123"));
end;
function strToInt(s);
begin
return 888;
end;
```
结果说明:
- 第一项 `strToInt("123")` 命中局部函数,输出 `888`
- 第二项 `::strToInt("123")` 命中全局系统函数,输出 `123`
- 说明 `::` 可以绕过当前作用域的同名局部函数,直接指向全局/系统函数
代码块身份:输出片段
```text
888
123
```
如果 `::FuncName(...)` 指向的全局函数本身不存在,会在运行时报 `function:<name> compile error or not found`
### 系统交互专题
`external`、原生函数指针包装、`makeInstance` / C 回调和线程调用,统一见 [18_external_calls_and_threads.md](18_external_calls_and_threads.md)。这一篇只保留“普通函数怎样定义和调用”的主线。
## 默认生成模板
如果你只是要写一个能被智能体稳定续写的 `.tsl` 脚本,从语句区起步,需要函数时把声明区放在后面:
代码块身份:可直接照写示例
```tsl
Hello();
function Hello();
begin
echo "hello";
end;
```
如果你要写 `.tsf` 模块/函数扩展,默认从 `function` 骨架起步:
代码块身份:可直接照写示例
```tsl
function HelloValue();
begin
return 1;
end;
```
只有用户明确要求 `procedure` / 过程时,才用下面这个 `.tsf` 骨架:
代码块身份:可直接照写示例
```tsl
procedure HelloProc();
begin
end;
```
## 禁止项
-`.tsl` 的声明区后面继续写脚本语句。
- 用户只说“写一个函数”时,默认改成 `procedure`
- 因为任务没有返回值,就自动改成 `procedure`
- 为了简写主动省略函数头后的分号;默认保留分号。
- 以为 `procedure` 只是 `function` 的别名,不涉及参数传递语义。
- 带类型注解时仍然用逗号分隔参数。
- 没有类型名证据时,发明说明性参数类型或返回类型。
- 以为默认未修饰参数天然就是按值传递。
-`a = 1` 这种比较表达式误当成命名参数调用。
- 以为默认值只能用于无类型参数。
-`const` 形参上直接赋值。
- 把普通函数的默认值规则原样套到 `unit interface` 里的 `const` 默认参数上。
- 把匿名函数或 `findFunction(...)` 返回值默认写成 `f(...)` 直调。
- 把命名参数直接套到二进制函数或系统函数上。
- 在一次调用里先进入命名参数模式,后面又退回位置参数。
代码块身份:反例 / 不可照写
```text
a := 1;
Add(1, 2);
function Add(a, b);
begin
return a + b;
end;
echo "after function";
```
上面的问题不在 `Add` 本身,而在于 `.tsl` 的函数声明区后面又继续出现脚本语句。正确做法是把会执行的语句全部放在声明区之前。
代码块身份:输出片段
```text
invalid statement
```
代码块身份:反例 / 不可照写
```text
function Demo(a: integer, b: integer);
begin
return a + b;
end;
```
上面这种写法会编译失败;参数一旦带类型,分隔符应改成分号。
代码块身份:反例 / 不可照写
```text
Pack(a = 1, b = 2);
```
这类写法不要当成命名参数。它虽然可能编译通过,但文档结果不符合命名参数语义,不能当成可靠的命名参数语法。
代码块身份:反例 / 不可照写
```text
function Demo(a: integer, b: integer = 2): integer;
begin
return a + b;
end;
```
上面这种写法也不对;参数一旦带类型,分隔符仍然应保持分号。
代码块身份:反例 / 不可照写
```text
function TouchDefault(a);
begin
a := 9;
end;
```
不要把上面这种未修饰参数自动理解成“按值传递”。按文档边界,它会把调用方实参改掉;如果任务要求按值语义,先看 `{$varByRef-}``in` / `out` 的例子。
代码块身份:反例 / 不可照写
```text
function Bad(const x);
begin
x := 2;
end;
```
上面这种写法会编译失败;`const` 形参不能被重新赋值。
代码块身份:反例 / 不可照写
```text
unit UnitConst;
interface
const default_value = 888;
function F(a, b = 100, c = default_value);
```
不要把上面这种 `unit interface` 声明直接当成已经等价于普通函数默认参数规则。按文档结果,对应的 `F(1)` 输出是 `101`,不是按 `default_value = 888` 补成的结果;具体边界见 [09_units_and_scope.md](09_units_and_scope.md)。
代码块身份:反例 / 不可照写
```text
a := function(x, y)
begin
return x + y;
end;
writeLn(a(7, 8));
```
不要把上面这种匿名函数变量的直调写法直接当成可靠规则。它会报 `function:a compile error or not found`;稳定写法仍然是 `call(a, 7, 8)``##a(7, 8)`
代码块身份:反例 / 不可照写
```text
writeLn(intToStr(value: 200));
```
不要把上面这种写法直接套到二进制函数或系统函数上。它会报 `named parameter mode can't support here`;如果确实要命名传参,先用 TSL 函数再封一层。
代码块身份:反例 / 不可照写
```text
function Pack(a, b, c);
begin
return a * 100 + b * 10 + c;
end;
begin
writeLn(Pack(a: 1, 2, c: 3));
end.
```
不要在同一次调用里“先进入命名参数模式,再退回位置参数”。这类写法会直接编译失败,错误信息包含 `paramname: not found`