aardio 知识库精简版


此文件为 aardio 官方提供的语法文档、范例、教程、常见问题解答的浓缩精简版。
原文网址: https://www.aardio.com/doc/ai-knowledge-min.html 。

本文件为 Markdown 格式,由多个 Markdown 文件合并而成,文件之间使用 Markdown 分隔符 `---` 分开。
此文件可作为 AI 大模型的知识库,此知识库仅为部分 aardio 知识而并不包含全部的 aardio 知识。

请正确拼写 aardio : 在任何时候都要全小写 aardio,不要单独大写 aardio 的首字母。 

----


# aardio 编程语言快速入门 —— 语法速览


##  第一个控制台程序

```aardio

//导入库
import console; 


//加载动画
console.showLoading("加载动画测试");
sleep(1000); //暂停 1 秒


//输出字符串
console.log('测试字符串');


//输出变量
console.dump({a=123;b=456});


//以 JSON 格式输出变量
console.dumpJson({a=123;b=456});


//暂停并等待按键
console.pause(true);
```

##  第一个窗口程序  

```aardio
import win.ui;
var winform = win.form(text="aardio form")
winform.add({
    button={cls="button";text="测试";left=435;top=395;right=680;bottom=450;color=14120960;font=LOGFONT(h=-14);note="点这里";z=2};
    edit={cls="edit";left=18;top=16;right=741;bottom=361;edge=1;multiline=1;z=1}
})

//按钮回调事件
winform.button.oncommand = function(){
    winform.edit.text = "测试"; //修改控件属性
}

//显示窗口
winform.show();

//启动界面消息循环,保持窗口显示,并响应用户操作
win.loopMessage();
```

##  调用静态 API

```aardio
//加载 DLL
var dll = raw.loadDll("user32.dll");


//调用 API 函数
dll.MessageBox(0,"测试","标题",0);
```

aardio 已经默认加载了一些常用的系统 DLL 对象,例如 `::User32`, `::Kernel32`, `::Shell32`,`::Ntdll` 等。  

  

所以上面的代码可以简化为:

```aardio
::User32.MessageBox(0,"测试","标题",0);
```

##  静态结构体

```aardio
var info = {
    INT size = 8;
    INT time;
}
::User32.GetLastInputInfo( info )
```

因为 aardio 函数支持多返回值,且结构体默认为传址输出参数 —— 会添加到返回值中,所以上面的代码也可以这样写:

```aardio
var ok,info = ::User32.GetLastInputInfo( {
    INT size = 8;
    INT time;
} );
```

##  注释语句

```aardio
// 单行注释


/*  
多行注释,首尾星号数目必须相同。
*/
```

##  变量、赋值

```aardio
var n = null;
var str = "字符串";
```

aardio 中的变量默认为当前名字空间变量,使用 var 语句可以定义局部变量。  

  

##  语句、语句块

```aardio
{
    var n = null;
    var str = "字符串";
}
```

语句尾部可以加分号也可以不加。

语句块首尾加 `{` 与 `}` 标记,`var` 语句支持语句块作用域。

  

##  数值

```aardio
var num = 123.01; //10 进制数值
var hex = 0xEFEF; //16 进制数值
var num2 = 123_456; //可用下划线作为分隔符
var num3 = 2#010; //可在 # 号前自定义进制
```

##  算术运算

```aardio
var num = 3 + 2; //加,值为 5
var num = 3 - 2; //减,值为 1
var num = 3 * 2; //乘,值为 6
var num = 3 / 2; //除,值为 1.5
var num = 3 % 2; //取模,值为 1
var num = 3 ** 2; //乘方,值为 9
```

##  位运算

```aardio
import console;


//按位取反
var n = ~2#101;
//显示2#11111111111111111111111111111010
console.printf("2#%b", n)


//按位与
var n = 2#101 & 2#100;
//显示2#100
console.printf("2#%b", n)


//按位或
var n = 2#101 | 2#110;
//显示2#111
console.printf("2#%b", n)


//按位异或
var n = 2#101 ^ 2#110;
//显示2#011
console.printf("2#%b", n);


//按位左移
var n = 2#101 << 1;
//显示2#1010
console.printf("2#%b", n);


//按位右移
var n = 2#101 >> 1;
//显示2#10
console.printf("2#%b", n);


//按位无符号右移
var n = -2 >>> 18;
//显示 16383
console.log(n);


console.pause();
```

##  字符串

```aardio
var str = "普通字符串。
可直接包含换行符,不能包含回车符。
可以用 "" 表示一个双引号。";


var str2 = `反引号的作用与双引号一样。
可直接包含换行符,不能包含回车符。
可以用 `` 表示一个反引号。`;


var str3 = '转义字符串
忽略回车换行,反斜杆作为转义符使用
例如 \r\n 表示回车换行';


var str4 = /*
段注释可以赋值为字符串。
首尾星号数目必须配对。
忽略首尾紧邻星号的第一个空行(如果有的话),
其他换行总是会规范化为回车换行符,
也就是 '\r\n'。
*/


var str5 = //行注释也可赋值为字符串,不含回车换行
//在文件路径前加 $ 符号,可将该文件编译为字符串


var str6 = $"~/aardio.exe"
```

##  二进制字符串、文本编码

```aardio
var bin = '字符串可以包含任意二进制数据,例如 \0'


var utf8 = "字符串可以包含文本,默认为 UTF-8 编码"


var utf16 = '转义字符串后加 u 字符表示 UTF-16 编码字符串'u
```

aardio 在很多地方都支持自动编码转换,例如调用 Unicode(UTF-16) 版本的 API 函数时,UTF-8 字符串可自动转换为UTF-16 编码字符串( 支持双向自动转换),例如以下两句代码的作用是相同的:

```aardio
::User32.MessageBox(0,"内容","标题",0);


::User32.MessageBox(0,'内容'u,'标题'u,0);
```

在调用 `::User32.MessageBox` 时,aardio 会自动检测并优先使用 Unicode 版本的 `::User32.MessageBoxW` 函数。当然,也可以主动在 API 函数名后加上大写的 `W` 尾标声明这是 Unicode 版本函数(即使该函数名尾部并没有 `W`,也可以添加 `W` 尾标)。

  

##  字符串连接

```aardio
//使用 ++ 操作符连接字符串
var str = "字符串1" ++ "字符串2";


//如果 ++ 前后有引号,可省略一个 + 号。
var str = "字符串1" + "字符串2";


//用 string.concat() 函数连接支持多参数,支持 null 值
var str = string.concat("字符串1","字符串2")
```

##  读写文件

```aardio
//写文件
string.save("/test.txt","要保存在文件的字符串");


//读文件
var str = string.load("/test.txt");
```

##  文件路径

```aardio
/*
文件路径开始可用单个斜杠(或反斜杠)表示应用程序根目录。
应用程序根目录在开发时指工程目录,发布后指 exe 目录。
*/
var str = string.load("\res\test.txt");


//文件路径中正斜杠可自动转换为反斜杠
var str = string.load("/res/test.txt");


/*
文件路径开始用波浪线表示 exe 所在目录。
aardio 不需要生成 exe 就可以运行调试,此时波浪线表示 aardio.exe 所在目录。
*/
var str = string.load("~/res/test.txt");


/*
aardio 很多读文件的函数都兼容工程内嵌资源目录。
不需要修改代码,所以 "\res\test.txt" 可以是资源目录。
如果读取文件失败,string.load() 会返回 null 值(不报错)。
*/
var str = string.load("\res\test.txt");


/*
将文件转换为完整路径。
将路径传给第三方组件时,建议这样转换一下。
*/
var path = io.fullpath("\res\test.txt");
```

注意在 aardio 中双引号内不需要转义反斜杠,所以写为 `"\\res\\test.txt"` 是错的。单引号内才需要使用转义符,例如 `'\\res\\test.txt'` 等价于  `"\res\test.txt"`。


在 aardio 中一般不必要使用 `"./dir/test.txt"` 这样的相对当前目录路径。因为当前目录可以任意更改,你不知道什么时候某个第三方组件是不是悄悄帮你改了当前目录。


##  嵌入文件到字符串

  

在文件路径前加上 $ 操作符可将该文件编译为字符串对象。

在编译该代码时文件必须存在,程序发布后就不再需要这个文件了。

  

示例:

```aardio
var str = $"\dir\test.txt";
```

不要用这个方法包含资源目录下的资源文件,  

因为这等于将同一个文件重复包含了多次,会不必要地增加发布体积。

  

##  名字空间

```aardio
//导入其他库文件,同时也导入该库创建的名字空间
import console;


//打开名字空间
namespace test.a.b{
   
    //定义当前名字空间变量
    console = 123;
   
    //..console 等价于 global.console
    ..console.log(console);
}


console.pause(true);
```

默认名字空间为全局名字空间,也就是 global 名字空间。在其他名字空间访问 global 名字空间必须在变量前加上两点( .. ) 。例如 global.console 等价于 ..console 。

  

在 aardio 中使用 import 导入其他库文件,同时也导入该库创建的名字空间。导入名字空间只是引入模块,访问该名字空间仍然要写完整的名字空间路径。

  

##  表 (table)

  

table(表)是aardio中唯一的复合数据类型,除了非复合的基础数据类型以外,aardio 中几乎所有的复合对象都是表,即使是变量的命名空间也是表。表的本质是一个集合,可以用于容纳其他的数据成员,并且表也可以嵌套的包含其他的表(table),在aardio里表几乎可以容纳一切其他对象。

  
1.  表可以包含键值对

    ```aardio
    tab = {
        a = 123;
        str = "字符串";
        [123] = "不符合变量命名规则的键应放在下标内。";
        ["键 名"] = "不符合变量命名规则的键应放在下标内。";
        键名 = {
            test = "表也可以包含表";
        }
    }
    ```

2.  表可以包含有序数组

    如果表中不定个数的成员的“键”是从1开始、有序、连续的数值,那么这些成员构成一个有序数组。aadio 中如果不加特别说明,数组一般特指有序数组,所有数组函数默认都是用于有序数组。

    ```aardio
    //在表中创建数组
    var array = {
        [1] = 123;
        [2] = "数组的值可以是任何其他对象";
        [3] = { "也可以嵌套包含其他表或数组"}
    }
    
    //有序数组的键可以省略,下面这样写也可以(并且建议省略)
    var array = {
        123;
        "数组的值可以是任何其他对象";
        { "也可以嵌套包含其他表或数组"}
    }
    ```

3. 表可以包含稀疏数组

    如果表中包含的成员使用了数值作为键,但是多个成员的键并不是从1开始的连续数值 - 则构成稀疏数组。在 aardio 一般我们提到的数组 - 如果未加特别说明则是特指有序连续数组(不包含稀疏数组)。
    
    如果表中包含了稀疏数组 - 也就是说成员的数值键(索引)包含不连续的、中断的数值,那么不应当作为有序数组使用。 aardio 中几乎所有针对数组操作的函数或操作符 - 如果未加特别说明都要求参数是有序数组。

    下面的数组就包含了 null 值,属于数值键(索引)不连续的稀疏数组:  
    `var array = { "值:1", null, "值:2", "值:4", null, null,"其他值" }`

4. 表的常量字段

    注意如果键名以下划线开始,则为常量字段(值为 非 null 值时不可修改) —— 可通过元属 `_readonly` 禁用此规则(参考语法手册)。

5. 表的类 JavaScript 写法

    如果表不是一个声明静态类型的结构体,那么在表中允许使用类 JavaScript 语法,用冒号代替等号分隔键值,用逗号代替分号分隔元素,并可用引号包含键名。

    示例:

    ```aardio
    var tag ={"name1":123,"name2":456}
    ```

    上面包含键名的双引号可以省略,这种写法的表作为函数参数使用时不可省略外面的大括号 `{}` 。

6. 表作为函数参数的简写法

    表作为唯一函数调用参数使用时,可省略最外层的 `{}` ,例如 `console.dump({a = 123,b=456})` 可以简写为 `console.dump(a = 123,b=456)`。如果

    但如果表使用类 JavaScript 语法用冒号代替等号分隔键值,则不能省略外层的外层的 `{}` 。

    例如:`console.dump({a : 123,b : 456})` 或者 `console.dump({"a" : 123,"b" : 456})` 都不能省略外层的  `{}` 。

  

##  成员操作符

```aardio
var tab = {a = 123; b = 456};


/*
用点号访问表成员,点号后面必须跟合法的标识符
*/
var item = tab.a;


/*
用下标操符符 [] 访问表成员,[] 里可以放任何表达式。
*/
var item = tab["a"];


/*
用直接下标操符符 [[]] 访问表成员,[[]] 里可以放任何表达式。
直接下标不会触发元方法,用于 null 值也会返回 null 而不是报错。
*/
var item = tab[["a"]];
```

##  null 值  

未定义的变量值默认为 null 。  
  
获取表对象不存在的成员值时也会返回 null 而不会报错。如果对象本身是 null 值,使用点号、普通下标获取成员值会报错,改用直接下标 \[\[\]\] 则不会报错,而是会返回 null 值。

  

用 # 操作符获取 null 值长度返回 0 而不是报错。

  

将表对象的成员赋值为 null 就可以删除该成员。

  

在逻辑运算中,null 值为 false。

  

##  取数组或字符串长度

```aardio
import console;


var array = {123;456};
console.log("数组长度",#array);


var str  = "abcd";
console.log("字符串长度",#str);


var n  = null;
console.log("null 值长度为 0",#n);


console.pause();
```

使用 `#` 操作取数组或字符串的值,也可以取 null 值的长度( 返回 0 )。

  
##  if 语句

```aardio
import console;
var enabled = true;


if (enabled) {
  console.log('enabled 的值为 true');
}
console.pause(true);
```

注意在逻辑运算中,非 `0`、非 `null`、非 `false` 为 `true`,反之为 `false`。

  

如果要准确判断一个变量的值是否为 `true` 或 `false` ,则应使用恒等操作符,如下:

```aardio
import console;
var enabled = false;


if (enabled === false ) {
  console.log('enabled 的值为 false,不是 0,也不是 null');
}
elseif( enabled ){
  console.log('enabled 的值为真');
}
else{
  console.log('enabled 值为:',enabled);
}
console.pause(true);
```

上面的 `elseif` 也可以改为 `else if`。

  

##  逻辑操作符

  

逻辑非 既可以用 `not` 也可以用 `!` 。

逻辑与 既可以用 `and`  也可以用 `&&` 或者 `?` 。

逻辑或 既可以用 `or`  也可以用 `||` 或者 `:`  。

  

aardio 语句在设计时试图兼容其他语言的一些基本语法,所以很多语法有类似上面的替代写法,例如可以用 `begin end` 替代 `{ }` 包含语句块。

  

##  三元运算符

```aardio
var n = 1;


// ret 值为 true
var ret = n ? true : 0;
```

要特别注意 `?` 实际上是逻辑与操作符,`:` 实际上是逻辑或操作符。如果 `a ? b : c` 这个表达式里 `b` 为 `false`,则该表达式总是返回 `c`。这与其他类 C 语言的三元操作符不同,请注意区分。

  

##  定义函数

```aardio
//定义函数:
add = function(a, b) {
  return a + b,"支持多个返回值";
}


//调用函数
var num,str = add(1, 2);


/*
要特别注意,函数可以返回多个值。
可以用 () 将多个返回值转换为单个调用参数。
*/
var str = tostring( ( add(1, 2) )  );
```

## 计数 for 循环语句( Counter-controlled for )

计数 for 循环语句基本结构:  
  
```aardio
for currentValue = initialValue;finalValue;incrementValue {
    //循环体(Code block)
}
```  

循环变量:  
- 循环变量 currentValue 的值从 initialValue 开始,到 finalValue 结束,每次递增 incrementValue。
- 循环变量的值自动更新不用写自增自减语句。
- 循环变量是一个作用域限于循环体内部的局部变量。 

循环参数:
- initialValue:起始值。
- finalValue:结束值(而非条件表达式)。
- incrementValue:步长值(而非递增/递减语句),可以使用负数步长。
全部循环参数都必须是数值表达式,不能是关系表达式或自增自减操作。
所有参数都在循环开始前计算一次,循环过程中不会重新计算。
可选在循环参数首尾添加一对 `()`。

循环体:
- 循环体可以是用 `{}` 包含的语句块,也可以是一个单独的语句。

以下为计数 for 循环演示:  

```aardio
import console;

// 循环变量 i 从起始值 1 循环到终止值 10 ,每次递增步长为 2。
for( i=1;10;2 ){ 
    
    // 在控制台输出 1,3,5,7,9
    console.log(i); 
}

// 循环变量 i 从起始值 1 循环到终止值 10 ,每次递增步长为 1。
for( i=1;10;1 ){
        
    i++; // 修改循环变量的值,使步长变为 2
    
    // 循环变量设为无效数值,下次循环以前自动修复为计数器值
    i = "无效数值" 
}

// 循环变量 i 从起始值 10 循环到终止值 1 ,每次递减步长为 1。
for( i=10;1;-1 ){  
    
    // 在控制台输出 10,9,8,7,6,5,4,3,2,1
    console.log(i); 
}

console.pause()
```  

##  for in 泛型循环语句(Generic for)

```aardio
import console;

var tab = {
    a = 123;
    b = 456;
}

for(k,v in tab){
    console.log(k,v)
}

console.pause();
```

##  while 循环语句

```aardio
import console;

var i = 0;
while (i < 100) {  
    i++;
         
    console.log(i);
   
    if(i==10){
        break; //中止循环
    }
}
 
console.pause();
```

##  while var 语句

```aardio
import console;


//用法1,while 条件中使用一个 var 语句
while(
    var i =  console.getNumber( "请输入数值,输入0退出:" )
    ) {
    console.log( i )
}


//用法2,while 条件中使用 var 语句、循环前执行语句、条件判断语句
while(
    var i;
    i =  console.getNumber( "请输入数值,输入100退出:" );
    i != 100  ) {
    console.log( i )
}


//用法3,省略 var 语句,仅使用循环前执行语句、条件判断语句
var i = 0;
while( ;i++; i<10 ) {
    console.log(i)
}


//用法4,省略 var 语句,循环前执行语句,仅使用条件判断语句
while(i>0){
    i--;
    console.log(i);
}


console.pause(true);
```

##  while var 模拟 for 循环

```aardio
import console;


while( var i = 0; i++ ; i < 5  ) {
    console.log( i )
}


console.pause(true);
```

##  break label, continue label, break N, continue N 语句

break 语句中断并退出当前循环语句。

continue 语句跳过当前循环体剩下的部分,继续执行下一次循环。


break label,continue label 通过为循环指定一个标号以中断指定的循环。

break N,continue N 通过指定循环语句的嵌套层次以中断指定的循环。

```aardio
import console;


var i, j;
for (i = 0; 2; 1 ) { loop1:


   for (j = 0; 2; 1) { loop2:
   
      if (i == 1 && j == 1) {
         continue loop1;
      }
      else {
         console.log("i = " + i + ", j = " + j);
      }
   }
}
console.pause(true);
```

注意循环标号写在循环体内部开始处。

  

##  类

```aardio

//定义类
class cls{
   
    //构造函数
    ctor(name,...){
        this.name = name;
        this.args = {...}
    };
   
    //属性
    value = staticNum;
   
    //成员函数
    add = function(v){
        this.value = this.value + v;
        return this.value;
    }
}




//打开类的名字空间
namespace cls{
    staticVar = "类的静态变量值";
    staticNum = 2;
}
 
//调用类创建对象
var obj = cls();


//调用对象函数
var v = obj.add(5);
```

类内部可以用 `this` 访问当前创建的实例对象。

类总是先调用构造函数 `ctor` 才会初始化其他成员。

  

每个类都有自己的名字空间。

类创建的所有实例共享同一名字空间。

类名字空间的成员也就是类的静态成员。

  

## self,this,owner 对象

  

`self` 表示当前名字空间。

`this` 在类内部表示当前创建的实例对象。

  

每个函数都有自己的 `owner` 参数( 无法使用外部函数的 `owner` 参数 )。如果用点号作为成员操作符获取并调用对象的成员函数,则点号前面的对象是被调用成员函数的 `owner` 参数,否则被调用函数的 `owner` 参数为 `null`。例如 `obj.func()` 调用 `obj` 的成员函数 `func` ,则 `obj` 是 `obj.func` 的 `owner` 参数。

  

注意一个独立的 aardio 代码文件编译后也相当于一个匿名的函数。独立运行的 aardio 代码文件 `owner` 参数默认为 `null` ,使用 `import` 语句加载的库文件, `owner` 参数为加载路径、或资源文件数据。

  

`owner` 在元方法中表示左操作数。

  

在迭代器函数中, `owner` 表示迭代目标对象。

  

aardio 允许用 `call`, `callex`, `invoke` 等函数调用其他函数并改变 `owner` 参数的值。


---


# aardio 之特殊符号用法大全   
 
## `{}` 表构造器、语句块标记

{} 可用作包含[语句块](https://bbs.aardio.com/doc/reference/the%20language/statements/blocks.html)的首尾标记,语句块一组顺序执行的[语句](https://bbs.aardio.com/doc/reference/the%20language/basic%20syntax.html#statement)组成,并可创建独立的[局部变量](https://bbs.aardio.com/doc/reference/the%20language/variables%20constants.html)作用域,局部变量拥有最高的存取优先级,查找同名变量时将优先搜索当前语句块的局部变量,例如下面的代码创建了多个语句块:  
  
```aardio

import console;

//创建函数体  
var func = function(cond){    

	//声明局部变量 
	var v = 1;  
    
    //条件语句  
    if(cond){        
    	//声明局部变量 
    	var v = 2;  
        console.log(v);  
    }  
}  
  
func(true);  
console.pause(true);
```


`{}` 在表达式中用于构造[表对象](https://bbs.aardio.com/doc/reference/the%20language/datatype/table.html),[表](https://bbs.aardio.com/doc/reference/the%20language/datatype/table.html)是 aardio 中唯一的复合数据类型,  
即可用作哈希表,也可以同时包含有序数组、稀疏数组等成员。声明字段的[静态类型](https://bbs.aardio.com/doc/reference/libraries/kernel/raw/datatype.html)时 —— 还可以表示[结构体](https://bbs.aardio.com/doc/reference/libraries/kernel/raw/struct.html)。  
  
表中默认用 `;` 号分隔成员( 可用逗号替代 ),默认用 `=` 号分隔键值对成员(表如果不是声明静态类型的结构全,则可用 `:` 号替代 `=` 号分隔键值 ),示例:  

```aardio
var object = { name1 = "abc"; name2 = 123 }  
var object2 = { ["name1"]="abc", ["name2"]=123 }  
var object3 = { name1:"abc", name2:123 }  
var object4 = { "name1":"abc", "name2":123 }  
var array = { 1;2;3 }var array2 = { 1,2,3 }var objectAndArray = { name1 = "abc"; name2 = 123; 1;2;3 }
```
  

虽然在 aardio 中以上构造表的语法都是正确的,但一般建议大家使用 `;` 号分隔元素,并使用 `=` 号分隔键值对。

  

## `.`  成员操作符 

用于访问对象的成员,  
例如 io.open 表示 open 函数是 [io](https://bbs.aardio.com/doc/reference/libraries/kernel/io/io.html)对象的成员( 这里是名字空间成员 )

  
## `..` 全局成员操作符


这个操符符用在自定义的[名字空间](https://bbs.aardio.com/doc/reference/the%20language/namespace.html)里访问全局名字空间 global;  
例如 `..io.open()` 实际上等价于 `global.io.open()`

  
## `::` 全局常量操作符


这个操符符用于将一个变量名转换为 global名字空间下的[全局常量](https://bbs.aardio.com/doc/reference/the%20language/variables%20constants.html#const)名 - 并保护该常量在其后加载的代码中一旦赋为非空值后即不可修改,例如:  

```aardio
::Kernel32 := raw.loadDll("Kernel32.dll");  
```

全局常量需要遵守以下使用规则  

*  全局常量名首字母大写(以区别普通变量 )。  
     aardio默认定义了少量全局常量函数名 - 这些关键函数全部小写首字母,但在aardio编辑器中显示为蓝色高亮状态。  
  
*  当一个变量被定义为全局常量,赋于非空值以后其值即不能再随意更改 。  
     全局常量一般使用 `::Name := 初始值` 赋值,等价于使用 `::Name = ::Name or 初始值 ` 以避免重复初始化。  
  
*  ::的作用域是根据代码的载入顺序向下向后的,所以在同一文件中已定义的全局常量名时再次使用可以省略`::`前缀,但因为代码文件的载入有不确定性 -  在其他文件首次使用该常量名时仍然应该使用`::`定义一次。

 
## `#` 计数操作符
 

> 当 [\# 操作符](https://bbs.aardio.com/doc/reference/the%20language/operator/len.html) 在一个数组前面时返回数组长度,在字符串或buffer对象前面时返回字节数。  
> 注意Unicode/UTF16/UTF8字符串用#取到的都是字节数而不是字符数,UTF8字符串使用string.len()函数可以获取字符数,而UTF16字符串只要简单的用字节数除以2就可以得到字符数。  
>   
> 如果 `#`在一个单引号包含的字符后面,计算并返回字符的 ASCII 码,例如 `'a'#` 返回 数值`97`  
> 当`#`放在数字中间时,用来表示自定义进值,例如 `2#101` 表示2进制的101


## `[]` 下标操作符( 或者叫索引操作符 ) 

也是用来访问对象的成员,中括号里面应当是一个合法的表达式。  
例如 `io.open` 用索引索作符来表示就是 `io["open"]` 。
          
而 [. 成员操作符](https://bbs.aardio.com/doc/reference/the%20language/operator/member.html) 这里的成员名字不需要放到引号里,并且必须是一个合法的变量名。  
但索引操作符就不同了,可以放任意的表达式,例如 io\[ "o" + "pen" \] 这样写也是可以的。  
          
另外一个区别:当你使用索引操作符调用成员函数时,被调用函数的 owner 参数为空。  
所以一般不应当这样写 io\["open"\]() ,而应当写 io.open()。  
  
下标操作符也可以用于字符串、或buffer对象,返回的是指定位置的字节码(数值),例如:  
  
```aardio
import console;  

var str = "test测试"  
var wstr = 'Unicode/UTF16宽字符串'u  
var buf = raw.buffer("abc测试");  
console.log( str\[1\], wstr\[1\], buf\[1\] );

console.pause(true);  
```
  
要特别注意的是:  
Unicode/UTF16字符串使用下标操作符返回的是宽字节码(2个字节组成的16位数值)。  
如果需要返回字符而不是字节码,需要改用下面会讲到的直接下标操作符 \[\[ \]\]

## `[[]]` 直接下标操作符 


这个操作符与 \[\] 的用法基本是一样的,  
唯一的区别是他不会触发元方法,所以数组里实际有这个成员就是有,没有就是没有,忽悠不了这个操作符。  
  
这个[直接下标操作符](https://bbs.aardio.com/doc/reference/the%20language/operator/member.html)可以应用于任何类型的对象( 包括null空值 )不会报错,  
如果对象不包含直接下标操作符中指定的成员就简单的返回 null空值。所以\[\[\]\]即可以用来取值同时又可以方便的检测对象类型,例如:  
  
```aardio
if( 可能为空或任意类型的变量[["test"]] ){
        io.print(  可能为空或任意类型的变量[["test"]] )
}
```
  
不要小看这个操作符 - 使用频率非常高,而且可以节省不少的代码。  
最近Javascript, Typescript里一个炒的很火的新语法“可选链”跟这个直接下标有点像,解决的也是类似的问题,实际上 aardio十几年前就有这些了。  
  
将普通下标操作符用于字符串时, \[\]操作符取的是字节码、是个数值,而 \[\[\]\] 取出来的是字符。  
例如定义字符串变量 str = "abcd" 这时候 str\[1\] 是他的ASCII码97,而str\[\[1\]\]则返回字符"a"  
  
对于Unicode/UTF16字符串,\[ \] 操作符取的是宽字节码( 以2个字节为单位的16位数值 ),而 \[\[ \]\] 操作符返回的是宽字符( 也是以2个字节为单位的单个Unicode字符 ),但使用 #取长度时就总是返回字节长度 - 这个要注意区别,下面看一个完整例子。  


```aardio
import console;var ws = 'abc中文'u;

for(i=1;#ws/2 ){
    console.log("Unicode字节码", ws[ i ], "Unicode字符",ws[[ i ]] )
}
console.pause(true);
```


## @**元表操作符**


这个操作符表来读取或设置对象的[元表](https://bbs.aardio.com/doc/reference/libraries/kernel/table/meta.html), 关于这个请查看[帮助文档](https://bbs.aardio.com/doc/reference/libraries/kernel/table/meta.html)  

一个简单的示例  
  
```aardio
var tab = { a = 123 };

tab@ = {
    _get = function(name){        return "有木有:" + name;
    }
}

io.open()
io.print( tab.a ) // -> 显示123
io.print( tab.b ) // -> 显示"有木有:b"
io.print( tab[["b"]] ) // -> 显示 null空值
execute("pause")
```


@ 在[模式匹配](https://bbs.aardio.com/doc/reference/libraries/kernel/string/pattern%20syntax.html)里还有特殊用途。

如果一个模式表达式的第一个字符是‘@’,表示全局禁用模式语法执行普通的二进制匹配。

如果一个模式表达式的第一个字符是两个'@@',同上表示禁用模式语法并且执行文本匹配(不会截断宽字符。)

也可以在模式表达式的尖括号中使用一个'@" 或两个 '@@' 表示局部禁用模式语法( 两个‘@@’ 表示启用文本匹配,并且忽略大小写 )

  
## 示例如下: 

```aardio
var  str = "abc\d测试字符串"

//模式匹配
var i,j = string.find(str,"\\d")

//禁用模式匹配
var i,j = string.find(str,"@\d")

//禁用模式匹配、且启用文本匹配
var i,j = string.find(str,"@@\d")

//局部禁用模式匹配
var i,j = string.find(str,"<@\d@>:+")

//局部禁用模式匹配、且启用文本匹配、并且忽略大小写
var i,j  = string.find(str,"<@@\D@>:+")

```

## `_` 下划线


如果在一个[成员变量](https://bbs.aardio.com/doc/reference/the%20language/variables%20constants.html#const)的前面加上下划线,则声明该变量的值为只读,在赋值后不可修改  
例如 \_version = 123 你就不能在后面再修改为 \_version = 456, 这种习惯在其他编程语言中通常只是一种书写习惯,但是在aardio则是语法级别的强制约束。  
  
如果下划线后面的变量名全部大写,则表示全局只读的常量  
例如 \_VERSION = 123 表示他在所有名字空间都有效  
  
另外数值的字面值允许加入下划线作为数值分隔符,  
例如 123\_456 等价于 123456, 2#1010\_1100 等价于 2#10101100,  
数值分隔符不能使用连续多个下划线,并且不能在字符串中使用数值分隔符,例如:  
  
```aardio
tonumber("123_456") //返回的是123
("123456") + 1 //返回的是一个数值 123457
("123_456") + 1 //会报错
```

  
##  `“\”,"/"` 应用程序根目录


在 aardio 中[文件路径](https://bbs.aardio.com/doc/reference/libraries/kernel/io/path.html)如果以单个斜杆或反斜杆开始表示[『应用程序根目录』](https://bbs.aardio.com/doc/reference/libraries/kernel/io/path.html)。


『应用程序根目录』指启动程序文件所在目录,在开发时指 aardio 工程目录,在发布后指启动 EXE 目录。


如果启动文件在工程外部,或者当前没有打开工程,则以启动文件所在目录为 『应用程序根目录』。


如果在开发环境中运行没有保存的 aardio 代码,则仍以当前工程根目录为『应用程序根目录』,如果没有打开工程,则以 aardio.exe 所在目录为 『应用程序根目录』。


## “~\”,"~/" EXE 目录


在 aardio 中如果文件以 "~" 右单个斜杠或反斜杠开始表示启动[EXE 所在目录](https://bbs.aardio.com/doc/reference/libraries/kernel/io/path.html)。

  

如果没有生成 EXE ,在开发环境中直接运行代码,EXE 目录指的是 aardio.exe 所在目录。  
  
注意单个 “~” 作为数值位运算符使用时表示[按位取反](https://bbs.aardio.com/doc/reference/the%20language/operator/bit.html)。


## `$` 包含指令符


这个[包含符号](https://bbs.aardio.com/doc/reference/the%20language/operator/include.html)挺有意思,  
只要在文件路径前面加上这个符号, 就会将该文件编译为一个普通的字符串对象.  

例如  str = $"e:/我的图像/x.jpg" ,在程序发布后,程序即可脱离原来的文件运行,因为该文件已经被编译为一个普通字符串变量并内嵌到EXE中了。  
  
如果$包含的文件路径以"~/"或"~\\"开始,并且查找文件失败,  
aardio会移除路径前面的~,转换为"\\"或"/"开头的应用程序根目录路径继续查找。  
应用程序根目录在开发时指工程根目录(工程之外的aardio文件指启动aardio文件所在目录)。  
  
反之,如果包含的文件以"/"或"\\"开始,并且查找包含文件失败,  
aardio不会在路径前面添加"~"到EXE目录下查找(EXE目录在开发时指aardio开发环境所在目录)。  
  
默认如果找不到包含文件会报错,但是如果包含文件路径前面添加一个问号,  
找不到文件时不报错而是返回null,例如: str = $"?找不到的文件"  
在aardio编辑器里,只要将资源管理器里的文件直接往编辑器里一拖就行了,会自动加上这个包含指令符。

  
## `++` 字符串连接操作符

在 aardio 里 1 + 2 的结果是数值3( 好像是废话哦 ),  
而 1 + "" + 2 的结果是字符串 "12" 这个不难理解吧?  
  
上面的 \+ "" + 可以直接缩写为 ++  
也就是说 1 ++ 2 的结果就是 字符串 "12"  
  
实际上一个加号也可以连接字符串,例如:  
"1" +  "2" 的结果是 字符串 "12"  
  
也就是说,只有在 + 号前后没有常量字符串(或者叫没出现引号)  
你需要用这个 ++ 来告诉aardio,你要做的的是[字符串连接](https://bbs.aardio.com/doc/reference/the%20language/operator/concat.html),而不是数值加运算。

  
## `//` 行注释语句

这个是比较通用的语法, 不过在aardio里有一个特殊的用法:  
行注释可以用于赋值语句中表示字符串,例如 var str = //这是一个字符串  
  
这个与双引号类似,字符串都表示字面意义,没有转义符。

  
## `/*  */` 段注释语句

这个也类似其他类 C 语言,但注意首尾的星号可以用多个、并且首尾星号的数目一定要匹配。  
aardio使用这个特性来解决注释嵌套的麻烦问题。  
  
另外,段注释也可以用在赋值语句中表示字符串,字符串都表示字面意义,没有转义符。  
而换行总是被强制解释为 '\\r\\n',以避免不同文本编辑器导致的可能混淆的结果。  
这个特性用来包含HTTP头之类严格指定回车换行的文本很方便。  
  
  
aardio中可以调用一些其他语言的源代码,通常要包含大段的其他语言的源码,放到这个段注释里赋值给字符串变量就可以了。直接复制粘帖,不需要象其他语言里一样苦闷的折腾字符串转换。

  
## 双引号

"这个用来表示  
普通字符串字面值"  

双引号里表示普通[字符串](https://bbs.aardio.com/doc/reference/the%20language/datatype/string.html)、不支持转义符。  
而且双引号里面的文件是可以换行的,换行都被强制解释为 '\\n'  
也就是说在双引号里绝对不会、也没办法出现回车符,也就是 '\\r'。

## 单引号
  
'这个用来表示转义字符串,  
例如\\r\\n表示回车换行,  
注意这后面的换行被忽略  
注意这前面的换行被忽略'  

单引号表示[字符串](https://bbs.aardio.com/doc/reference/the%20language/datatype/string.html)时支持转义符,例如'\\r\\n'表示回车换行,  
而且只能用 '\\r\\n'表示回车换行,文本本身的换行会被忽略,上面的示例中就只有一个回车换行

  
## 反引号 

\`**反引号即键盘左上角ESC键下方的按键,字符串也可以放在反引号中**,其作用与放在双引号中完全一样,通常含单引号的字符串我们用双引号,含双引号的字符串我们用单引号,那么同时包含单引号双引号的字符串呢?当然我们可以使用转义符、注释字符串,aardio10新增的反引号写法会更方便书写。\`  

上面使用是键盘左上角ESC键下方的按键输入反引号,  
反引号在 aardio 中语法作用完全等价于双引号,唯一的区别就是可以用来包含双引号字面值。

  
##  `**` 乘方运算 

例如 2 的3次方写为 2 \*\* 3

  
## % 取模运算 

例如24小时制的19点转换为12时制请问是几点, 就可以写为 19 % 12 结果是 7点

  

##  `or` `||`  `:`  逻辑或运算符

这几个运算符语义都是完全相同的,唯一的区别是 : 的优先级略低

  
## `and` `&&` `?`  逻辑与运算符


这几个运算符语义也是完全相同的,唯一的区别提 ? 的优先级略低

  
## a ? b : c 三元运算符

这个是三元运算符,计算规则为:  
a为真则计算b( b为真则返回b,否则仍然计算并返回c ),否则计算并返回c。  

当a与b条件满足时不会计算c( c如果是一个函数调用就不会执行 ),a为假时不会计算b。  
  
注意与C语言有所区别:  
**当b运算结果为假的时候仍然会返回c,aardio里这个三元运算符是尽最大可能去取回真值**。  

## `()` 括号 

这个用在表达式中可以改变[操作符的优先级](https://bbs.aardio.com/doc/reference/the%20language/operator/priority%20.html), 例如 ## 7 \* ( 2 + 3 )括号里面的会先运算.  

放在函数名后面则表示调用执行该函数,例如  
  
```
io.open() //打开控制台  
io.print("还可以写一个或多个参数")  
```

[定义函数](https://bbs.aardio.com/doc/reference/the%20language/function/definitions.html)的时候用来表示形参,例如  
  
```
func = function(a,b,c){  
   io.print("收到参数",a,b,c )  
}
```
  
## `...` 不定参数 

运行下面的示例你就明白了  
  
```
func = function( a,... ){  
   io.print( "收到参数",... )  
}  
  
io.open()  
func( 1 )  
func( 1,2 )  
func( 1,2,3,4 )
```
  
## `λ` 希腊字母 λ 在aardio中可用于替代 lambda 关键字 

[lambda](https://bbs.aardio.com/doc/reference/the%20language/function/lambda.html)用于定义匿名函数,示例代码:  
  
```
import console;    
var arr = {1;2;3;4;7}    
var value = reduce(arr,λ(a,b) a + b );

console.dump(value);
console.pause(true);
```
  
代码 `λ(a,b) a + b`    
等价于 `lambda(a,b) a + b`    
也等价于 `function(a,b) return a+b;`

## `/*DSG{{*/ /*}}*/` 窗体设计器代码块

aardio 窗体设计器生成的代码会置于 `/*DSG{{*/` 到 `/*}}*/` 中间。

在 aardio 开发环境中打开 *.aardio 源文件时,
aardio 会搜寻 `/*DSG{{*/` 到 `/*}}*/` 的创建窗体与控件的代码块,并可以在窗体设计器中呈现并修改生成窗体的 aardio 代码。

 
 ---


# aardio for循环语句的正确写法

## for循环语法

for (变量=起始值;终止值;步长) {    
    // 循环体    
}    

- 变量从起始值开始,到终止值结束
- 每次迭代后,变量按步长增加(正数)或减少(负数)
- 当变量超过终止值时,循环结束
- 所有参数必须是数值

示例:

```aardio
for (i=1;10;2) {
    // 执行5次:i值为1,3,5,7,9
}
```

```aardio
for (x=5;-5;-1) {
    // 执行11次:x值为5,4,3,...,-4,-5
}
```

## for循环的常见错误写法

从 1 循环到 10,每次循环变量 i 自动加 2,循环打印数值 1,3,5,7,9 的正确代码:

```aardio
import console; 

//从 1 循环到 10,每次循环 i 自动加 2,循环打印数值 1,3,5,7,9
for( i=1; 10; 2) {
	console.log(i)
}

console.pause();
```

请注意以下 aardio 代码存在哪些语法错误:

```aardio
import console;
var arr = {1,2,3};

for( i=0; i < #arr; i++ ) {
	console.log(i,arr[i]);
}

console.pause();
```

上面代码块中的 `i < #arr` 是错误的,应当改为表示循环结束计数的表达式 `#arr` 。

上面代码块中的 `i++ ` 是错误的,应当改为表示循环增量的数值 `1` 。

另外 aardio 数组的起始索引为 1 而不是 0 。

以上错误的代码修正以后的正确写法如下:

```aardio

//导入控制台支持库
import console;

//定义一个数组
var arr = {1,2,3};

//从数组的第一个元素循环到数组的最后一个元素。
for( i=1; #arr; 1) {
	//在控制台输入变量 i,以及数组元素 arr[i] 的值。
	console.log(i,arr[i]);
}

//暂停控制台,等待用户按键
console.pause();
```




---
  
# aardio 的 for 循环语句正确写法

## for 循环语句

  
for 循环执行一个固定次数的循环,for 循环语句的基本结构如下:  

  
```aardio
for (循环变量=起始数值;终止数值;步长数值) {  
    // 循环体  
}
```  
要点:

- `起始数值` `终止数值` `步长数值` 都必须是返回数值的表达式,不能包含 `<` `>` `++` `--` 操作符。
- `循环变量`从`起始数值`开始,到`终止数值`结束。
- 每次循环之后,循环变量按`步长数值`增加(正数)或减少(负数)。
- 当`循环变量`超过`终止数值`时,循环结束。
- `(循环变量=起始数值;终止数值;步长数值)`指定循环计数规则,可省略首尾的括号。
- 循环计数规则仅在循环开始时执行一次,确定计数规则后不会再重复执行。
- 循环体可以是一个单独的语句,也可以是用{ } 包含的语句块,但不允许是单独的分号(空语句)。
- `步长数值`为 1 时可省略(必须同时省略步长前面的分号)。
- `循环变量`是仅在循环内部有效的局部变量,不需要在前面增加 `var` 关键字。
- 可以在循环体内部修改`循环变量`到一个合法数值。但不建议这么做。循环体内部代码应当尽可能遵循黑盒原则、使循环体的条件控制仅仅置于循环体的开始或结束是好的习惯。
- 如果在循环内部将`循环变量`赋值为非数值,那么在下次循环前自动修复为计数器值。

for循环它可以简化循环条件控制为简单的递增或递减计数,使代码结构清晰可读。 可能的话,应优先选择使用for计数循环来替代其他的循环语句。

以下为 for 循环演示:  
```aardio
import console;

// 循环变量 i 从起始值 1 循环到终止值 10 ,每次递增步长为 2。
for( i=1;10;2 ){ 
    
    // 在控制台输出 1,3,5,7,9
    console.log(i); 
}

// 循环变量 i 从起始值 1 循环到终止值 10 ,每次递增步长为 1。
for( i=1;10;1 ){
        
    i++; // 修改循环变量的值,使步长变为 2
    
    // 循环变量设为无效数值,下次循环以前自动修复为计数器值
    i = "无效数值" 
}

// 循环变量 i 从起始值 10 循环到终止值 1 ,每次递减步长为 1。
for( i=10;1;-1    ){  
    
    // 在控制台输出 10,9,8,7,6,5,4,3,2,1
    console.log(i); 
}

console.pause()
```  
  
下面是用 for 循环计算阶乘的示例:  
  
  
```aardio
//计算阶乘(指从1乘以2乘以3乘以4一直乘到所要求的数) 
math.factorial = function(n){ 
    
   var result = 1; 
   
   //for 计数循环,递增步长为 1 时可省略,循环体可以为单个语句。
   for(i=2;n) result *= i;  
   
   return result;  
}

import console;
console.log( math.factorial(15) )
console.pause();
```


---

# 用 aardio 如何创建 winform 窗口界面, win.ui 图形界面库基本用法

下面是一个创建窗口程序的 aardio 示例:

```aardio
//自 win.ui 库中导入 win.form 窗口类
import win.ui;
	
//创建窗口
var winform = win.form(text="窗口标题不要省略";right=763;bottom=425)

//创建多个控件应当仅调用 winform.add 一次
winform.add({//如果函数的参数只有一个表对象,可省略名值对外层的 {}  。
	
	//指定访问控件的名字为 winform.button1 ,不可省略
	button1={
		cls="button";// 指定用类 win.ui.ctrl.button 创建按钮控件,不可省略
		text="点击我";// 按钮上的文字
		left=556;top=367;// 按钮左上角坐标
		right=689;bottom=407; // 按钮右下角坐标
		z=1 // 文本框左上角坐标
	};
	
	//指定访问控件的名字为 winform.edit1 ,不可省略
	edit1={
		cls="edit";// 指定用类 win.ui.ctrl.edit 创建文本框控件,不可省略
		text="输入文本";// 文本框内的文字
		left=9;top=10;// 文本框左上角坐标
		right=752;bottom=351;// 文本框右下角坐标
		multiline=1;//多行文本框
		z=2 //Z序,可省略
	}
})

// 设置按钮的点击事件处理程序,winform.button1 也就是 winform.add 的参数中创建的名为 "button1" 的控件。
winform.button1.oncommand = function(id, event) {
    // 从 1 循环到 33 的数字输出到 edit 控件
    for(i=1; 33; 1) {
        //注意 edit 以 '\r\n' 换行,而 richedit 以 '\n' 换行
        winform.edit1.appendText(i,'\r\n'); //winform..edit1 也就是 winform.add 的参数中创建的名为 ".edit1" 的控件。
    }
};
	
//显示窗口
winform.show();

//启动界面线程消息循环
win.loopMessage();
```

上面的代码执行了以下程序:

- 首先自 `win.ui` 库导入了 `win.form` 类。
- 然后创建了一个带有指定标题的 winform 窗口。
- 接着,程序在该窗口上添加了一个按钮和一个文本框。
- 当按钮被点击时,会从 1 循环到 33 的数字,每个数字后跟一个换行符,然后输出到文本框中。
- 最后,显示窗口并进入消息循环,以便响应用户的操作。

aardio 编写 winform 窗口程序的要点:
 
- 创建 `win.form` 对象的程序,给出的 aardio 代码第一行总是添加 `import win.ui`。
   注意 `import win.form` 是错误写法,只能自 win.ui 库会导入 `win.form` 窗口类。
- 创建 `win.form` 对象的程序,最后一行总是加上 `win.loopMessage()` 以启动界面消息循环 。 
- 包含 `win.loopMessage()` 的代码不要在代码结束添加 `console.pause(true)` 。 
- 在用 `win.form` 创建普通窗口时建议在参数表中指定 text 属性,例如 `var winform = win.form(text="这是窗口标题")`。
    不指定标题的窗口运行后没有标题栏,没有标题栏就没有标题栏按钮,这样用户就无法使用最大化、最小化、关闭窗口这些常用按钮会很不方便。

- 独立窗口创建后必须使用 `winform.show()` 才会显示窗口(控件或子窗口随父窗口显示)。
- 调用 `winform.add` 创建控件的参数应当是包含一个或多个控件名值对的表对象(table),参数表里的控件名如果是 `button1` 后续就要通过 `winform.button1` 访问该控件。aardio 程序一般不会去获取  `winform.add` 的返回值,要注意  `winform.add` 的返回值是控件数组而不是单个控件。
- 在  `winform.add` 的参数表中,创建每个控件的参数都必须用 `cls` 字段指定创建控件的类名,这些控件类名指定是 `win.ui.ctrl` 名字空间定义的类似。例如控件参数表中的  `cls="button"` 对应`win.ui.ctrl.button` 控件类。 



---


# 理解 aardio 迭代器

## 最简单的例子  
  
首先我们把迭代器的一大堆复杂的语法描述先扔到一边,看来一个简单至极的使用迭代器的示例:  
  

```aardio
import console;

//each函数创建一个迭代器
each = function(){
   
    //在局部变量闭包中保存一些需要用到的数据
    var i = 0;
   
    //下面返回一个迭代器 - 实际上就是一个函数。
    return function(){
        i++;
        if( i < 10 ) return i; //迭代器函数会被循环调用,直到他返回的是空值
    }
}

for( i in each() ){
    console.log("i",i)
}

console.pause(true);
```


从上面我的代码我们了解到:

> 1、迭代器实际上就是一个函数对象,这个函数会在for语句中被多次的循环调用
> 
> 2、each函数是用来创建迭代器的,这个创建迭代器的函数我们称之为“迭代器生成器”,for语句在开始循环以前会调用each函数并获取迭代器函数。

  
  
参考aardio帮助文档:[《泛型for与迭代器》](http://bbs.aardio.com/doc/reference/the%20language/statements/iterator.html)  
  
  
上面一节给出的例子返回的迭代器返回了0到10中间的整数,  
现在我们希望生成器一些参数让他生产不同的迭代器,例如我们可以指定数值从哪里开始、到哪里结束。  
  
  
示例:  

```aardio
import console;

//each函数创建一个迭代器
each = function(min,max){
   
    //下面返回一个迭代器  
    return function(){
        min++;
        if( min <= max ) return min-1;
    }
}

for( i in each(10,20) ){
    console.log("i",i)
}

console.pause(true);
```


  
现在我们又有了新的需求,我们不是要求迭代器循环列出min到max之间的数值。  
而是希望给迭代器一个集合对象,要求迭代器根据一定的规则列出我们需要的成员。  
  
那么我们进一目改进迭代器如下:  

```aardio

import console;

//each函数创建一个迭代器
each = function( array ){
     
    var i = 0;
    return function(){
        i++;
        if( array[ i ] ) return array[ i ];  
    }
}

var arr = {1;23;45};
for( n in each( arr ) ){
    console.log("n",n)
}

console.pause(true);
```


通过学习aardio基础语法,我们知道在调用一个对象的成员函数时 - 函数默认会提供owner参数指向他所在的集合。  
所以如果我们把上面的 each( array ) = function(){}  函数修改为 arr.each = function(){}  以后,就可以在迭代器内部使用owner对象来访问集合对象了。  
  
我们想当然的把上一节的代码修改如下:  

```aardio

import console;

var arr = {1;23;45};
arr.each = function(){
     
    var i = 0;
    return function(){
        i++;
      if( owner[ i ] ) return owner[ i ];  
    }
}

for n in arr.each() {
    console.log("n",n)
}

console.pause(true);
```


可是运行后,代码报错了,说是 onwer参数为null,出错行为上面红字标出的那行。


我们知道每个函数都有自己的owner参数,arr.each函数内部的owner参数无疑就是指向arr对象。

但在 arr.each 内部创建的新函数( 也就是那个迭代器 )并不是任何集合对象的成员,他的owner对象是空的。

> 关于owner参数的详细说明,请参考这里:[owner参数](http://bbs.aardio.com/doc/reference/the%20language/function/owner.html)

这该如何是好呢?!

幸运的是 - aardio中提供了一种机制,for语句可以在接受迭代器生成器的多个返回值,你可以用第一个返回值返回迭代器,你可以用第二个返回值返回迭代器的owner对象,正确的代码如下:

```aardio

import console;

var arr = {1;23;45};
arr.each = function(){
     
    var i = 0;
    return function(){
        i++;
        if( owner[ i ] ) return owner[ i ];  
    },owner //使用第二个返回值显式指定迭代器的owner参数
}

for n in arr.each() {
    console.log("n",n)
}

console.pause(true);
```

如果上面的代码让你困惑,这也没关系,只要不使用owner这个特殊的名字,你完全可以简单而粗暴的把代码写成下面这样:

```aardio
import console;

var arr = {1;23;45};
arr.each = function(){
     
    var i = 0;
    var 集合对象 = owner;
    return function(){
        i++;
        if( 集合对象[ i ] ) return 集合对象[ i ];  
    }
}

for n in arr.each() {
    console.log("n",n)
}

console.pause(true);
```


下面的范例把集合对象保存在了迭代器生成器函数创建的局部变量的闭包中,这种使用上层生成器函数的局部变量保存迭代器所需的集合对象以及状态的迭代器,我们称之为“有状态的迭代器”。

```aardio
var arr = {1;23;45};
arr.each = function(){
     
    var i = 0;
    var 集合对象 = owner;
    return function(){
        i++;
        if( 集合对象[ i ] ) return 集合对象[ i ];  
    }
}

```
  
而依赖for语句来传递迭代器集合对象、以及状态值的叫“无状态的迭代器”,也就是说这种迭代器依赖for语句来保持状态。  
我们前面说过了,for语句在循环开始首先调用迭代器生成器,迭代器生成器可以返回以下三个返回值。  

> 迭代器,集合对象,控制变量 = 迭代器生成器( 可选的生成器参数 )

当然集合对象,控制变量这两个返回值都是可选的,  
如果迭代器生成器返回了集合对象,那么for语句会在每次调用迭代器时把他指定为迭代器的owner参数。  
如果返回了控制变量,for语句会在调用迭代器时把控制变量作为调用参数传过去,而每次调用迭代器的第一个返回值会成为新的控制变量的值。  
  
听起来是不是很复杂?!其实我们一般用不上这种写法,但我们还是来看一个简单的例子:  

```aardio
import console;

var each = function(集合对象){
      
    return function(控制变量){
   
        控制变量++;
        if( owner[控制变量] ) return 控制变量/*新的值*/,集合对象[控制变量];
         
    },集合对象,0/*控制变量的初始值*/
}

var arr = {1;23;45};
for i,v in each(arr) {
    console.log(i,v)
}

console.pause(true);
```


上面就是一个无状态的迭代器,其实就是一个语法糖,利用for语句来传递一些迭代器的状态而已。

  
  
实际应用中,不必去区分一个迭代器是有状态还是无状态,两种方法可以混合起来使用。

在aardio中有一个规则,所有对象提供的迭代器生成器的函数名都以"each"开始,例如:  

```aardio
import console;

import process;
for processEntry in process.each( ".*.exe" ) {  
    console.log( processEntry.szExeFile )
}

import winex;
for hwnd,title,theadId,processId in winex.each() {  
    console.log( hwnd,title,theadId,processId  )
}
console.pause(true);
```


所以,请善用aardio编辑器提供的智能提示找到您需要的迭代器生成器。 

 
fiber.generator 创建迭代器演示:

```aardio
import console;

function fib(max){
    var a, b = 1, 1;
    while a < max {
        fiber.yield( a );
        a, b = b, a+b;
    }
}

for v in fiber.generator(fib,console.getNumber( "请输入斐波那契数列范围:" )) {
    console.log( v )   
}

console.pause()
```


---


# aardio 多线程开发入门

## 一、做好心理准备
  
虽然 aardio 的多线程开发非常简单,但是:  
1、请先了解:「**多线程」开发比「单线程」开发更复杂**这个残酷的现实。  
2、请先了解: aardio 这样的动态语言可以实现**真多线程**非常罕见。  
  
建议先找任意的编程语言试试能不能更轻松地实现 aardio 多线程范例相同的效果。 如果**实践后你发现 aardio 做多线程开发要轻松得多,那么请继续往下看**, 如果遇到一点困难和限制就不行,那只能早点放弃。  
  
## 二、简单了解什么是线程
  
当你点击EXE文件系统一个应用程序的时候 - 系统会创建一个进程(process)  
而在一个进程内可以包含多个线程(thread)。用来显示界面的线程,我们通常称为“界面线程”,  
其他不是用来显示界面的线程,我们一般称为“工作线程”或者是“后台线程”。  
进程的启动线程称为「主线程」,「界面线程」通常是主线程。  
  
每个线程可以按单一顺序执行一系列的任务 —— 但不能并发执行多个任务。  
**多个线程就可以并发执行多个任务**。  
  
## 三、为什么需要多线程
  
界面线程会使用 win.loopMessage() 启动一个消息循环,  
win.loopMessage() 就象一个快递公司不知疲倦地分发、处理窗口消息,直到最后一个非模态、非 MessageOnly 的独立窗口( 或 mainForm )关闭后才会退出消息循环。当然你也可以使用 win.quitMessage() 退出消息循环。  
  
界面线程如果遇到耗时操作,就会停下来无法继续分发处理消息 —— 这时候界面就表现为「卡死」状态。  
这时候如果创建工作线程去执行耗时操作,就可以让界面线程继续分发处理消息(不会卡住)。  
  
## 四、线程同步的风险
  
多线程就象多个在并列的轨道上疾驰的火车,如果 A 火车与 B 火车上的人想操作同一部手机( **线程共享变量** ),你不能直接从 A 火车上把手伸出去跟B 火车上的人拉拉扯扯。这时候只能先让所有的火车都停下来,互动完了再继续往前开。需要互动的时候先停下来 —— 这在多线程开发里就是线程同步锁机制。 在 aardio 中用 thread.lock() 创建同步锁,但实际上在 aardio 中很少需要用到同步锁。  
  
上面这种看起来省事的方式会制造大量的麻烦。更好的方法是 A 火车、B 火车上的人都玩自己的手机,而不是共享一部手机。大家拿着自己的手机( **线程独享变量** )相互联系,一样可以愉快地互动。这就是 aardio 多线程的基本规则:每个线程有独立的运行上下文、独立的全局变量环境,一个线程中的对象传到另一个线程 —— 只会传值而不会传址(传引用)。  
  
注意:  

1. aardio 也提供操作共享变量的 thread.get,thread.set,thread.var,thread.table 等。  
2. 一个对象传入另一个线程虽然是传值,但仍然可能引用同一个可以在线程间安全共享的资源,例如 thread.command,thread.event 等。  
3. 窗口或控件对象从一个线程传到另一个线程实际上是传同一个窗口句柄的引用对象 —— 在多线程中操作同一个窗口或控件总是会转发到创建窗口的界面线程内执行。  
  
  
## 五、aardio 多线程开发基本规则
  
多线程开发时要谨记以下基本规则。  
  
1. 非主线程的错误信息默认只会输出到控制台。 
只有用 console.open() 或 io.open() 打开控制台才能看到非主线程的错误信息。  
  
2. 每个线程有独立的运行上下文、独立的全局变量环境,有独立的堆栈。
一个线程不会使用另一个线程的全局部变量。  
一个线程也不会使用另一个线程引入的库。  
  
3. 不是所有对象都可以从一个线程传到另一个线程使用。  
没有任何外部依赖的数值、字符串、buffer、table、function 可以传入其他线程使用。  
这些对象在传入另一个线程时通常会复制值 - 也就是传值而非传址(传引用)。  
  
「类」不可以从一个线程传入另一个线程使用。  
「类」创建的实例对象,除非文档有特别说明一般不可以传入另一个线程使用。  
  
win.form 创建的窗体对象以及该窗体上创建的控件对象都可以作为参数传入其他线程。  
在其他线程调用窗体与控件对象的成员函数时 —— 都会回发到创建窗体的界面线程执行。  
利用这种奇妙的特性 —— 实际上可以在工作线程调用界面线程的任意代码。  
  
COM 对象不可以从一个线程传递到另一个线程。  
  
以下对象可从一个线程传递到另一个线程:  
>time,time.ole,thread.var,thread.table,  
thread.command,thread.event,thread.semaphore,process.mutex,  
fsys.file,fsys.stream,fsys.mmap,raw.struct …… 请参考相关文档说明。  
  
如果不想看文档,直接判断一个对象是不是可以跨线程传递的方法也很简单:  
不支持传入线程使用的对象,那么传入线程后调用必然会失败报错。  
  
## 六、重视 aardio 自带的多线程范例
  
如果找不到范例,请看下图:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGRmNDVmYTYxfDE3MjA1NDI0ODR8MHwxMzYyNQ%3D%3D)
  

一个线程会排队执行一系列的编程指令,但一个线程同时只能做一件事。  
例如在界面上有耗时的操作在执行时 - 就不能同时处理其他的界面消息或者响应用户的操作。  
这时候我们就要使用多线程来完成我们的任务。  
  
我们假设有一个耗时操作是这样的:

```aardio
//下面这个函数执行耗时操作
doSomething = function( str ){

    for(i=1;100){
        str = str + " " + i;
        sleep(100)
    }
   
    return 123;
   
}
```

一般我们直接调用这个函数会是这样写:

```aardio
 doSomething( "也可以有参数什么的" )
```

如果希望写复杂一点调用这个函数,我们也可以这样写:

```aardio
 invoke(doSomething ,,"也可以有参数什么的" )
```


如果我们希望创建一个新的线程来调用这个函数,那么就需要下面这样写:

```aardio
 thread.invoke(doSomething ,"也可以有参数什么的" )
```


切记不要犯一个低级错误:

> 如果把创建线程的代码改为 thread.invoke( doSomething("也可以有参数什么的") )
> 
> 这是在创建线程前就调用函数了,实际执行的代码是 thread.invoke( 123 ) 这肯定会出错的。

**aardio中多线程交换变量的几种方法:**  
  
1. 如果你有一些函数需要被多个线程用到,请他们写到库文件里,然后在任何线程中使用 import 语句导入即可使用。  
  
2. 可以在创建线程时,通过线程的启动参数把变量从一个线程传入另一个线程,例如:  

```
thread.invoke( 线程启动函数,"给你的","这也是给你的","如果还想要上车后打我电话" )
```

3. 多线程共享的变量,必须通过 thread.get() 函数获取,并使用 thread.set() 函数修改其值,thread.var, thread.table 对这两个函数做了进一步的封装。

4. aardio提供了很多线程间相互调用函数的方法,通过这些调用方式的传参也可以交互变量,具体请查看aardio范例中的多线程范例。

  
界面线程会使用 win.loopMessage(); 启动一个消息循环,  
win.loopMessage();  就象一个快递公司不知疲倦的收发消息,直到最后一个非模态、非 MessageOnly 的独立窗口( 或 mainForm )关闭后才会退出消息循环。当然你也可以使用 win.quitMessage() 退出消息循环。  
  
下面是一个启动界面线程的例子:  

```aardio
import win.ui;
var winform = win.form(text="aardio form";right=759;bottom=469)
winform.add(
button={cls="button";text="耗时操作";left=392;top=232;right=616;bottom=296;z=1}
)

//用户点击窗口上的按钮时会触发下面的回调函数
winform.button.oncommand = function(id,event){

    //下面用sleep函数休眠5秒(5000毫秒)模拟耗时操作
    sleep(5000)
}

winform.show();
win.loopMessage();
```

复制上面的代码到 aardio 中并运行,你可以看到一个窗体显示在屏幕上。

如果你去掉代码中的最后一句 win.loopMessage() 那么窗体只会显示一下就消失了,你的程序也迅速退出了。  
但如果你加上 win.loopMessage() 窗体就会一直显示在屏幕上(直到你点击关闭按钮)。并且你可以做其他的操作,例如点击按钮。  
  
我们尝试点击按钮,点击按钮后触发了 winform.button.oncommand() 函数,一件让我们困惑的事发生了,窗体卡死了任何操作都没有反应,这是因为类似 sleep(5000) 这样的耗时操作阻塞了win.loopMessage()启动的消息循环过程。  
  
一种解决方法是把 sleep(5000)改成 thread.delay(5000),虽然他们同样都是延时函数,但是 thread.delay() 在界面线程会继续处理窗口消息。但很多时候我们其他的耗时操作 —— 不能同时处理窗口消息,这时候就需要创建工作线程执行耗时操作。  
  
下面的代码演示在界面线程中创建工作线程,然后在工作线程中与界面线程交互:  

```aardio
import win.ui;
var winform = win.form(text="多线程 —— 入门";right=536;bottom=325)
winform.add(
button={cls="button";text="启动线程";left=27;top=243;right=279;bottom=305;db=1;dl=1;dr=1;font=LOGFONT(h=-16);z=1};
edit={cls="edit";left=27;top=20;right=503;bottom=223;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=2}
)

winform.button.oncommand = function(id,event){
   
    //禁用按钮并显示动画
    winform.button.disabledText = {"✶";"✸";"✹";"✺";"✹";"✷"}
   
    //线程工作线程
    thread.invoke(
        function(winform){
            
            for(i=1;3;1){
                sleep(1000); //在界面线程执行 sleep 会卡住
               
                //调用界面控件的成员函数 - 会转发到界面线程执行
                winform.edit.print("工作线程正在执行,时间:" + tostring( time() ) );
            }
            
            winform.button.disabledText = null;
            
        },winform //窗口对象可作为参数传入其他线程
    )
}

winform.show();
win.loopMessage();
```

在工作线程中直接操作界面控件固然令人愉快,  
但如果代码量一大,界面与逻辑混杂在一起,会让代码不必要的变的千头万绪复杂臃肿。  
  
如果把多线程比作多条轨道上并列飞奔的火车,那么火车交互的方法不仅仅只有停下来同步,或者把手伸出车窗来个最直接的亲密交互。一种更好的方式是拿起手机给隔壁火车上的人打个电话 - 发个消息,或者等待对方操作完了再把消息发回来。  
  
这种响应式的编程方式在 aardio 里就是 thead.command,下面我们看一个简单的例子:  

```aardio
import win.ui;
var winform = win.form(text="线程命令";right=599;bottom=399)
winform.add(
edit={cls="edit";left=12;top=11;right=588;bottom=389;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=1}
)

import thread.command;
var listener = thread.command();
listener.print = function( ... ){
    winform.edit.print( ... ) //我们在界面线程中这样响应工作线程的消息
}

//创建工作线程
thread.invoke(

    function(){
   
        //必须在线程函数内部导入需要的库
        import thread.command;
        
        //调用界面线程的命令
        thread.command.print("hello world",1,2,3);
        
    }
)


winform.show();
win.loopMessage();
```

thread.command可以把多线程间复杂的消息交互伪装成普通的函数调用,非常的方便。


这里新手仍然可能会困惑一点:我在工作线程中不是可以直接操作界面控件么?! 你这个thread.command虽然好用,但是多写了不少代码呀。


这样去理解是不对的,你开个轮船去对象菜市场买菜固然是有点麻烦,但如果你开轮船去环游世界那你就能感受到它的方便在哪里了。thread.command 一个巨大的优势是让界面与逻辑完全解耦,实现界面与逻辑的完全分离,当你的程序写到后面,代码越来越多,就能感受到这种模式的好处了,我举一个例子,例如 aardio自带的自动更新模块的使用示例代码:

```aardio
import fsys.update.dlMgr;
var dlMgr = fsys.update.dlMgr("http://update.aardio.com/api/v1/version.txt","/download/update-files")

dlMgr.onError = function(err,filename){
    //错误信息 err,错误文件名 filename 这里可以不用做任何处理,因为出错了就是没有升级包了
}

dlMgr.onConfirmDownload = function(isUpdated,appVersion,latestVersion,description){

    if( ! isUpdated ){
        //已经是最新版本了
    }
    else {
        //检测到最新版本,版本号 latestVersion
    };
   
    return false; //暂不下载
}

dlMgr.create();
```


这个 fsys.update.dlMgr 里面就用到了多线程,但是他完全不需要直接操作界面控件。  
而你在界面上使用这个对象的时候,你甚至都完全不用理会他是不是多线程,不会阻塞和卡死界面,有了结果你会收到通知,你接个电话就行了压根不用管他做了什么或者正在做什么。

  
这个fsys.update.dlMgr里面就是使用thread.command实现了实现界面与逻辑分离,你可以把检测、下载、更新替换并调整为不同的界面效果,但是fsys.update.dlMgr的代码可以始终复用。


我们有时候在界面中创建一个线程,仅仅是为了让界面不卡顿,我们希望用 thead.waitOne() 阻塞等待线程执行完闭(界面线程同时可以响应消息),然后我们又希望在后面关闭线程句柄,并获取到线程最后返回的值。  
  
可能我们希望一切尽可能的简单,尽可能的少写代码,并且也不想用到thread.manage(因为并不需要管理多个线程)。  
  
这时候我们可以使用 thread.invokeAndWait,thread.invokeAndWait 的参数和用法与 thread.invoke 完全一样,区别是 thread.invokeAndWait 会阻塞并等待线程执行完毕,并关闭线程句柄,同时获取到线程函数的返回值。  
  
示例:  

```aardio
import win.ui;
var winform = win.form(text="aardio form";right=759;bottom=469)
winform.add(
button={cls="button";text="读取网页";left=272;top=368;right=624;bottom=440;z=1};
edit={cls="edit";text="edit";left=48;top=40;right=720;bottom=336;edge=1;multiline=1;z=2}
)

winform.button.oncommand = function(id,event){
    winform.button.disabledText = {"✶";"✸";"✹";"✺";"✹";"✷"}
   
    winform.edit.text = thread.invokeAndWait(
        function(winform){
            sleep(3000);//暂停模拟一个耗时的操作
            
            import inet.http;
            return inet.http().get("http://www.aardio.com");
        },winform
    )
   
    winform.button.disabledText = null;
}

winform.show()
win.loopMessage();
```

请复制上面的代码运行测试一下,在线程执行完以前,你仍然可以流畅的拖动窗口,操作界面。


一般我们可以使用 thread.invoke() 函数简单快捷的创建线程,  
而 thread.create() 的作用和用法与  thread.invoke() 一样,唯一的区别是 thread.create()会返回线程句柄。  
  
线程句柄可以用来控制线程(暂停或继续运行等等),  
如果不再使用线程句柄,应当使用 raw.closehandle() 函数关闭线程句柄(这个操作不会关停线程)  
  
有了线程句柄,我们可以使用 thread.waitOne() 等待线程执行完毕,  
而且 thread.waitOne() 还可以一边等待一边处理界面消息(让界面不会卡死)。  
  
下面看一下 aardio 范例里的多线程入门示例:  

```aardio
//入门
import win.ui;
var winform = win.form(text="多线程 —— 入门";right=536;bottom=325;)
winform.add(
button={cls="button";text="启动线程";left=27;top=243;right=279;bottom=305;db=1;dl=1;dr=1;font=LOGFONT(h=-16);z=1;};
edit={cls="edit";left=27;top=20;right=503;bottom=223;db=1;dl=1;dr=1;dt=1;edge=1;multiline=1;z=2;};

)

winform.button.oncommand = function(id,event){
	
	//禁用按钮并显示动画
	winform.button.disabledText = {"✶";"✸";"✹";"✺";"✹";"✷"}	
	
	//创建工作线程
	thread.invoke( 
	
		//线程启动函数
		function(winform){
			
			for(i=1;3;1){
				sleep(1000); //在界面线程执行 sleep 会卡住
				
				//调用界面控件的成员函数 - 会转发到界面线程执行
				winform.edit.print("工作线程正在执行,时间:" + tostring( time() ) ); 
			} 
			
			winform.button.disabledText = null;
			
		},winform //窗口对象可作为参数传入工作线程
	)
} 

winform.show();
win.loopMessage();
```

 
aardio中提供了 thread.manage,thread.works 等用于管理多个线程的对象,  
例如标准库中用于实现多线程多任务下载文件的 thread.dlManager 就使用了thread.works管理线程:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGRmNDVmYTYxfDE3MjA1NDI0ODR8MHwxMzYyNQ%3D%3D)_(, 下载次数: 624)_  
  
thread.works 用于创建多线程任务分派,多个线程执行相同的任务,但可以不停的分派新的任务,一个例子:

```aardio
import console;
import thread.works;

var works = thread.works( 20,
    function(...) {
        import console;
        
        thread.lock("写控制台")
        console.log("线程ID" + thread.getId(),",开始工作,接收到任务指令参数",...)
        thread.unlock("写控制台")
        
        return "返回值,线程ID" + thread.getId();
    }
);

//分派任务
works.push("一个任务")
works.push("两个任务")

//等待任务完成
works.wait(
    function(r){
        console.log( "检查成果", r  )
    }
)

works.push("三个任务")
works.push("四个任务")
works.push("五个任务")

//退出程序前,等待任务完成并关闭所有线程
works.waitClose(
    function(r){
        console.log( "检查成果", r  )
    }
)

execute("pause")
```

而 thread.manage 可以用来创建多个线程执行多个不同的任务,可以添加任意个线程启动函数,在线程执行完闭以后可以触发onEnd事件,并且把线程函数的返回值取回来,示例如下:

```aardio
import console;
import thread.manage

//创建线程管理器
manage = thread.manage(3)

var thrdFunc = function(name){
    import win;
    import console;
   
    for(i=1;10;1){
        console.log( thread.getId(),name )
        if( !win.delay(1000) ){ //主线程可以用 manage.quitMessage()中断这个循环
            console.log("收到退出指令")
            return;
        }
    }
    return 67;
}

manage.create(thrdFunc,"线程1").onEnd = function(...){
    console.log("线程1的回调",...)
}

manage.createLite(thrdFunc,"线程2").onEnd = function(){
    console.log("线程2的回调")
}

manage.create(thrdFunc,"线程3")

manage.waitClose()
console.pause();
```

thread.manage通常是用于界面线程里管理工作线程,上面为了简化代码仅仅用到了控制台。  
  

---

# aardio 模式匹配快速入门  

**一个最简单的模式匹配代码:**

```aardio
 io.open()  
   
 结果 = string.match("字符串","这里是模式串")  
 io.print( 结果 )
```
  
## 一、模式

用于表示字符或者某一类的字符。  
  
### 1.1 字面值
  
字面值指的是a表示a, b表示b,字面意思是什么就表示什么,这个不难理解。  

```aardio
 io.open()  
 结果 = string.match("abcd","abc")  
 io.print( 结果 )
```


  
###  1.2 任意字符
  
另外还有两个非常特殊的预定义字符类,  
一个小圆点**"."**表示任意字符( 与正则表达式相同 ),  
而一个冒号**":"**表示任意双字节字符( 中文字,正则表达式无此语法 )  
  
### 1.3 预定义的字符类  
  
**以下蓝色粗体部分与正则表达式兼容  
**\\n 换行符**  
**\\r 回车符**  
**\\w 字母和数字**  
**\\s 空白符**  
**\\d 数字**  
**\\f 换页符 '\\x0c'**  
**\\v 匹配一个垂直制表符。等价于 '\\x0b'**  
**\\t 制表符**  
\\a 字母  
\\c 控制字符  
\\i 是否ASCII字符( 字节码 < 0x80 )  
\\l 小写字母  
\\p 标点字符  
\\u 大写字母  
\\x 十六进制数字(正则表达式里用于16进制字符前缀,即\\xhh)  
\\z 表示 '\\0'  
  

> **没有任何必要去死记硬背上面的列表**

  
大写表示反义( 与正则表达式相同 ),例如\\D表示不是数字的字符。  
  

```
 io.open()  
 结果 = string.match("12345678","\d")  
 io.print( 结果 ) //显示 1
```

### 1.4 自定义的字符类

例如\[abcd\]表示字符是abcd其中一个,或者\[a-z\]表示a到z的所有字符都可以。  
  

```
 io.open()  
 结果 = string.match("12345678","[1230-9]")  
 io.print( 结果 ) //还是显示 1
```


### 1.5 自定义的字符串  

**例如 他的语法与**字符类**一样,但是他表示的是一**串**,而不是其中一个。  
注意正则表达式里没有这个语法,但是正则表达式里可以将捕获分组作为一个匹配单位,所以可以用( ) 实现相同的效果。  

```
 io.open()  
 结果 = string.match("12345678","<1230-9>")  
 io.print( 结果 ) // 显示 1234  
```

  
## 二、修饰符
  
指定一个模式应当怎样去匹配。修饰符有很多种,而最常用的就是用来指定匹配次数。  
  
**p{2,3} 表示a出现2到3次  
**

```
 io.open()  
 结果 = string.match("12345678","\d{2,5}")  
 io.print( 结果 ) //显示 12345  
```


  
**p+  表示a出现1次到任意次数,等价于 p{1,}**

```
 io.open()  
 结果 = string.match("12345678","\d+")  
 io.print( 结果 ) //显示 12345678
```


  
**p\* 表示a出现0次到任意次数,等价于 p{0,}**

```
 io.open()  
 结果 = string.match("12345678","\d+\s*") //这里的"\s*"匹配零个或多个空格  
 io.print( 结果 ) //还是显示 12345678
```


模式匹配对正则表达式进行了简化,保留基本语法,牺牲一些功能换取效率。  
  
例如模式串 **"\\d+"**    
前面红色的是模式表示匹配什么样的字符(\\d表示数字),后面蓝色的是修饰符表示怎样去匹配、匹配多少个字符(+表示一个或任意多个),这就是模式匹配的全部内容了。很简单.  
  
这里的"\\d"还可以换成"\[0-9\]" ,他们的意思是一样的。  
也可以换成 "\[0123456789\]",跟上面的意思也是一样的。  

**中括号表示自定义的一个字符集合,只要目标字符是其中的一个就匹配成功。**  

模式就这几种,很简单。  
  
而后面的**"+"表示匹配一次或多次,这个也可以写为"{1,}"** 意思是一样的。  
  
我们看一个身份证匹配的模式 "\\d{15,18}"  这里指匹配15到18个数值。  
  
但是实际情况是:最后一位可能不是数值,可能是"x",也就是说**可能是数值也可能是x**,那么我们就要改为"\\d{15,18}\[\\dx\]"  
  
那么再笨的人也能看出这个写错了,最后面去掉一位前面就不是15或18,而是14或17, 也就是正确的是"\\d{14,17}\[\\dx\]"  
  
但是别人还有可能把x大写啊,所以最终就是  
"\\d{14,17}\[\\dxX\]"  
  
所以说模式匹配很简单,但是他也不简单,其实上面的写法并不是绝对正确,但大多时候够我们用了。  
再严格一点身份证他还不能是16位,17位。所以你要这样写  

```
 io.open();  
 //打开控制台  
 结果 = string.match("身份证号码","\d{14,14}<\d\d\d>*[\dxX]" )  
 io.print( 结果 )
```
  
象这些,你动手折腾很快会明白,别人灌输给你的知识永远没有自已探索到的理解深刻。  
  
更进一步,19位,20位的数值都可能匹配成功,因为他只要匹配其中18位就成功了吗,这时候我们还要限定他前面后面都不能有其他的字符,这时就要指定边界。例如:  
  

```
 io.open();//打开控制台  
 结果 = string.match("身份证号码","^\d{14,14}<\d\d\d>*[\dxX]$" )  
 io.print( 结果 )
```

但是这样还有一个问题,如果身份证号码前后可能有空格怎么办呢?  
\\s表示所有空白字符,可能有也可能没有的修饰符就是"\*",再改进:  

```
 io.open();//打开控制台  
 结果 = string.match("身份证号码","^\s*\d{14,14}<\d\d\d>*[\dxX]\s*$" )  
 io.print( 结果 )  
```

如果身份证前后不仅仅是空格,还可能有别的字符,哪怎么办呢?  
  
这里我们可以接触一个新的概念:**边界**边界是在模式前面加一个表示否定的感叹号,例如 "!\\d" 表示不是数字到数字交界的位置,这是一个试探性的匹配,匹配的是边界,匹配的字符串长度为0。  

```
 io.open();//打开控制台  
 sfz="dsf612323198608110000fgd"  
 结果 = string.match(sfz,"!\d(\d{14}<\d\d\d>*[\dxX])![^\dxX]")  
 io.print( 结果 )  
   
 execute("pause")  
 io.close()  
   
```

这些需要在**实践中嗑碰出来的知识,如流水无形,无一定之规**。  

## 模式匹配中的括号

可以熟练使用模式匹配中的括号,表示你精通了模式匹配。

> **\[ab\]** 中括号匹配指定字符中的**一个**。  
>   
> **** 尖括号匹配**一连串**的字符。  
>   
> **p{2,3}** 大括号指定模式重复匹配的**次数  
>   
> (p)**  而圆括号则设定模式匹配返回的结果,每增加一对圆括号,匹配函数就**多一个返回值**

  
## 模式匹配语法参考
[http://bbs.aardio.com/doc/reference/libraries/kernel/string/pattern%20syntax.html](http://bbs.aardio.com/doc/reference/libraries/kernel/string/pattern%20syntax.html)  
  
## 模式匹配函数说明
[http://bbs.aardio.com/doc/reference/libraries/kernel/string/pattern%20matching.html](http://bbs.aardio.com/doc/reference/libraries/kernel/string/pattern%20matching.html)  
  

---


# aardio 开发环境

## aardio 开发环境简介

aardio 开发环境为 aardio 编程语言的集成开发环境。

aardio 是历经 20 年活跃更新(自 2004 年发布的上代产品 ~ 直到 2024 为 20 年)的桌面软件开发工具。

aardio 开发环境仅数 MB 大小,绿色免安装(下载就可以使用)。使用方便,不需要复杂地搭建环境与准备工作,也不需要安装任何第三方库就可以直接使用。

aardio 生成的软件体积也很小,而且不依赖外部运行库。

个人或企业可永久免费用于开发商用、或非商用的软件。

aardio 专用于 Windows 操作系统,因此可以摆脱跨平台的复杂性,专注发挥和利用 Windows 的专有特性和优势。没有跨平台负担所带来的回报是丰厚的。例如 aardio 的图形界面库就只用了少量纯 aardio 代码编写,而且很好用。甚至整个 aardio 标准库基本都是用纯 aardio 代码编写的。

aardio 以及 aardio 开发的程序兼容 Windows XP,Vista,Windows 7,Windows 8Windows10,Windows 11 等所有流行桌面操作系统,也兼容 Windows 11 以后的操作系统。

aardio 目前用于开发 32 位程序,专用于生成 EXE 文件,可以方便地生成独立 EXE 文件,或将非独立 EXE 转换为独立 EXE 。

aardio 不能用于创建 DLL 文件,但可以方便地调用 DLL 文件。

aardio 不能用于开发手机 APP。

aardio 只能通过 Wine 运行于 Linux 系统,

aardio 可以非常方便的调用十几种第三方编程语言。 aardio 能这样做并不是  aardio 必须这样做(指调用第三方编程语言并非必须)。大多数 aardio 开发的软件,很多都是以纯 aardio 代码编写的。

aardio 开发的程序虽然不能跨所有平台,但得益于 aardio 良好的扩展能力(指支持很多接口,支持很多第三方语言以及第三方语言开发的组件)因此可以在 aardio 中大量的使用一些跨平台友好的技术,例如在 aardio 中可以用前端技术开发 Web 界面,使用  Python,Go,C语言 这些适用于跨平台的编程语言。

aardio 官网为 www.aardio.com 。
 
## aardio 开发的三种主要应用程序

aardio 开发的程序分类三大类。

1. 控制台程序:也称命令行程序,主要用于创建控制台窗口的程序。控制台程序需要用 `import console` 导入控制台支持库,并使用 console 库提供的函数操作控制台,在程序结束前应调用 `console.pause()` 暂停以等待用户按任意键再关闭程序,避免控制台立即关闭,用户看不到控制台的输出。console 库提供了更多更好用的控制台函数,在 aardio 里更常用。当然也可以不使用 console 库,改用无参数的 `io.open()` 打开控制台窗口(有参数用于打开文件),然后使用 io 库提供的函数操作标准输入输出,例如用 `io.print` 函数输出数据到标准输出。这与 `console.log` 等输出到控制台的函数有一定的区别,标准输出默认指向控制台,但也可以指向其他的文件。如果只是操作控制台,建议使用 console 库。

2. 窗口程序: 也称图标界面程序,或称为 winform 程序。主要用于带有 Windows 窗口的图形界面应用。窗口程序必须要用 `import win.ui` 导入 win.ui 界面库,在 win.ui 界面库中定义了 win.form 类。 win.form 类用于创建 winform 窗口 ,winform 窗口用于显示图形界面。winform 是经典的 Windows 窗口,所有 winform 对象都可以使用 `winform.hwnd` 得到 Windows 窗口句柄(这是一个数值,在 aardio 的 API 函数类型中被声明为 addr 类型)。所有 winform 程序必须调用 `win.loopMessage()` 启动界面线程消息循环以处理用户输入并持续显示图形界面。

3. 网页界面程序: 这是包含了浏览器控件的窗口程序。aardio 中有很多浏览器控件,例如 web.view, win.form, web.sciter 等等。最常用最推荐的是 web.view,web.view 基于微软 WebView2 浏览器控件,有强悍的性能,接口也强大而方便,因为属于系统自带控件,所以生成的 EXE 体积也很小。在 aardio 中创建浏览器控件首先要在参数中指定一个 winform 窗口或窗口控件对象,浏览器才能在指定的窗口中显示网页,这个参数被称为宿主窗口。下面是一个简单的示例:

```aardio
//导入界面库
import win.ui;

//创建 winform 窗口
var winform = win.form(text="WebView2")

//导入浏览器
import web.view;

//创建浏览器控件,参数必须指定窗口或控件窗对象以作为浏览器的宿主窗口
var wb = web.view(winform);

//指定网页可以调用的 aardio 对象,必须在打开或显示网页前定义	
wb.external = {
	log = function(str){ 
		winform.msgbox(str)
		return str;
	};
}

//写入网页,也可以用 wb.go 函数打开网址
wb.html = /********


hello

********/

//显示窗口
winform.show();

//启动消息循环
win.loopMessage();
```

## aardio 工程向导

在 aardio 主界面,点击主菜单按钮打开主菜单,然后点击菜单项『新建工程』可以打开工程向导。

工程向导上有 6 个主要的分类导航按钮,分别为:

- 窗口程序
- 控制台
- Web 界面
- Web 服务端
- CGI 服务端
- 更多

默认显示的是『窗口程序』

点击这 6 个导航按钮会显示二级分类,所有工程的完整列表如下

- 窗口程序
    - 空白程序 最简单的 winform 窗白程序
    - 范例程序 有一些示例代码的 winform 程序。
    - Python 调用 Python 的工程范例,编写 aardio 调用 Python 的程序务必此用此模板创建工程,避免配置错误导致的问题。
    - Java  调用 Java 的工程范例。
    - ActiveX EXE 用 aardio 创建 ActiveX 进程外控件的示例,也包含了很多其他语言调用 aardio 控件的示例代码。
- 控制台
    - 控制台 最简单的控制台程序
    - 后台服务 实现后台 NT 服务的工程范例,主要为调用标准库 service 的演示。
- Web 界面
    - WebView2
        - React 用 web.view 实现网页界面的工程,前端代码使用 React。
        - Vue 用 web.view 实现网页界面的工程,前端代码使用 Vue。
        - htmx 用 web.view 实现网页界面的工程,前端代码使用 html。
    - Chrome App 调用用系统自带的 Edge 或 Chrome 浏览器做界面的工程模板。
    - Web Form 用 web.form 库调用系统自带 IE 内核做界面的例子。
    - SciterJS 用 web.sicter 扩展库调用 Sciter JS 组件做界面的例子。
    - HTMLayout 用 web.layout 扩展库调用 HTMLAyout 组件做界面的例子。
    - 浏览器扩展 调用标准库 web.nativeMessaging 使用 Native Messaging 创建本地浏览器扩展的模板工程
- Web 服务端
    - 网站程序 用 aardio 写的示例网站
    - Web 服务端 用 aardio 实现的 HTTP 服务端示例工程
- CGI 服务端
    - 安装配置 此选项不是工程分类,而是 CGI 服务端的安装配置说明,点击创建工程时直接创建 CGI 服务端模板工程。
    - 常见问题 此选项不是工程分类,而是 CGI 服务端的安装配置说明,点击创建工程时直接创建 CGI 服务端模板工程。
- 更多
    - 竖版导航 使用 aardio 的高级选项卡( win.ui.tabs )实现垂直排列导航按钮的界面范例工程,主要使用 plus 控件与 win.ui.tabs。
    - 横版导航 使用 aardio 的高级选项卡( win.ui.tabs )实现水平排列导航按钮的界面范例工程,主要使用 plus 控件与 win.ui.tabs。
    - 横版大图标  使用 aardio 的高级选项卡( win.ui.tabs )实现水平排列导航按钮(使用大图标)的界面范例工程,主要使用 plus 控件与 win.ui.tabs。
    - 自解压 aardio 创建 7zip 自解压 EXE 的范例工程
    - 批处理 aardio 调用批处理的范例工程。

aardio 实际上可以创建更多类型的工程,以上仅为示例的模板工程。

用户打开工程向导,输入『工程名称』,然后点『创建工程』就可以创建好工程文件了。

在创建工程以前,也可以勾选『管理权限』,这样会在(指 main.aardio 文件)前第一行添加 `//RUNAS//`,程序运行时将会请求 UAC 管理权限。

在创建工程以前,也可以勾选『启动参数』,这样会在启动主文件(指 main.aardio 文件)前添加接收并操作命令行在数的示例,例如:

如果勾选『启动参数』生成的工程里 main.aardio 的代码如下:

```aardio
import console;
console.setTitle("这里是在工程向导里设置的工程名字");

if(_ARGV.opt == "test"){
	/*
	启动参数名前导字符可以为任意个数 / 或 - 。
	参数值可以空格或等号分开,例如: "/opt test" 或 "--opt=test" 。
	*/
	console.dump(_ARGV.opt,_ARGV[#_ARGV]) //_ARGV 既包含命名参数组成的键值对,也包含按参数顺序组成的数组
	/*
	生成 EXE 以后,按 Ctrl + L 切换到地址栏,输入 cmd 回车 —— 在发布目录打开 cmd.exe,
	然后输入 exe 文件名(按 tab 键可自动完成文件名)+ 参数后回车执行,例如 "?.exe /opt test c:\xxx.text"。
	
	除了传参数,也可以用 string.getenv() 获取父进程设置的环境变量,
	或者在父进程创建管道(参考标准库 process.popen )读写标准输入输出,子进程用 io.stdin.read() 读标准输入, io.stdout.write() 写标准输出。
	如果父进程、子进程都是 aardio 编写的,也可以用 process.command 交互更方便。
	*/
}

var str = console.getText("请输入一行文本,然后回车:");
console.log("您输入的是" ,str );
 
console.pause();
```

如果不勾选 『启动参数』生成的代码就更简单,内容如下:


```aardio
import console;
console.setTitle("这里是在工程向导里设置的工程名字"); 

var str = console.getText("请输入一行文本,然后回车:");
console.log("您输入的是" ,str );
 
console.pause();
```

## aardio 工程的结构与文件格式

一个 aardio 工程文件通常位于独立目录,由一个 *.aproj 文件存放工程的配置信息与文件结构,默认为 `default.aproj`。

aproj 文件的内容为 XML,一个控制台程序的 `default.aproj` 内容如下:

```xml


	
	

```

用 aardio 打开 aproj 文件就打开了 aardio 工程。
可在工程属性中可视化地修改工程配置,可以指定工程的版本信息,EXE 图标等常用发布属性。

启动代码默认为 `main.aardio` ,默认在工程根目录下。工程根目录下也只能有一个启动代码文件(默认为  `main.aardio` ),其他文件必须放在其他工程目录下。

文件与目录必须添加到工程中,生成 EXE 时才会发布这些文件与目录。

工程目录并不一定对应于硬盘上的实际物理目录(虽然一般是对应的),工程目录实际是一种虚拟目录。

可以在工程中指定一个目录为内嵌资源目录,这样目录就会被嵌入发布后的 EXE。在 aardio 很多函数读取这种资源目录下的文件并不需要修改任何代码,这是非常方便的,例如最常见的默认资源目录为 `"/res"`,读到文件的代码 `string.load("/res/test.txxt")` 可以兼容资源目录与本地文件。 aardio 中大多库与函数都可以自动兼容内嵌资源目录,所以 aardio 可以方便地生成独立 EXE 文件且不需要特别修改什么代码。

一个常见的错误用法是写 `$"/res/test.txxt"` 这样的代码,res 目录默认为内嵌资源目录,这说明 "/res/test.txxt" 文件在发布后被嵌入 EXE 资源,而 aardio 中 `$`  操作符也是将文件的数据编译到代码中,这就会导致同一个文件在 EXE 中嵌入了 2 个重复的副本,这是不必要且错误的,需要避免。

## aardio 工程选项:界面系统

工程属性的『界面系统』有 3 个选项

- console 这指的是控制台程序,那么运行发布后的 EXE时无论是否用代码打开控制台都会默认显示控制台窗口。
- win 这指的是 winform 图形界面程序,如果代码并没有创建 winform 界面,那么运行发布后的 EXE 运行后不显示任何界面。
- website 这指的是网站程序,也就是用 aardio 开发的网站,发布时不会生成 EXE 文件,仅编译代码并复制到发布目录。

## 运行 aardio 程序

在开发时运行 aardio 程序并不会生成 EXE,而是直接调用 aardio.exe 创建进程并运行 aardio 程序。

运行程序的方式分为两种:

- 运行工程的启动文件 ( 默认为 main.aardio ),这是启动工程主程序。
- 运行工程内部的其他 aardio 文件。
- 在工程外单独运行文件。
- 可在开发环境中新建代码文件,直接编写或复制粘贴 aardio 代码,然后运行 aardio 代码编辑器中未保存为文件的代码。

我们可以看出 aardio 运行代码是非常方便的,主要特点:

- aardio 几句代码就是一个独立程序,很多 aardio 自带的范例都是独立程序。不需要复杂地搭建环境,使用非常方便。
- 可以直接复制范例代码,粘贴到编辑器就可以运行 aardio 程序,操作非常简单。

这几种运行方式的区别在于对『应用程序根目录』的设定。

- 运行工程内的 aardio 文件(包括 main.aardio 这样的启动文件)则『应用程序根目录』设为工程根目录。
- 独立运行工程外部的其他 aardio 文件,则启动程序的 aardio 文件所在的目录设为 『应用程序根目录』。
- 运行 aardio 代码编辑器中直接运行未保存为文件的代码时,如果当前开发环境打开了工程则『应用程序根目录』为工程根目录,否则为 aardio.exe 所在目录。

## aardio 『应用程序根目录』

应用程序根目录指定的是启动程序目录,根据启动方式不同应用程序根目录的含义如下:

- 运行工程内的 aardio 文件(包括 main.aardio 这样的启动文件)则『应用程序根目录』设为工程根目录。
- 独立运行工程外部的其他 aardio 文件,则启动程序的 aardio 文件所在的目录设为 『应用程序根目录』。
- 运行发布后的 EXE 文件,则 『应用程序根目录』为 EXE 所在目录。
- 运行 aardio 代码编辑器中直接运行未保存为文件的代码时,如果当前开发环境打开了工程则『应用程序根目录』为工程根目录,否则为 aardio.exe 所在目录。

在 aardio 的所有函数中,凡文件径参数第一个字符为单个正斜杠 `\` 或反斜杆 `/` 表示『应用程序根目录』。

外部第三语言实现的函数则需要用 `io.fullpath("/")` 或 `io.localpath("/")` 将 aardio 格式的路径转换为绝对路径。

另外,aardio 还可以用路径前面用单个 `~` 表示 EXE 所在目录,例如 `io.fullpath("~/lib")` 或 `io.localpath("~/lib")` 表示 EXE 目录下的 lib 目录。

要注意 aardio 程序在开发时通过 aardio.exe 运行,所以 `io.fullpath("~/")` 指向 aardio.exe 所在目录,而发布后  `io.fullpath("~/")` 指向发布的 EXE 启动时所在的目录。

## 转换为独立 EXE 文件

aardio 生成的 EXE 通常都是独立 EXE 文件。

但如果你的程序引用了一些不能内嵌到 EXE 的组件,例如 Python 解释器,那么发布后还要带上这些组件。但 aardio 发布工程提供了一个将这些外部组件转换并嵌入到独立 EXE 的功能,这个功能自动调用免费的 `Enigma Virtual Box`实现( 由 aardio 的 process.evb 扩展库封装 EVB 并实现自动调用 )。发布 EXE 以后,在发布完成界面点击『转换为独立 EXE 』按钮就可以了。要注意,如果发布的 EXE 本来就是独立 EXE 不必要调用 process.evb 转换,不需要点击『转换为独立 EXE 』。

## aardio 自带了大量范例

aardio 自带了大量范例( 参考知识库中有关范例的文件),别看 aardio 体积小,因为 aardio 实现程序的代码通常非常简短,所以小小的 aardio 中自带了大量范例,这些范例分门别类,涉及到了桌面开发的方方面面,仅仅是拼凑范例都能快速开发出不错的程序。

## aardio 默认界面布局

aardio 主界面左侧上方为『工程』面板 - 这是一个树视图,显示工程内的文件列表以及工程目录下的用户库文件。

aardio 主界面左侧下方为『标准库』、『界面控件』面板,『标准库』是一个树视图,列出了所有的标准库,扩展库实际也是安装到这个目录(但只有 aardio 默认自带的才是标准库),在文件路径上来说标准库与扩展库的存储路径是相同的。

『界面控件』是 winform 窗体可视化设计器用到的界面控件工程箱,提供了按钮、编辑框、plus 控件、custome 控件等等还有很多其他常用控件。

## aardio 窗体设计器

用 aardio 打开一个 aardio 源文件时,aardio 会检查该文件的源码是否包含 `/*DSG{{*/` 与 `/*}}*/` 标记,如果找到这 2 个标题,aardio 会提取 `/*DSG{{*/` 到 `/*}}*/` 中间创建窗体与控件的代码并在可视化的窗体设计器中打开。

aardio 的窗体设计器使用了 aardio 本身来读取解析创建窗体与控件的 aardio 源码,也使用 aardio 将窗体设计器上设计好的界面生成为 aardio 代码,并放入源码的  `/*DSG{{*/` 到 `/*}}*/` 中间。

如果你不希望 aardio 以窗体设计器打开源码,只要简单地去掉  `/*DSG{{*/`  与  `/*}}*/`  标记就可以,对于 aardio 代码来说这只是注释语句。

在可视化界面可以点击工具栏的『代码视图』切换到代码编辑器界面,在代码编辑器界面可以点『设计视图』切换回到窗体设计器界面,也可以按快捷键 Ctrl + U 快速切换『代码视图』与『设计视图』。

aardio 的窗体设计器简单方便,通过拖放就可以制作不错的界面。而且 aardio 提供了强大开源的 plus 控件(使用纯 aardio 代码实现),plus 控件可支持各种字体图标, jpg 图像,透明 gif 图像,透明动画,半透明 png 图像,并可设定多种不同的绘图模式、九宫格贴图等等,使用 plus 控件可以简单地通过在窗体设计器中拖拉创建各种漂亮的控件效果、可创建静态图片框、动画播放控件、按钮、透明按钮、不规则按钮、复选框、超链接、进度条、扇形进度条、滑块跟踪条、选项卡、弹出菜单、下拉框...... plus 控件还提供了非常多的灵活的可调整参数,如果您擅于发挥可以做出更多的控件效果。

## win.ui 库

aardio 的标准库是开源的,而 win.ui 是标准库中开源的图形界面库( GUI 库)。

win.ui 使用纯 aardio 代码实现,不调用其他例如 C++ 写的界面库。

win.ui 简单轻量,易于使用,生成的 EXE 体积小,由 aardio 可视化窗体设计器提供良好的支持。

win.ui 是 aardio 的基础界面库,所有 aardio 开发的图形界面程序必须导入 win.ui 库,并需要使用 `win.loopMessage()`启动界面线程消息循环。

有个别人错误地认为 win.ui 只是调用 WinAPI 而已,这种似是而非的观点是错误的。实际上所有编程语言都要调用系统 API,没有一个语言能在真空里生存。完全不调用系统 API 的程序或界面库实际上并不存在。

区别只是有的界面库实现了无句柄界面,有的界面库使用了有句柄的窗口。这两种类型的界面库很多,但真正做得好用的很少,所以像 Python 这样强大的编程语言,有很多不同类型的界面库(当然有调用系统 API,甚至是更多地调用 C++ 二次封装的界面组件 API   ),但仍然有很多用户利用 aardio 为 Python 写界面。

aardio 的图形界面库是 aardio 最重要的一个库,也是 aardio 本身的特性淋漓尽致的体现。说实话,即使参考 win.ui 的源代码,要想用其他语言写出这么轻量,用起来又非常好用的界面库并不容易。

当然,aardio 中还有更多选择,可以调用更多其他的界面库,例如通过 web.view 调用 WebView2 ,用 web.sciter 调用 Scite JS ,但这些界面支持库或扩展库的实现都以 win.ui 为基础。

## aardio 支持大量第三方编程语言

aardio 支持大量第三方编程语言,例如 C语言、C++、C#、Go 语言、Python、Rust、JavaScript、Node.js、Java、Delphi、Fortran、Julia、R、PHP、VBA、JSA、VB、VBScript、FreeBasic、Ruby、PHP、Nim、V 、PowerShell、批处理、汇编等编程语言或这些语言编写的组件。

对这一点有几个常见的误解。

1. 认为 aardio 有什么保密的技术实现这些调用其他编程语言的功能。实际上 aardio 调用这些编程语言的支持库扩展库都是开源的,而且基本都是代码量很少。

2. 认为 aardio 本身有什么缺陷才不得不调用其他语言来弥补。这个理解是完全错误的,其实编程语言间的交互往往是最困难的,例如在 JavaScript 中调用 C++ 或 C语言 可能会让人疯掉,aardio 能如此轻便地支持这么多第三方编程语言,这本身对 aardio 是极大的挑战与考验,关键是 aardio 做到这一点并非刻意追求,参考我们开源的调用代码可以看出,aardio 调用很多语言的代码非常简洁 ,举个例子,以调用 MATLAB 为例,aardio 什么都不用干就可以自动支持 MATLAB 的所有接口,这种事我们可乐而不为呢?

3. 认为学 aardio 先要学其他编程语言,这么想是错的。能做什么不等于必须做什么,实际上大家注意一下很多流行的用 aardio 开发的桌面软件,基本都是用纯 aardio 开发为主,例如 ImTip,Gif123,WubiLex,WinpeMaker,edge-TTS-record,WechatVideoSniffer,WeChatDownloader,rimage_gui,WinAsar,Ghips …… 这些软件基本都是开源软件,大家可以看一看源码。

实际上 aardio 之所以支持那么多第三方编程语言,是基于以下考虑:

1. 几乎所有用户都已经掌握了一种或多种其他编程语言(因为一个人任何语言都不学只学 aardio 这种可能性并不大 ),aardio 支持这些编程语言,就可以让这些用户在使用 aardio 时降低学习与使用成本。

2. 虽然大多数需求我们可以用纯 aardio 实现,但需求千变万化,支持更多第三方语言可以利用这些语言广泛的生态,如果你发现有什么是 aardio 不能做的,你可以调用其他语言,这非常方便。.aardio 本身的库已经非常多,覆盖到了桌面开发的方方面面。但总有没覆盖到的地方,这是我们需要支持第三方编程语言的原因。


---


# aardio 开发环境的快捷键与小技巧  

> 本帖仅介绍一些容易被忽视的重要技巧,  
> 例如括号匹配、同词高亮这些一看就会用,你不知道他也会自己跑出来介绍自己的功能,本帖一律不介绍。

首先介绍最重要的一个快捷键 **Ctrl + K,**  
这个快捷键的作用是更新与**修复更新折叠、着色、智能提示这些数据**。  
  
我们在输入代码时 —— 代码的结构会不断地变动,aardio 为了优化性能,并不会在输入每一个字符时都会更新折叠、着色、智能提示这些数据,而且有时候只是做局部更新。但我们可**以按 Ctrl + K 快捷键主动更新或修复这些数据**。  
  
我们也可以在标准库库、扩展库、用户库目录上点击鼠标右键,  
在右键菜单中点击【刷新目录、智能提示】刷新对应支持库的智能提示数据。 
  
一般代码的智能提示会自动出来,也可以按 Ctrl + I ( 或者 Ctrl + J )组合键显示。  
注意 aardio 显示代码提示时会跟踪输入名字空间的顺序,有部分提示 Ctrl + I 调不出来,只有输入时才会出来。  
  
## F1 快速查询帮助
  
在编辑器中选中关键字,然后按 F1 可以打开百度搜索 aardio 网站上的相关帖子。  
  
如果未选中关键字,直接按 F1 会打开 《 aardio 语法与使用手册 》。  
  
##  其他一些常用快捷键

 
	 1. `F11` 切换全屏  
	   
	 2. `Ctrl + B` 自动隐藏侧边栏  
	   
	 3. `Ctrl + W` 关闭当前窗口(这个不是指文档,一个文档可以有多个视图窗口)  
	   
	 4. 选中一段代码,按 `Tab` 键增加缩进,按 `Shift + Tab` 减少缩进。  
	   
	 5. `Ctrl + /` 切换注释  
	   
	 用于取消或添加注释。  
	 如果选区内包含注释,或光标位于注释内(或两侧)则移除注释,否则添加注释。  
	 
	 添加注释时:  
	 无选区总是添加「行注释」,有选区则总是添加「段注释」  
	   
	 6. `Ctrl + *` 自动输入段注释标记  
	 如果遇到热键冲突,同时按下 `Ctrl + Shift +  *`  作用是一样的。  
	   
	 如果当前光标位置无选区:则自动输入 输入段注释标记 `/* ..... */`,  
	 如果当前光标位置有选区:则在选区首尾添加段注释标记,如果选区内已经包含其他段注释,则外层新加的段注释标记会自动增加首尾星号数目,以避免冲突。  
	   
	 7. `Ctrl + Shift + <` 自动输入网页模板标记 ``	  
	   
	   
	 8. `Ctrl + {` 自动输入{}包含的语句块,并添加缩进,光标移动到语句块内部。  
	         如果先选中一段代码,该段代码自动移入语句块内部,如果遇到热键冲突,同时按下Shift键也可以。  
	   
	 9. `Ctrl + (` 自动输入(),光标移动到括号内部。  
	         如果先选中一段代码,该段代码自动移入括号内部,如果遇到热键冲突,同时按下Shift键也可以。  
	   
	 10. `Ctrl + "` 自动输入一对引号,光标移动到引号内部。  
	         如果先选中一段文本,该段代码自动移入引号内部,并且文本中的所有双引号自动替换为一对双引号(因为在双引号内用一对双引号表示原来的双引号),如果遇到热键冲突,同时按下Shift键也可以。  
	   
	 11. Ctrl + ` 自动输入一对反引号,光标移动到反引号内部。  
	         如果先选中一段文本,该段代码自动移入反引号内部,并且文本中的所有反引号自动替换为一对反引号(因为在反引号内用一对反引号表示原来的反引号),如果遇到热键冲突,同时按下Shift键也可以。  
	   
	 12. `Ctrl + &` 自动输入2个 &&,如果遇到热键冲突,同时按下Shift键也可以。  
	   
	 13. `Ctrl + |` 自动输入2个 ||,如果遇到热键冲突,同时按下Shift键也可以。

  
  
##  Tab,Shift+Tab 快捷键的用法
  

> 按 Tab,Shift+Tab 键时,  
> 如果有代码选区,但按Tab缩进、按Shift+Tab减少缩进逻辑。  
>   
> 按Shift+Tab 键时如果无代码选区,  
> 则向右侧查找符合以下条件之一的位置,并移动输入光标到该位置:  
> 1. 右括号右侧,如果刚好跳到 if(){} 的语句块开始标记前,则继续向后跳到语句块内部。  
> 2. 字符串与注释内侧开始处  
> 3. 字符串与注释结束处。
  
##  其他快捷键
  
界面菜单或工具栏有说明的这里不列出,注意这些是很多编辑器都可以通用的快捷键。  
  
-  `Ctrl + N` 新建文件  
-  `Ctrl + Tab` 快速切换当前窗口   
-  `Ctrl + /` 切换注释   
-  `Ctrl + A` 全选  
-  `ctrl + C` 复制  
-  `ctrl + V` 粘贴  
-  `ctrl + X` 剪切  
-  `Ctrl + Z` 撤消  
-  `Ctrl + Y` 重做  
-  `Ctrl + F` 查找  
-  `Ctrl + H` 替换   
-  `Shift +  ↑ ↓ ← → ` 改变文本选区。   
-  `Enter` 在下方添加换行  
-  `Shift + Enter` 在当前行上方添加换行   
-  `Alt+  ↑ ↓`  将当前行向上,或向下移动   
-  `Ctrl + Shift + K` 删除当前行   
-  `ctrl + 退格键 `删除上一个单词(或符号)。  
-  `ctrl + →` 移动到下一个单词(或符号)的开始处。  
-  `ctrl + ←` 移动到下一个单词(或符号)的开始处。  
-  `Ctrl+↑ ↓`        向上/向下滚动   
-  `Home` 到行首  
-  `End` 到行尾  
-  `Shift + Home` 扩展文本选区到行首  
-  `Shift + End` 扩展文本选区到行尾   
-  `Ctrl + Home` 到文件开始  
-  `Ctrl + End` 到文件尾  
-  `Ctrl + Shift + Home` 扩展文本选区到文件开始  
-  `Ctrl + Shift+ End `扩展文本选区到文件尾   
-  按住 `Ctrl` 键,然后按 `+` 键放大,按 `-` 键缩小编辑器视图。  
-  按住 `Ctrl` 键,然后滚动鼠标滚轮可放大或缩小编辑器视图。


##  复制通用 HTML 高亮代码块
  
在 aardio 代码编辑器点右键,打开右键菜单:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
然后点击『复制全部到 HTML 代码块』就可以复制 HTML 格式的高亮代码块到剪贴板。  
在很多在线编辑器都可以直接粘贴此代码块,兼容公众号、头条号、百家号等图文编辑器。  
  
如果点击『复制全部到 HTML 代码块』的同时按住 Ctrl 键,  
则复制的代码块支持自动换行。  
  
如果点击『复制全部到 HTML 代码块』的同时按住 Shift 键,  
则以文本格式复制高亮代码块的 HTML 代码到剪贴板。  
  
其他编程语言的代码可使用『 aardio 范例程序 > Web 界面 > web.view > 其他应用 > 通用代码块 』复制通用的 HTML 高亮代码块。  
  
##  快速选择代码段
  
按住CTRL用鼠标左键点击,可选择当前所属代码段。  
一个字符串、注释均被识别为一个代码段单位。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
  
如果按住Ctrl 点击的是代码中的超链接 - 略有不同的是会调用系统默认浏览器打开该网址。


##  在窗体设计器中巧用shift键对齐控件、统一控件大小

在窗体设计器中,选择多个控件,  
然后点击工具栏控件布局按钮,弹出菜单中可以对齐、统一控件大小等,如下图:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
你可以拖动鼠标,拖选多个控件。  
也可以单按 Ctrl 键不放用鼠标左键点击并切换一个控件的选择、取消选择状态。  
或者单按 Shift 键不放用鼠标左键点击并多选控件。  
如果同时按住 Ctrl + Shift 不放用鼠标左键点击控件会移动控件到最前面。  
按住 Ctrl + Shift 连续点击所有控件会重排 Z 序( 也是Tab 键顺序 )。  
  
**当你单按 Shift 键不放,最后一次用鼠标点击的控件会被设置为【参考控件】**,  
【参考控件】的选区控制点显示为黑色小方块,而其他选中控件显示为空心小方块。  
当对齐位置或统一大小时,以【参考控件】所在的位置或大小为标准 - 并调整到与【参考控件】相同。  
  
##  使用对齐辅助线
  
aardio新版有显示对齐辅助线的功能,可以非常方便的实现手动对齐。  
使用这个功能之前,要首先设置控件自动对齐到网格(在aardio设计器上方的工具栏点击【自动对齐到网格】按钮)。  
下面是演示:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
  
##  锁定控件
 

[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
可以在右键菜单中临时锁定控件(切换到代码视图、或关闭文件时自动解锁),  
如果一个控件被锁定以后就不能再调整大小(避免被无意拖动),如果被锁定控件与其他控件重叠,鼠标点击时将穿透重叠区域 -优先选择未被锁定的控件。  
  
另外,我们也可以在控件属性中设置“**临时锁定**”属性。  
  
##  九宫格自动缩放布局
  
aardio 中的窗口、控件自带窗口缩放自适应功能。  
每一个控件都可以在控件属性中设置自适应缩放、固定边距等属性。  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
  
而且我们可以一键为所有控件自设置这些参数,  
方法很简单,右键点窗体,然后在弹出菜单中点击「九宫格缩放布局」即可。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
请参考入门教程:[《快速掌握九宫格( 井字格 )界面布局》](https://mp.weixin.qq.com/s/W-1un6Q1n6r4ifOMgdcZxQ)  


##  跳转到定义 / 文件   
在代码中右键点击变量(或函数名等),  
在弹出的右键菜单中点击【 跳转到定义 / 文件 】,或直接按F12快捷键,  
可以快速跳转到该变量定义位置,如果选择的是标准库函数,则打开标准库定位到函数所在位置。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
注意不需要设置选区,直接鼠标右键点击就行了。  
  
也可以在代码中的文件路径上右键点击,使用【 跳转到定义 / 文件 】功能打开文件,例如:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
如果点选的不是aardio文件 - 将调用外部编辑器打开。  
如果点选的是目录 - 调用资源管理器打开。  
   
  
##  跳转到库文档 
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)

* * *

 
可以直接从桌面拖动一个文件放到代码编辑器里,  
将文件内容包含进一个普通字符串变量,编译发布后不再需要原文件(已经编译到程序的变量里面了)  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
  
##  $ 包含指令符

这个符号挺有意思,只要在文件路径前面加上这个符号, 就会将该文件编译为一个普通的字符串对象.  
例如  `var str = $"e:/我的图像/x.jpg"` 如果编译或发布以后, 你就不需要这个图像文件了,即使别人电脑上没有这个E盘也没有关系,因为文件已经编译成一个普通的二进制字符串了。在aardio编辑器里,只要将资源管理器里的文件直接往编辑器里一拖就行了,会自动加上这个包含指令符。


##  可以直接从资源管理器拖动外部文件到 aardio工程目录中

如下:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
  
也可以复制文件以后,在工程目录上点右键,在弹出菜单点击“**粘贴文件...**”即可。  
如果是 \*.ttf 格式的图标字体文件,请粘贴到标准库(或用户库)的fonts目录,如下图:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
 

[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 
可以直接拖动工程目录下的文件到代码内,  
会自动生成相对工程目录的短路径( aardio中路径首字符为"/"或"\\"表示应用程序根目录)  
如果被拖动的文件是一个窗体文件,将自动生成加载窗体的代码( 如上图 )  
 

可以用鼠标左键点选指定代码段文件,  
然后按住鼠标左键拖动代码到编辑器中,如下图:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D) 

##  状态栏查看文件路径

如果你在aardio中打开了太多的aardio文件,  
可以在aardio状态栏查看文件路径,如果状态栏显示的是其他信息,  
试试点击不同文件的选项卡来回切换一下 - 就可以看到路径了。  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)  
  
也可以在编辑器选项卡上右键菜单上点击【浏览此文件...】  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
注意 aardio 菜单项的标题后面带 "..." 省略号,通常表示此操作与系统资源管理器有关。  
【浏览此文件...】指的也是调用资源管理器中查看文件位置。
 
## 自定义发布( build )

打开 aardio工程,首次点击【发布】按钮生成EXE文件,  
会在工程下生成 “/.build/” 目录,如下图:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
双击打开“/.build/” 目录,可以看到生成了以下文件:  
  
“/.build/default.init.aardio”  
“/.build/default.main.aardio”  
“/.build/default.Manifest.xml”  

注意上面所有文件名第一个"."前面的部分应当与工程名相同,例如上面的几个文件对应的是 default.aproj 工程文件。  
  
“/.build/default.Manifest.xml” 为EXE文件的配置信息,Manifest一般不建议修改,详细了解该文件的作用请查阅MSDN以及相关资料,这里不多讲。  
  
其中“/.build/default.main.aardio” 是一个触发器,在生成EXE以后会被自动调用,例如用aardio写安装向导程序,就可以在这里调用7z等支持库把其他文件压缩到EXE文件尾部了。  
  
另外一个“/.build/default.init.aardio” 在发布以前被调用,例如我们在服务器上写fastcgi程序,因为cgi.exe在测试运行时一直是在占用状态,可以在此文件中添加下面的代码自动退出cgi进程。

```aardio
 //发布前触发  
 import ide;import process.file;  
 process.file.terminate( ide.getPublishPath() );
```

另外支持库**如果是库目录下面的默认库**,也可以**在同一库目录**添加 \\.build\\main.aardio 自定义一些发布操作,

很多库下面有这个文件,大家可以自己看一下。

## 自定义控件

打开窗口设计器,在控件工具箱中的最后一个控件是自定义控件。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
自定义控件可以修改类名 - 这里可以使用所有 win.ui.ctrl 名字空间下的类名。  
所以,你可以通过自定义控件创建所有其他的控件。  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
自己写自定义控件也很简单,  
只要在win.ui.ctrl名字空间下建立库文件,然后创建窗口就可以。  
可参考  win.ui.ctrl 下面的其他控件源码,以及 win.ui.ctrl.custom 控件源码

## 窗口特殊坐标

在窗口属性中,left, top 属性默认为相对于左上角的坐标,  
如果为-2表示显示在右下角,如果为 -1 就是屏幕居中,如下图:  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
对于窗体对象,还可以调用 winform.center() 函数居中显示,  
这个函数可以指定一个计算居中位置的父窗口,如果不指定就取自己的当前父窗口。  
这个函数会首先计算居中显示以后如果在可见范围内 - 就居中显示,否则自动调整到屏幕可见范围内显示。

## 调整控件 Z 序的技巧

Z 序指的是控件在 Z 轴上的排序,  
X 轴表示横坐标,Y 轴表示纵坐标,而 Z 轴表示的是窗口在屏幕上的前后叠加顺序,  
  
当你打开 aardio,从工具箱里往窗体上拖放控件,先放上去的 Z 序较小,后放上去的 Z 序较大。  
在运行时默认的控件会从Z序较小的开始创建、先创建的控件 Z 序较小。  
  
可以理解为:  
窗口内部有一个数组维护了所有子窗口的句柄,按 Z 序从小到大依次排列。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
在窗口设计器中,可以在控件上使用鼠标右键点击,  
在弹出的右键菜单中点击【前置】【后置】【最前面】【最后面】等菜单项调整Z序。  
  
移动控件到【最前面】的快捷键是:Ctrl + Shift + 鼠标左键。  
如果按住 Ctrl + Shift + 鼠标左键 依次点击所有控件,就是重排全部 Z 序。  
注意 Tab 键切换焦点顺序等于 Z 序。

## 设置代码编辑器字体与配色

设置代码编辑器字体与配色:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
  
调整编辑器字体大小有三种方式.  
  
1\. 在顶部功能区点击"放大","缩小"。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
2\. 在底部状态栏右侧 - 字体滚动选框上点击向上,或向下箭头, 也可以用鼠标滚轮快速设定。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
3\. 按住键盘上的Ctrl键, 然后在代码编辑器上用鼠标滚轮向上或向下滚动调整字体大小。  
  
## 跳选到代码指定快

[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
另外按 Ctrl + G 可以直接跳转到指定行 - 代码比较多的时候这个快捷键比较有用。  
  
## 编辑器中鼠标双击选中单词

在代码编辑器中双击文字,可以快速选中一个中文短句或一个英文单词。  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
  
## 快速缩进、撤消缩进
选中多行代码,按键盘上的Tab键可增加一个缩进,按Shift + Tab可撤消一个缩进。

## 快速注释

可以拖选一段代码,然后点击工具栏的【注释】按钮,  
如果当前没有任何选区,点【注释】按钮会注释输入光标所在的当前代码行。  
  
取消注释并不需要拖选代码,在任何一个注释块内点【取消注释】按钮就可以了,  
编辑器会自动检测出注释所在的开始位置、结束位置并取消注释。  
  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)

## 同一目录放多个工程文件

你知道吗?其同一工程文件目录下,其实可以放多个aardio工程文件,  
在新版工程管理器中可以方便的管理同一目录下的工程,aardio新版对于同一工程只会打开一个进程实例,所以任何地方双击打开工程 - 都会立即切换到已打开的实例,请看演示:  
[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)
上图为方便截图使用了右键菜单操作,在工程管理器中也可以鼠标左键双击直接打开工程,开始页可直接鼠标左键单击打开工程。  
  
**注意:**  

1. 同一个目录下的工程 可以共享同一用户库目录(即工程目录下的"/lib/"目录),但用户库可以按名字空间分类组织 - 这与标准库包含大量的库并不会混乱的道理是一样的。注意**在工程中删除用户库中的目录、或文件 - 会实际的删除硬盘文件**。  
  
2. 每个工程可以创建不同的虚拟目录,也可以包含相同的虚拟目录( 允许添加不同的文件 ),**在工程中移除虚拟目录、或文件 - 不会删除硬盘文件**。  
   
3. 每个工程的启动文件文件名可以是 \*.main.aardio 格式,例如 default.aproj的启动文件就是 default.main.aardio。

[](https://bbs.aardio.com/forum.php?mod=attachment&aid=fGVjMzkyM2IwfDE3MjA1NDMzNzV8MHwxMzIyMA%3D%3D)

## 代码首行注释的特殊用途

在 aardio 代码中,首行注释有一些特殊用途。  
  
在标准库、用户库、代码段中,首行注释可以更改显示在IDE中的标题(修改以后,需要在上层目录的右键菜单中点刷新目录)。  
![](http://bbs.aardio.com/data/attachment/forum/201603/23/013714gjyjywhh7oyjlz75.jpg.thumb.jpg)  
  
其他的普通 aardio 文件,如果首行注释为

```aardio
 //RUNAS//
```

那么在 aardio 中点击【运行】运行该代码时将会请求系统管理权限。  
如果在工程的 main.aardio 启动文件首行添加 //RUNAS//,那么将会自动修改 "\\.build\\default.Manifest.xml" 文件设置生成的EXE文件启动时需要管理权限。  
  
如果当前进程已经有管理权限,则可执行以下代码设置为开机启动自动获取管理权限(无弹框):  
  
```aardio
if(!_STUDIO_INVOKED){
    import sys.runAsTask;
   
    //创建开机任务
    var task = sys.runAsTask("WubiLex( 五笔助手 ) 启动任务","用于微软五笔的辅助工具。");
   
    //如果用户勾选开机启动
    if(winform.chkEnableSystemRun.checked){
        
        //设为开机以管理权限启动
        task.register("/tray");
    }
    else {
        //取消开机启动
        task.delete();
    }   
}
```

开机启动、或开机以管理权限启动 —— 都尽量在用户同意并勾选相关选项后再设置。


---

# 在 aardio 中发布后运行程序与在开发环境(IDE)直接运行程序的有什么区别?



一般桌面软件开发工具是先生成 EXE 才通运行,例如 C++,改几句代码也可能要先编译很久才能运行测试。在 aardio 开发环境里有一个功能,就是可以不生成EXE直接运行测试,这非常方便,改代码可以马上看到效果。

但是,由于在IDE(开发环境)中直接运行前并未生成 EXE 文件,与发布生成EXE 文件以后再运行还是会有一些微小的区别。 如果不苛求完美,那 IDE 中直接运行调试的方式还是非常方便的。不过需要注意以下几点。

1. 在 IDE 中直接运行程序,创建进程的 EXE 文件是 aardio.exe ,发布后则是你生成的 EXE 文件。io._exepath, io._exedir  的值都不一样,io.fullpath("~/") 的值也不一样,在aardio 中文件路径前加上 "~/" 表示创建进程的 EXE 文件所在目录。但是 io.fullpath("/") 的值始终是一样的,路径开始如果是一个斜杠表示应用程序目录(发布前为工程目录,发布后为 EXE 文件所在目录)。

2. 发布后运行 `_STUDIO_INVOKED` 的值为 `null` 空值,而在 IDE 中运行普通程序其值为 `"process"` - 这表示IDE为该程序创建了一个独立的进程。如果代码的第一个 import 语句为 `import ide`(这种程序只能在 IDE 中运行),那么在 IDE 中运行后 `_STUDIO_INVOKED` 的值为 `"thread"` - 这表示IDE在当前进程中创建了一个线程来运行程序。

3. 调用 `web.form.emulation(11001)` 指定 web.form 浏览器控件的 IE 兼容版本时需要注意,这个设置是针对当前 EXE 文件名生效的。例如你加上这句代码运行一次,再去掉这句代码运行一次,他的效果相当于这句代码的作用一直存在。因为在开发环境中直接运行时创建进程的EXE文件名始终是 aardio.exe,如果IE兼容版本被之前的代码改为IE11,而当前代码并没有设置,这就会导致发布前后使用的IE兼容版本不一致 - 当然这只有少数兼容性有问题的网页会出问题。因 IE11 内核已经普及 web.form 控件时已默认自动调用 `web.form.emulation(11001)` 。

4. 当你发布 EXE 文件时,aardio 会首先分析所有引用的库模块,排除没有引用的库,并且提前编译。而在 IDE 中直接运行代码,并没有这样事先分析引用库并编译的过程 - 并且引用的库模块文件编译顺序不一致,在 aardio 中有一个常量操作符 `::` 是在编译时起作用,并且会受到编译顺序的影响,这可能会导致混乱的结果,例如在发布前好好的,发布后报错(或者反过来) 。为了避免这一问题必须牢记在 aardio 基础语法中规定的规则:

    > 如果用 `::` 定义一个常量,那么在所有文件中他必须加上 `::` ,例如你不能在一个文件中写 `::XXX` 在另一个文件中写 `XXX` 而省略前面的 `::`,并且我们建议大家尽可能不要滥用`::`操作符,减少使用全局常量,尽可能使用名字空间模块化代码、以及变量。

5. 在使用窗口控件时,为了避免大家书写太多的 import 语句,所有控件的库模块都是按需自动导入的,也就是你使用了 listbox 控件,就会自动导入 `win.ui.ctrl.listbox`,在发布时发现你没有使用 listbox 就会移除对应的控件库模块 `win.ui.ctrl.listbox`。但是有时候 aardio 可能无法分析你使用了哪个控件,例如在 HTMLayout 的 HTML 文件中间接的引用了一些窗口控件,这时候就需要显示的添加 import 语句来声明可能动态引用的控件,例如用 `import win.ui.ctrl.static` 引入 static 控件。

6. 有一些代码可能是在生成 EXE 以后再动态的用 loadcode等函数加载的,也就是说在发布时并没有引用这些代码,那么 aardio 没有办法预测到你后面可能会需要哪些其他的库模块,那么这种代码你可能在 IDE 中运行是好好的,但发布后就找不到引用的库,类似这样的情况,应当提前用 import 语句声明后面可能引用到的库(或者把可能用到的库复制到生成的 EXE 下面的lib目录)。

    如果你不想在程序中 import 这些库,但又需要在发布后按需引用,那么可以将暂时不需要执行的 import 语句放在代码注释里,aardio 会分析注释中的 import 语句并将这些库编译到 EXE 中,可在将来按需引用这些库。

    如果你想在开发环境中 import 某个库,在发布后并不需要这个库,那你可以用 global.import 来导入库,aardio 在发布时会忽略 global.import 导入的库,不会将这些库编译到 EXE 中,示例:

    ```aardio
    if(_STUDIO_INVOKED){
        global.import("console");
        console.log("仅在开发环境导入库")
    }
    ```


一些技巧提示:

1. 在 aardio 中按 F5 快捷键可直接运行程序。  
    按 F8 快捷键可发布程序为 EXE 执行文件。

2. 可以用 _STUDIO_INVOKED 来判断不同的运行环境,例如

    ```aardio
    import console;

    if( !_STUDIO_INVOKED ){
        console.log("发布后的EXE在运行")
    }
    elseif( _STUDIO_INVOKED == "process"){
        console.log("在IDE中创建进程运行")
    }
    elseif( _STUDIO_INVOKED == "thread"){
        console.log("在IDE中创建线程运行")
    }

    console.pause(true);
    ```

3. 对于一些后期可能动态加载的库,但是目前并不想提前加载,可以把import语句写到注释里,例如:

    ```aardio
    /*
    import win.ui.ctrl.static
    */
    ```

    aardio 允许用这种方式声明 EXE 需要用到的库文件,但是在执行时并不引入。

4. 对于一些希望在IDE中引用,但又并不希望在发布时编译到EXE文件中的库,可以用下面的方式导入:

    ```aardio
    if(   _STUDIO_INVOKED ){
        global.import("console");  
    }
    ```

    aardio 允许你用 global.import() 函数引用一个库,但是在发布为 EXE 文件时跳过不引用。


---



# web.rest 模块使用教程:自动封装任意 HTTP 接口为 aardio 函数


aardio 里的 web.rest 设计了一种简单的 HTTP 接口描述规则 —— 可将指定的网址(可选指定模板参数)自动转换为本地函数对象。用法极其简单( web.rest 本身的实现也非常简单 )。

  
web.rest 是一种调用规则,也是 aardio 里的一个名字空间,web.rest 名字空间中的所有类都是 web.rest 的具体实现,所有 web.rest 类都继承自 web.rest.client,用法基本相同。  


## 简单示例 


```

import console;
import web.rest.jsonLiteClient;

//创建 HTTP( REST ) 客户端。
var http = web.rest.jsonLiteClient();

//声明 HTTP 接口对象
var api = http.api("http://httpbin.org/anything/")

/*
调用 HTTP 接口函数(已经自动转换为了本地函数)。
返回 JSON 自动转换为了 aardio 对象。
*/ 
var result = api.object.method({
    name = "用户名";
    data = "其他数据";
});

//输出服务端返回对象 —— 这个接口返回的是 HTTP 请求信息 
console.dumpJson(result);
console.pause();
```

上面的代码可以直接复制粘贴到 aardio 中运行,该接口返回的是 HTTP 请求信息 —— 用于测试学习非常方便。  

我们主要看下面这句发送请求调用 HTTP 接口的代码:  

```

var result = api.object.method({
    name = "用户名";
    data = "其他数据";
});
```

  
上面的函数调用做了几件事:  

1. 将 api.object.method 每一个下级成员名字追加到请求网址后面,多个名字自动用 "/" 分开。也就是自动生成下面这样的请求地址:  

```
 "http://httpbin.org/anything" + "/object" + "/method",
```

2. 将函数参数按网页表单编码规则转换为字符串提交,也就是自动执行下面的转换:

```
inet.url.stringifyParameters({
      name = "用户名";
      data = "其他数据";
})
```

3. 解析服务器返回的 JSON ,并作为函数返回值返回。

实际上这句发送请求的代码会被转换为下面的代码执行:  

```
var result = http.post(
  "http://httpbin.org/anything" + "/object" + "/method",
  inet.url.stringifyParameters({
      name = "用户名";
      data = "其他数据";
  })
);
```

上面的 object , method 我们称为『网址模板参数』。  
  

=======================================

##  网址模板参数

在声明 API 对象时, HTTP 接口网址中可选使用大括号 { } 包含模板参数名,示例:

```
"http://httpbin.org/anything/{org}/{name}/repos"
```

上面的 {org} , {name} 都是『网址模板参数』。

在调用 HTTP 接口时,HTTP API 对象的所有下级成员名称(模板实参)会逐个替换『网址模板参数』。  

例如下面的代码:

```
import console;
import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient();

//声明 HTTP 接口对象
var api = http.api("http://httpbin.org/anything/{org}/{name}/repos")

//调用 HTTP 接口函数
var result = api.aardio.jacen({
    name = "ImTip";
    description = "通用输入法状态提示";
});

console.dumpJson(result);
console.pause();
```

上面调用 api.aardio.jacen() 函数的实际请求地址为:

```
"http://httpbin.org/anything/"
  + "aardio" + "/" + "jacen" + "/" + "/repos"
```

模板实参 "aardio" 会替换模板参数  {org} ,模板实参 "jacen" 会替换模板参数 {name} ,依次从前向后替换(忽略模板名 )。  

也可以把斜杠写到模板变量里面,例如 {/name} ,这表示:如果调用时指定了模板实参则保留斜杠,否则去掉斜杆。

我们也可以在调用时明确指定模板实参的名字,代码如下:

```

import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient();

//声明 HTTP 接口对象
var api = http.api("http://httpbin.org/anything/{org}/{name}/repos")

//URL 模板实参,使用名值对指定了参数的名字
var urlParam = { org = "aardio", name = "jacen" } 

//调用 HTTP 接口函数
var result = api[urlParam]({
    name = "ImTip";
    description = "通用输入法状态提示";
});
```

在网址尾部可以用 `{...}` 指定不定个数的模板参数,例如:

```
import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient();

//声明 HTTP 接口对象
var api = http.api("http://httpbin.org/anything/{...}")
```

这个  `{...}` 可以省略不写,省略仍然支持不定个数模板参数,多个模板实参会自动用 "/" 分隔。

##  HTTP 请求方法

HTTP 协议常用的请求方法有:GET,POST,PUT,DELETE,PATCH,HEAD。

我们可以在调用 HTTP 接口函数时,可以将小写的 HTTP 方法名作为函数名调用 —— 就是以该 HTTP 方法发送请求。

例如:

```

import console; 
import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient(); 

//可在参数 @2 指定默认 HTTP 方法,不指定默认为 "POST"
var api = http.api("http://httpbin.org/anything/","POST") 

//发送 GET 请求
var ret = api.name.get( a = 1,b = 2);

//发送 POST 请求
var ret = api.name.post( a = 1,b = 2);

//发送 PATCH 请求
var ret = api.name.patch( a = 1,b = 2);

//发送 PUT 请求
var ret = api.name.put( a = 1,b = 2);

//发送 DELETE 请求
var ret = api.name.delete( a = 1,b = 2);

console.dumpJson(ret); 
console.pause(true);
```

这些 http 方法不仅仅可以作为函数使用,也可以作为转换对象 —— 用于转换后续的请求方法,例如:  

```
var ret = api.get.method( a = 1,b = 2);
```

注意 head, get, post, put, patch, delete 等作为 HTTP 接口函数名时不会视为『网址模板实参』被添加到请求网址中。

如果将这些默认 HTTP  方法名前面添加一个斜杠,就可以视为『网址模板实参』而非 HTTP 方法。例如:  

```
var result = api.get["/get"]()
```

上面的代码可以简写为:

```
var result = api.getGet()
```

类似的还有 postPost(), putPut() …… 等函数。

##  web.rest 类

web.rest 名字空间的类都继承自 web.rest.client,区别在于请求数据格式或服务器响应数据格式不同。  

最常用的 web.rest 类如下:  

1、web.rest.client

请求为网页表单编码,响应数据直接返回。

3、web.rest.jsonClient  

请求与响应数据都是 JSON 。

3、web.rest.jsonLiteClient

请求为网页表单编码,响应数据为 JSON 。

下面我们看一下 web.rest.jsonLiteClient 的主要源码:

```

import web.json;
import web.rest.client;

namespace web.rest; 

class jsonLiteClient{
  ctor( ... ){
    this = ..web.rest.client( ... ); 
   
    //请求数据 MIME 类型
    this.contentType = "application/x-www-form-urlencoded"; 
    
    //自动转换请求参数
    this.stringifyRequestParameters  = function(param,codepage){
      //省略其他代码
      return ..inet.url.stringifyParameters(p,codepage);
    } 
    
    //期望服务端返回的数据 MIME 类型
    this.acceptType = "application/json,text/json,*/*";
     
    //自动转换服务器响应数据
    this.parseResponseResult = function(s){
      //省略其他代码
      return  ..web.json.parse(s,true);
    }
  }; 
}
```

每个不同的 web.rest 类 —— 主要是修改了转换请求参数格式的 this. stringifyRequestParameters 函数,以及修改服务器响应数据格式的 this. parseResponseResult 。

##  获取错误信息

在调用 HTTP 接口时,成功返回解析后的响应数据,失败则返回 3 个值:null, 错误信息, 错误代码(可选) 。

这里要特别注意一下:aardio 中的很多函数都是成功返回非 null 值,失败返回 null 与 错误信息 等多个值,一般不会抛出异常。

例如:  

```

import console; 
import web.rest.client;
var http = web.rest.client(); 

var api = http.api("http://httpbin.org/status/500"); 

//发送 GET 请求
var ret,err = api.get( a = 1,b = 2);

if(!ret){

  //出错了输出错误信息
  console.log("出错了",err)

  //获取原始服务器响应数据
  http.lastResponse()
}
else {
  console.dumpJson(ret); 
}

console.pause(true);
```

可以运行 aardio **「 工具 > 网络 > HTTP 状态码检测」**查看返回状态码的相关信息。

http.lastResponse() 可以获取服务器原始响应数据 —— 如果事先导入了 console 库,则在控制台直接显示。

当然上面的代码一般在调试故障时才需要,一般没必要把错误处理写得这么细,上面的代码可以简化如下:

```

import console;
import web.rest.jsonLiteClient;
var restClient = web.rest.jsonLiteClient();   

var duck = restClient.post("http://httpbin.org/post",{
    用户名 = "用户名";
    密码 = "密码";
} )

//这句相当于 if( duck and duck["翅膀"] ) 
if( duck[["翅膀"]] ){ 
    io.print("不管服务器给我的是什么鸭子,总之有翅膀的都是好鸭子")
}
else {
    //出错了
    console.log("怎么回事没翅膀还能叫鸭子吗?");
}

console.pause();
```

duck\[\["翅膀"\]\] 使用了直接下标 —— duck 为 null 时 duck\[\["翅膀"\]\] 不会报错,而是返回 null 。

##  自定义 HTTP 请求头

可使用 http.addHeaders 自定义 HTTP 请求头,示例:

```

import console; 
import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient(); 

//如果所有请求都要添加的相同HTTP头,在这里指定
http.addHeaders = {
    ["Test"] = "test"
}

var api = http.api("http://httpbin.org/anything/") 

//发送 GET 请求
var ret = api.name.get( a = 1,b = 2); 

console.dumpJson(ret); 
console.pause(true);
```

  
如果每次请求都要动态修改HTTP 请求头,可以在 http.  beforeRequestHeaders 函数内返回需要添加的请求头,例如:

```
import crypt; 
import web.rest.jsonLiteClient;
import console; 

var http = web.rest.jsonLiteClient(); 

//每次请求都要动态修改 HTTP 请求头
http.beforeRequestHeaders = function(params){

    var apiKey = "";
    var secretKey = "";
    var authorization = {
        ["apiKey"] = apiKey;
        ["time"] = tonumber(time());
    }

    authorization["sign"] = crypt.md5(apiKey ++ secretKey ++ authorization.time)

    //通过返回值设置本次请求的 HTTP 头, Content-Type 不需要指定(会自动指定)
    return {
        ["Authorization"] = crypt.encodeBin(web.json.stringify(authorization))
    };
}

//声明 API 对象
var api = http.api("http://httpbin.org/anything/") 

//发送 GET 请求
var ret = api.name.get( a = 1,b = 2); 

console.dumpJson(ret); 
console.pause(true);
```

##  multipart/form-data 编码上传文件

示例:

```
import web.rest.client;
var http = web.rest.client();
var api = http.api("https://fontello.com");

//使用文件表单上传文件,可以指定多个字段
var result = api.sendMultipartForm(
    file = "@/test.json"; //上传文件路径前面必须加一个字符 @ ,其他字段不用加
});
```

上面代码也可以写为 api.post.sendMultipartForm() ;

如果需要处理上传进度,可以这样写:  

```
var result = api.sendMultipartForm( {
        file = "@/test.json";
    },function(sendData,sendSize,contentLength,remainSize){
        /*
        sendData 为本次上传数据;
        sendSize 为本次上传字节数;
        contentLength 为要上传的总字节数;
        remainSize 为剩余字节数;
        */
    }
);
```

  
##  上传文件 

直接上传文件示例:

```
import web.rest.jsonLiteClient;
var http = web.rest.jsonLiteClient();
var api = http.api("http://httpbin.org/anything");

//上传文件
var result = api.sendFile("/上传文件路径.txt");
```

  
也可以如下指定上传进度回调函数。

```
var result = api.sendFile( "/上传文件路径.txt"
    ,function(sendData,sendSize,contentLength,remainSize){
        /*
        sendData 为本次上传数据;
        sendSize 为本次上传字节数;
        contentLength 为要上传的总字节数;
        remainSize 为剩余字节数;
        */
    }
);
```

  
##  下载文件 

下载文件示例:

```
import console;
import web.rest.jsonLiteClient;
var http = web.rest.client();

var aardio = http.api("https://www.aardio.com");

/*
下载文件:
如果创建文件失败 receiveFile 函数会返回 null 及错误信息,否则返回对象自身。
*/
var ok = aardio.receiveFile("/.test.html").get();
```

  
可选如下指定下载进度回调函数:  

```
aardio.receiveFile("/.test.html",function(recvData,recvSize,contentLength){
    /*
    recvData 为当前下载数据。
    recvSize 为当前下载数据字节数。
    contentLength 为需要下载的总字节数。
    */
    console.log(,recvSize,contentLength)
}).get();
```

  
##  自动模式匹配

在声明 HTTP 接口对象时,还可以指定模式表达式 —— 用于自动匹配服务器响应数据,并返回匹配结果。

示例:

```

import console; 
console.showLoading("获取外网IP");

import web.rest.client;
var http = web.rest.client();

//声明 API,参数 @3 指定的模式表达式用于匹配返回数据
var api = http.api("http://myip.ipip.net",,"(\d+\.\d+\.\d+\.\d+)");

//调用 HTTP 接口 
var ip = api.get();

//显示查询结果
console.log( ip  );

console.pause(true);
```

  
##  普通 HTTP 客户端

web.rest 基于 inet.http ( [请参考:玩转 inet.http](http://mp.weixin.qq.com/s?__biz=MzA3Njc1MDU0OQ==&mid=2650932255&idx=1&sn=d8d6055dac486d0d2c827f5ce543fb3d&chksm=84aa2fa5b3dda6b3ce67731de3a19576aabb385b675c87259a6591a19cdcd4410132e6fde7fa&scene=21#wechat_redirect) ),也可以作为增强版的 HTTP 客户端功能,示例:

```
import console;
import web.rest.jsonClient;

//创建 HTTP 客户端
var http = web.rest.jsonClient();

//发送 GET 请求
var ret = http.get("http://httpbin.org/anything",{
    name = "用户名";
    data = "其他数据";
})

//发送 POST 请求
var ret = http.post("http://httpbin.org/anything",{
    name = "用户名";
    data = "其他数据";
})

console.dumpJson(ret);
console.pause();
```

与 inet.http 不同的是,如果服务端返回数据的编码声明不是 UTF-8,web.rest 会自动转换为 UTF-8 。

##  直接提交原始数据

使用 web.rest 调用 HTTP 接口时,如果提交数据是一个字符串,则不作任何处理 —— 直接提交。

示例:

```

import console;
import web.rest.jsonClient;
var http = web.rest.jsonClient(); 

//示例 JSON
var json = /*
{
    "data":"其他数据",
    "name":"用户名"
}
*/

//如果提交数据是字符串,则不作任何转换直接发送
var ret = http.post("http://httpbin.org/anything",json)

console.dumpJson(ret);
console.pause();
```

  

---


# aardio 的正确拼写。

`aardio`  在任何时候应当保持小写,不要单独大写 `aardio` 的首字母。


---


# 用 aardio 创建一个简单的 winform 窗口

下面是一个使用 aardio 创建一个简单 winform 窗口的示例代码。这段代码将创建一个带有按钮的窗口,并在按钮点击时显示一个消息框。

```
// 导入 winform 类库
import win.ui; 

// 创建 winform 窗口
var winform = win.form(text="我的 aardio 窗口");

// 在窗口上添加一个按钮控件
winform.add({
    button = {
        cls: "button", // 指定按钮控件类名
        text: "点击我", // 按钮上的文字
        left: 100, // 按钮左边的距离
        top: 100, // 按钮上边的距离
        width: 100, // 按钮宽度
        height: 30 // 按钮高度
    }
});

// 设置按钮的点击事件处理程序
winform.button.oncommand = function(id, event) {
    win.msgbox("你点击了按钮!"); // 当按钮被点击时显示一个消息框
};

// 显示窗口
winform.show();

// 进入消息循环,等待用户操作
win.loopMessage();

```

这段代码首先导入了 winform 类库,然后创建了一个带有指定标题的 winform 窗口。接着,它在该窗口上添加了一个按钮,并设置了按钮的点击事件。最后,显示窗口并进入消息循环,以便响应用户的操作。当按钮被点击时,会显示一个消息框。

---


# 创建 winform 窗口,在 winform 窗口上添加控件的步骤与要点。

下面我们用一个简单的 aardio 代码示例说明创建 winform 窗口界面,并且在窗口上添加控件的要点。

aardio 代码示例:

```
//必须通过 win.ui 库导入 win.form 类。
import win.ui;

/*
创建 winform 窗口的要点
1. win.form 类在 win.ui 库定义,要先执行 `import win.ui` 导入 `win.form` 类。
2. win.form 的参数是一个表对象( table 对象),表最外层的大括号 `{}` 可以省略。
3. 创建参数的 text 字段用于指定窗口标题,不指定窗口标题时默认显示为无标题栏窗口(也不会显示标题栏按钮,例如关闭窗口的按钮)。
*/
var winform = win.form(text="aardio form")

/*
在 winform 窗口上创建控件的要点。
1. winform.add 函数的参数是表个表对象( table 对象),表最外层的大括号 `{}` 可以省略。
2. winform.add 函数的参数应当是包含名值对的表,名字为控件名,值为创建控件的参数,
例如下面创建了一个名为 winform.button 的控件,该控件的创建参数为 `{cls="button";text="Button";left=283;top=253;right=518;bottom=315;z=1}` 。
3. 控件的创建参数最重要的是 cls 字段,cls 字段指定了创建该控件的类。
cls 的属性值用于指定 win.ui.ctrl 名字空间的类名。例如创建参数指定了 `cls="button"`,则调用 win.ui.ctrl.button 创建一个按钮按件。
*/
winform.add(
button={cls="button";text="Button";left=283;top=253;right=518;bottom=315;z=1}
)

//显示窗口
winform.show();

//启动窗口界面线程的消息循环,消息循环用于处理窗口消息,响应用户操作并触发事件函数等等。
win.loopMessage();
```

winform.add 的参数应当是一个包含名值对的表对象(名字为控件名,值为控件的创建参数表),当函数参数只有一个表参数时则可以省略外层的大括号 `{}`,省略表外面的  `{}` 以后不能再用 `:` 替代 `=` 号作为表的键值对分隔符。

如果使用 aardio 的可视化窗体设计器创建窗体并添加控件,窗体设计器生成的代码(包含创建窗体与控件的代码)默认会放在 开始标记 `/*DSG{{*/` 到结束标记 `/*}}*/` 之间。aardio 在打开 *.aardio 格式的 aardio 源文件时如果找到 `/*DSG{{*/` 与  `/*}}*/` 标记就会在窗体设计器中打开源文件。



---


# aardio 如何向控制台窗口输出一句话,例如 "Hello, World!"

代码示例: 

``` 
import console; 
console.log("Hello, World!");
console.pause();
``` 

aardio 中一般不使用 print 函数输出,而是使用 console.log() 输出文本到控制台窗口。在 aardio 中 print 函数是用来输出模板代码的,这是其他编程语言是不同的。

---

# 用 aardio 如何调用 web.view 显示网页并与 JavaScript 交互?

aardio 可以使用 web.view 创建 WebView2 浏览器控件以显示网页,
并且 aardio 可以方便的现 JavaScript 交互调用。

以下为示例代码:

```

//创建 winform 窗口
import win.ui; 
var winform = win.form(text="窗口标题")

//创建 WebView2 浏览器控件
import web.view;
var wb = web.view(winform);

// 导出 aardio 函数
wb.external = {
	add: function(a, b) {
		return a + b;
	}	
} 

// 指定网页 HTML 代码
wb.html = /******



 
******/; //显示窗口 winform.show(); //启动界面消息循环 win.loopMessage(); ``` --- # aardio 调用 Python 语言 ## 如何切换 Python 虚拟机版本 aardio 提供以下扩展库用于加载不同版本的 Python 虚拟机环境。 - `import py2` 导入 Python 2.7 扩展库,支持 XP 及 XP 以上系统。 - `import py3` 导入 Python 3.8 扩展库,支持 Win7 及 Win7 以上系统。 - `import py3.4` 导入 Python 3.4 扩展库,支持 XP 及 XP 以上系统。 - `import py3.10` 导入 Python 3.10 扩展库,支持 Win10 及 Win10 以上系统。 使用时应去掉扩展库副版本号,例如: ``` import py3.4; console.log( py3.version ); ``` 在一个程序中,不应同时导入多个不同版本的 Python 扩展库, aardio 中所有 Python 扩展库都自带绿色 Python 运行时。 aardio 中 process.python 库支持任意版本 python ( 支持 64位 Python )。 但是 rocess.python 是在进程外执行 Python.exe 以运行 Python 代码。 所以并不能像 py3,py2 扩展库那样可以调用嵌入 aardio 进程内的 Python 解释器执行 Python 函数。 一个解决方案是使用 JSON-RPC 协议实现跨进程调用 RPC 函数。 下面是一个完整的例子。 ```aardio import console; import process.python; //启动 JSON-RPC,参数可以是 Python 代码,Python 文件路径,或 Python 模块名称 var py = process.python.jsonRpc(` #定义允许客户端调用的类 class MyTarget(object): def greet(self, name): return "Hi, %s!" % name def add(self, a,b): return a + b #启动 JSON-RPC 服务端 from jsonrpyc import RPC; RPC( MyTarget() ) `); //使用 JSON-RPC 协议调用 Python 进程提供的函数。 var rep = py.greet("Jacen") //获取 JSON-RPC 返回的 result 字段。 var ret = rep[["result"]]; console.log(`调用 py.greet("Jacen") 成功,返回值:`,ret); console.pause(); ``` ## aardio 调用 Python 函数入门示例 ```aardio import console.int; import py3; //导入 Python 模块 var math = py3.import("math"); //调用 Python 函数, var num = math.floor(22.3); //在控制台输出数值。 console.log(num); ``` Python 返回的浮点数值、不大于 53 位的整型数值、布尔值、字节数组 会自动转换为纯 aardio 值。 其他 Python 对象在 aardio 存为 py3.object 对象(PyObject)。 PyObject 可以在 aardio 中也可以像普通对象一样使用。 可以调用其成员函数、读写属性、通过下标读写索引项、并支持常用运算符。 也可以通过 pyObject.parseValue() 函数转换为纯 aardio 值(通过 JSON 自动转换)。 ## 在 aardio 中执行 Python 代码 示例 ```aardio import console.int; import py3; py3.main.testData = "可以这样预先指定 Python 全局变量"; //Python 代码,注意 Python 对空格有严格要求,乱按空格报错不是 bug。 var pyCode = /** def getList(a,b): return [a,b,testData] **/ /* 执行 Python3 的代码, 如果参数 pyCode 为类似 "/res/py.aardio" 这样的 aardio 代码路径, 则支持模板语法: chm://the%20language/template_syntax.html */ py3.exec( pyCode ) //从 py3.main 模块调用 Python 代码定义的函数 var pyList = py3.main.getList(12,23); //可以如下遍历 pyObject 对象。 for( pyItem in pyList.each() ){ console.log(pyItem) //基础类型已转换为纯 aardio 值,其他为 py2.object } //pyObject 支持 table.eachIndex 创建的迭代器 for i,pyItem in table.eachIndex(pyList){ console.log( i,pyItem ) //基础类型已转换为纯 aardio 值,其他为 py2.object } //转换为纯 aardio 值 var list = pyList.parseValue() console.dump(list); ``` ## 在 Python 中调用 aardio 对象 示例: ```aardio import winex; import console.int; import py3; //可以这样预先指定 Python 全局变量 py3.main.console = py3.export(console);////用 py3.export(com) 直接将 aardio 对象完整导入 Python var pyCode = /** def testPy(winex): # 在 Python 里直接使用 aardio 对象,简单得就像在 aardio 里调用 aardio 对象 console.log("在 Python 里调用 aardio 库函数") # 还能支持 aardio 创建的迭代器 for(hwnd,title,threadId,processId) in winex.each(): console.log(title) **/ //执行 Python 代码 py3.exec( pyCode ); //调用 Python 函数 py3.main.testPy( py3.export(winex) //也可以通过参数将 aardio 对象导出给 Python ); ``` 1. 简介 当 py3.export() 作为对象构造函数使用时, 可用于导入参数指定的 aardio 对象为 Python 对象。 这种「代理对象」在 Python 中将保持对原始 aardio 对象的引用,也就是传址而非传值。 2. 解决了什么问题 aardio 在与 Python 交互时, 默认除了布尔值、浮点数值、小于53位的整数值,布尔值转换为纯 aardio 值以外。 在 aardio 这些对象存为 pyObject,并保留对原始 Python 对象的引用。 而 aardio 对象在自动转换为 Python 对象时, 默认是传值而非传址,Python 中不保留对 aardio 对象的引用。 py3.export 的特别之处在于可以导出 aardio 模块到 Python, 在 Python 中引用与操作原始的 aardio 对象。 例如: ```aardio py3.export.aardio = { exportFunction = function(){ } } ``` 然后可以在 Python 中 import aardio,调用这个模块的所有成员函数。 3. py3.export( aardioObject ) 使用要点 py3.export( aardioObject ) 可以导出 aardio 中的 table,cdata,class,function 等 aardio 对象, 也可以自动导出 aardio 迭代器,适用于 Python 的 for 语句(aardio 迭代器在 Python 中返回 tuple 而非单个值) 对于整型数值 py3.export( number ) 默认转换为 Python 中的整型,而非浮点数。 其他类型(例如字符串) py3.export 不作转换直接返回。 Python 调用导出函数的返回值也会由 py3.export() 再次导出 。 Python 调用 aardio 导出函数的参数会自动调用 parseValue() 解析为纯 aardio 值。 4. 使用限制 注意 py3.export 只能在 Python 启动线程中使用, 这是 Python 的限制与 aardio 无关! aardio 可以支持真多线程, 也提供了 py3.lock 简化了 Python 全局锁操作,支持除 py3.export 以外的接口。 但是:py3.export 不支持多线程。 在非 Python 主线程下,py3.export 为 null 值, 使用 py3.mainThread 也可以检测是否 Python 主线程, 其实 Python 因为有全局锁 —— 无法实现真正的多线程, 调用 process.python 创建多进程来替代多线程可能更方便一些。 --- # 在 aardio 中调用 .NET 与 C# 编程语言 aardio 中可以直接使用 .NET 与 C# 组件,下面是一个简单示例。 ```aardio import console.int; import dotNet; //导入程序集与名字空间 https://docs.microsoft.com/zh-cn/dotnet/api/?view=netframework-2.0 dotNet.import("System"); //System 也可以用 import System 导入 //调用类的静态成员函数,当然也可以这样读写类的其他静态成员属性。 var isValidHost = System.Uri.CheckHostName("www.aardio.com") //用 System 名字空间下面的类构造对象实例,官方建议规范:对象实例建议首字符小写 var uri = System.Uri("https://www.aardio.com/test?q=aardio") //读或写 .NET 对象的实例属性 console.log( uri.Host ) //调用 .NET 对象实例的成员函数 console.log( uri.GetHashCode() ) //.NET 对象还可以用 tostring 转换为字符串 console.log( tostring(uri) ) //这里的 tostring可以省略。 //可用 com.Release() 主动释放 .NET 对象。但一般没有必要这样做,aardio 会自动释放这些 .NET 对象。 com.Release( uri ); //.NET 对象在 aardio 中底层都是 COM 对象 ``` aardio 调用 .NET 非常方便,aardio 开发环境提供了大量调用 .NET 组件的范例。 Win7 自带 .NET 3.5.1,支持 lambda Win8 自带 .NET 3.5.1 + .NET 4.5 Win10 自带 .NET 4.6 ,.NET 4.x 支持 dynamic 对象 + 异步任务,.NET 4.5 支持 task.Run 。 Win10 1709 自带 .NET 4.7.1 ,支持 ValueTuple Win11 自带 .NET 4.8 NET 虽然有很多版本,但核心运行时只有 CLR 2.0 与 CLR 4.0 的区别。 如果使用了一些非常新的 C# 语法 —— 用 VS 编译成 DLL以后在 CLR 4.0 下运行时没有问题的。 aardio 可以自动兼容 CLR 2.0 / CLR 4.0 编写的程序集。 aardio + .NET 开发有更好的兼容性,对 .NET 版本没有严格要求,可以重用大量的 .NET 组件, 并且可以编写出体积小、不依赖非系统 DLL 的独立 EXE 文件( aardio 也可以方便地内存加载外部程序集) 系统自带的都是 .NET Framework 。 如果 NuGet 包里找不到 .NET Framework 的程序集, 那么可以改用 NET Standard 2.0 的程序集。 自 .NET Framework 4.5 及以上 起支持 NET Standard 1.0, 自 .NET Framework 4.6.1 起支持 NET Standard 2.0, 但实际上 NET Standard 2.0 推荐的最低版本是 .NET 4.7.2 。 --- # aardio 字符串与其他编程语言的异同 1. aardio 的字符串与其他编程语言的区别 1. aardio 字符串可以包含'\0',可以包含二进制数据,甚至可以将二进制文件直接放到字符串变量中 例如: `var 字符串 = $"\test.rar"` 。 2. 提供两种操作符分别获取字符串中指定位置字符的字节码或字符, 使用普通下标`[]`获取单字节的字节码,使用直接下标`[[]]`获取单字节字符。 3. 提供多种不同形式表示字符串字面值的语法. 1. 双引号与反引号中表示原始字符串,与自然用法一致。 2. 单引号中表示转义字符串,与 C 系语法一致 3. 可以在赋值语句中用注释语句表示复杂字符串,方便地表示包含各种引号的字符串, aardio 独特的段注释更可用于方便的表示大段文本。 4. 在代码中表示字符串时,除使用行注释表示字符串以外,都可以包含换行. 其中双引号反反引号内的字符串将换行总是解释为'\n'('\r'被忽略),而单引号内的字符串忽略换行符,段注释内换行总是解释为'\r\n'(即使在源代码中不包含'\r',aardio 也会保证段注释内的换行解释为'\r\n' ),而行注释不能包含换行。 2. 与其他语法一些类似的地方. 1. aardio 保证字符串内存的尾部总是用'\u0000'(占用两个字节)标志结束,所以能兼容 API 中的字符串类型. 2. 单引号中的转义字符串中转义符用法与 C 系语言类似. 3. 在双引号或反引号中,可使用两个引号表示一个引号,与 VB 类似。 示例: ```aardio import console; var str = "ABCD" //定义一个字符串 console.log("显示第二个字符的数值", str[2] ) //使用普通下标获取字节码 console.log("显示第二个字符", str[[2]] ) //使用直接下标获取单字符字符 console.log("后面是字符B","B") console.log("后面字符B的数值",'B'# ) //字符放在单引号内,并在后面跟一个#号 console.log(str[2] == 'B'# ,"字节码") //显示true,他们是相等的 console.log(str[[2]] == 'B',"字符") //显示true,他们也是相等的 //转换为一个数组 bytes = { string.unpack(str) } console.dumpTable( bytes ) //显示{65;66;67;68} //将字节码数组转换为字符串 str = string.pack( bytes ) console.log( str ) // 显示"ABCD" //字节码就是一个8位的数值.在静态结构体中的数型名是BYTE struct = { BYTE str2[] = {65;66;67;68}; //一个BYTE数组在内存里就指向一个字符串 BYTE str[] = "ABCD"; //一个字符串在内存里也就是一个BYTE数组 } //字符串就是一个特殊的数组, //所以他可以取到数值的字节码,也可以显示为字符,有两种表现形式. /* buffer 是纯字节数组,大多数函数的字符串参数都兼容 buffer 与字符串。 例如 type.isString() 传入 buffer 或字符串都会返回 true。 */ var buf = raw.buffer("ABCD"); console.log(buf) console.pause() ``` ---