aardio 文档

aardio 语法速览

第一个对话框

import win; //导入库
win.msgbox("Hello, World!");

 第一个控制台程序

一句代码的控制台程序:

print("Hello, World!");

aardio 模板代码 ?>字符串<? 会被编译为 print("字符串")。print 可指向不同实现(网站应用则指向 response.write )。

使用 console 库:

import console; 

console.showLoading("请稍候");
thread.delay(1000); //暂停 1 秒

console.log('你好');//支持多参数,可输出纯数组内容

//输出表的数据
console.dump({a=123;b=456});

//输出为 JSON
console.dumpJson({a=123;b=456});

if(console.askYesNo("按 Y 或 N")){
    console.error("错误!")    
}

//等待按键
console.pause();

 第一个窗口程序

import win.ui;
/*DSG{{*/
var winform = win.form(text="窗口程序")
winform.add(
button={cls="button";text="点我";note="按钮";left=435;top=395;right=680;bottom=450;color=14120960;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 = "Hello, World!";

    //可输出任意个数任意数据类型,尾部添加换行
    winform.edit.print(" 你好!");
}

winform.onClose = function(){
    if( ! winform.msgboxTest("关闭吗?") ){
        return false;// 返回任何非空( null )值阻止关闭
    }
} 

//显示
winform.show();

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

窗口程序必须使用 import win.ui 导入 win.form 窗口类

aardio 窗体即代码、代码即数据,通常一页代码就可以实现一个完整程序,上下文很短

第一个网页应用

import win.ui; 
var winform = win.form(text="标题")

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

// 导出为 JavaScript 的 window.aardio 对象
wb.external = {
    add = function(a, b) {
        return a + b;
    }   
} 

// 写入网页
wb.html = /******
<div id="result"></div>

<script> 
(async ()=>{

    //调用 aardio 函数
    var num = await aardio.add(1, 2)
    document.getElementById('result').innerText = num
})()
</script>
******/;

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

打开网址:

wb.go("https://www.example.com/")

打开工程资源文件:

import wsock.tcp.simpleHttpServer; //用于支持从资源文件加载网页
wb.go("/res/index.html");

import 语句

aardio 默认已导入内置库 raw,string,table,math,time,thread,io(包含 io.file) 到全局命名空间,其他库都必须先用 import 导入当前线程才能使用。

import 语句可导入命名空间类的命名空间,不能导入单个函数对象。

 命名空间

默认命名空间为全局命名空间( global 对象 )。在其他命名空间访问 global 成员请加 .. 前缀,例如 ..console

示例:

//打开命名空间
namespace test.a.b{

    string = " 当前命名空间的成员 " // string 等价于 self.string

    string = ..string.trim( string ) // ..string 等价于 global.string
}

 注释

// 行注释,直到行尾


/*  
块注释(可包含换行),首尾标记的星号数目必须相同。
*/

变量与赋值


//var 语句定义局部变量
var n = null;
var str = "局部变量";
var a,b,c = 1,2,3; //初始化多个局部变量,1 赋值到 a, 2 赋值到 b, 3 赋值到 c

//通过 self 访问当前命名空间成员
self.str = "当前命名空间变量";
myStr = "如果没有同名局部变量则可省略 self 前缀"; 

..globalObject = "全局变量"

 语句与语句块

aardio 使用 {} 标记语句块。

{
    var str = "字符串"; //语句尾可选加分号
    var n = null; //局部变量具有块级作用域
}

 函数


//定义函数,三连点表示不定参数。
var func = function(a, b, ...) {

    var args = [...] //转为数组

    return a + b,"支持多返回值";
}

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

必须用 {} 包围函数体。

 null 值

未定义变量默认为 null值。

# 操作符获取 null 值长度返回 0 (不报错)。

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

在条件表达式中 null 值为 false 。

 数值

var num = 123.01; 
var num2 = 123_456; //分隔符
var hex = 0xEFEF; //16 进制
var num3 = 2#01010; //自定义进制

 算术运算

var num = 3 + 2;
var num = 3 - 2;
var num = 3 * 2; //乘
var num = 3 / 2; //除
var num = 3 % 2; //取模
var num = 3 ** 2; //两个星号表示幂运算

 按位运算

//按位取反
var n = ~2#101

//按位与
var n = 2#101 & 2#100

//按位或
var n = 2#101 | 2#110

//脱字符表示按位异或  
var n = 2#101 ^ 2#110

//按位左移
var n = 2#101 << 1

//按位右移
var n = 2#101 >> 1

//按位无符号右移
var n = -2 >>> 18

 逻辑操作符

逻辑非操作符可以使用 not!

逻辑与操作符可以使用 and  、&& 或者 ?

逻辑或操作符可以使用 or|| 或者 :  

a := b 等价于 a = a : b

 三元运算符

var n = 1;

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

如果 a ? b : c 这个表达式里 bfalse,则该表达式总是返回 c

 字符串

var backslashEscapedString = '单引号内使用反斜杠 \ 作为转义符(编译时处理),忽略源代码中的原始回车换行符。';

var rawString = "双引号内是原样字符串,反斜杠 \ 就是普通字符(编译时不处理转义),源码中的回车换行会规范化为单个换行符。使用 "" 表示单个双引号";

rawString = `反引号内也是原样字符串,编译阶段不识别 \ 转义符,使用 `` 表示反引号。`;

//双引号包含文件路径,\ 不需要写为 \\
var filepath = "C:\path\to\file.txt"

//双引号包含模式串,\d 不需要写为 \\d
var str = string.match("123","\d+")

//原样字符串前加 $ 前缀,表示将指定文件内容编译为字符串
var embeddedFile = $"/example.jpg"

var nullChar = '\0' //空字符

var nullByte = '\0'# //空字节(数值 0),单引号后加 # 取字节码(宽字符取 UTF-16 编码)

字符串操作:

var str = "abc中文"

//从 1 截取到 3,按字节计数
var s = string.slice(str,1,3);

//负数自尾部倒计数,参数 4 为 true 则以字符计数
s = string.slice(str,-2,-1,true);

//取倒数第 3 个字符
s = string.slice(str,-3,-3,true)

//取后 2 个字符,正数表示长度,参数 3 为 true 表示以字符计数
s = string.right(str,2,true)

//右侧截取,负数表示自左侧第 2 个字节开始向右截取
s = string.right(str,-2)

//取前 2 个字节,正数表示长度,负数表示截取到右侧指定字节
s = string.left(str,2)

//遍历所有字节
for(i=1;#str){
    var byte = str[i] //字节码(数值)
    var singleByteStr = str[[i]] //单字节字符串
}

//如果参数 @2 不指定分隔符,则以字符为单位拆分
var chars = string.split("a汉字")

//输出 ["a","汉","字"]
print(chars)

//用模式匹配遍历所有字符,冒号匹配多字节字符
for char in string.gmatch(str,":|."){ 
    print(char)
}

//取第一个字节码
var byte1 = str[1] 

//取第一个字符的 Unicode 码点(可大于 0x10000),按字符计数。
var charCodePoint = string.charCodeAt(str,1);

//重复字符串
str = string.repeat(3/*重复次数*/,"a") //返回 aaa

拆分与合并字符串:

var str = "hello,,,-world"

//拆分,参数 @2 里每个字节都是分隔符,不支持模式语法
var arr = string.split(str,",-") //返回 ["hello","","","","world"]

//以 <> 内部的整个字符串作为单个分隔符
var arr = string.split(str,"<,,,->") //返回 ["hello","world"]

//拆分,参数 @2 可以模式匹配语法指定分隔符
var arr = string.splitEx(str,"[,\-]+") //返回 ["hello","world"]

//合并数组( string 或 buffer  )到字符串,参数 @2 指定分隔符
var str = string.join( ["字符串1",raw.buffer("buffer")] ,'\n')

找出数组中最长的字符串:

var longest = reduce(["apple", "banana"], lambda(a, b) #a > #b ? a : b); 

用注释表示字符串

赋值语句中紧邻 = 右侧的注释表示原样字符串:

var longRawString = /***
- 块注释首尾标记内的星号数目必须相同。
- 忽略块紧邻注释首尾标记的首个空行(可选)。
- 其他换行总是会规范化为回车换行符 '\r\n'。 
***/

出现在其他位置的注释不是字符串。

字符串的文本编码

var utf8 = "字符串默认为 UTF-8 编码"

var utf16 = '单引号加 u 后缀表示 UTF-16 编码字符串'u

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

::User32.MessageBoxW(0,"内容","标题",0);

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

在调用 ::User32.MessageBox 时,aardio 会优先使用 Unicode 版本的 ::User32.MessageBoxW 函数。当然,也可以主动在 API 函数名后加上大写的 W 尾标以声明 Unicode 版本 API(实际函数名尾部没有 W也可以加)。

 二进制字符串

aardio 字符串既可包含纯文本或二进制数据。

var bin = '可包含二进制数据,\0 后面不会被截断' 

使用下标读写字节:

var str = "abc"

//普通下标操作符返回字节码
print( str[1] ) 

//直接下标操作符(双层中括号)返回包含单字节的字符串
print( str[[1]] ) 

for(i=1;#str;1){
    print( str[[i]] + "的字节码是:" + str[i]);
}

对字符串使用下标或直接下标,索引超出范围返回 null 而不会报错。

对字符串使用 # 操作符、下标 [] 或直接下标 [[]] 都是按字节计数。

也可以调用 string.unpack( str,startIndex,endIndex ) 将字符串拆分为字节码, 例如 var b1,b2,b3,b4 = string.unpack('\x01\x02\x03\x04') 返回 1,2,3,4 。而 string.pack([1,2]) 则返回字符串 '\x01\x02'

UTF-16 字符串

对于 UTF-16 字符串,下标或直接下标以双字节为一个单位计数( 4 字节代理对计为 2 个单位 ),示例:

var utf16Str = '你好'u

//# 按单字节计数
for(i=1;#utf16Str/2){
    var wideCharCode = utf16Str[i] //宽字符编码(数值)
    var wideCharStr = utf16Str[[i]] //宽字符(字符串)
}

使用 ustring 库获取与转换 Unicode 代码:

import ustring;

var utf16String = '💻字符串'u

//取 Unicode 码点数组,参数支持 UTF-8 或 UTF-16 字符串。
var charCodePoints = ustring.toCharCodes(utf16String);

//码点数组转为 UTF-16 字符串
utf16String = ustring.fromCharCode(charCodePoints)

//码点数组(或多个码点参数)转为 UTF-8 字符串
var utf8tring = string.fromCharCode(charCodePoints)

缓冲区( buffer )

使用 raw.buffer 创建的固定长度、可读写的内存对象,用于存储二进制字节串,数据类型为 buffer

创建 buffer

在几乎所有 aardio 字符串函数中,buffer 都可以替代字符串。 调用 type.isString(stringOrBuffer) 传入字符串或 buffer 都会返回 true
但字符串只读,而 buffer 可读写。

示例:

var buf = raw.buffer(20) //所有字节默认初始化为零

buf = raw.buffer(3,'\0'#) //自定义初始字节码

buf = raw.buffer(6,"初始化数据") //长度为 6,初始化数据超长截断,过短补零(null byte)

buf = raw.buffer("字符串转 buffer")

//结构体
var structObject = {
    BYTE ubyte;
    byte bytes[] = buf;
}

//结构体转 buffer
buf = raw.buffer(structObject)

//可作为字节串(bytes)用下标读写字节码,起始索引为 1
buf[1] = 65

//合并数组( string 或 buffer  )到新的 buffer
var buf2 = raw.join([buf,"字符串"])

//buf2 = buf ++ "str" //错误!buffer 不支持连接操作符

//复制内存数据
raw.copy(buf2/*或指针*/,"源数据"/*,可选指定要复制的字节长度*/)

//复制内存数据,支持更多参数
raw.copy2(buf2,0/*dstOffset*/,"源数据"/*,srcOffset,copySize*/)

//截取内存返回 buffer 对象
buf = raw.slice(buf2/*或字符串、指针*/,4/*startIndex*/,6/*endIndex*/)

aardio 中索引( index )自 1 开始,内存偏移量( offset )自 0 开始

十六进制编码

var str = "abc"

//16 进制编码,可选用参数 @2 指定前缀
var hex = string.hex(str,"%") //返回 "%61%62%63"

//16 进制解码,可选指定前缀
var str = string.unhex(hex,"%") //返回 "abc"

str = string.unhex("0x61,0x62","0x") //返回 "a,b"

var hex = string.hex(str) //无前缀,返回 "616263"

 字符串连接

//用 ++ 连接字符串
var str = "a" ++ "b";

// ++ 前后紧邻引号,可略写为单个 + 号。
var str = "字符串1" + "字符串2";

//调用内置函数拼接 2 ~ n 个参数,忽略 null 参数
var str = string.concat("字符串1","字符串2")

 表 (table)

除了其他基础数据类型 aardio 中几乎所有复合对象都是表,命名空间也是表。

  1.  表可以包含键值对

    tab = {
        a = 123;
        ["键 名"] = "下标内放表达式表示键";
        键名 = {
            test = "嵌套表";
        }
    }
    
  2.  表可以包含稠密数组

    表中不定个数的成员的“键”是从 1 开始、有序、连续的数值索引则构成有序的稠密数组。

    //构造数组
    var array = {
        [1] = 123;
        [2] = "值可以是任何对象";
        [3] = { "嵌套表"}
    }
    

    稠密数组的键可以省略,例如 { 1,2,3 }

    aardio 数组起始索引为 1 。

  3. 表可以包含稀疏数组

    如果表中包含数值键,但键值不是从 1 开始的连续索引,则构成稀疏数组。例如 var sparseArray = { 1,2,null,null,"其他值" }

  4. 表的类 JavaScript 写法

    表构造器中允许使用类 JavaScript / JSON 语法,可用冒号代替等号分隔键值,用逗号代替分号分隔元素,并允许用引号包含键名(也可以省略引号)。

    示例:

    var tag = {"name1":123,"name2":456,arr=[1,2,e]}
    

    这种写法的表作为函数参数时不可省略 {} ,也不能用于表示结构体

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

    如果函数的调用参数只有一个表构造器( 非结构体 ),并且表的第一个元素是以等号分隔的名值对(键名不在 [] 内),则可省略最外层的 {}

    例如:

 成员操作符

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

//用点号访问表成员
var item = tab.a;

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

tab[null] = "value"; //写入键值时表索引为 null 则报错

读写表成员

使用下标[]检索表内不存在的键返回 null ,不会报错:

var tab = {}

if(tab["key2"]){
    //找到键值
}

任意非字面值使用直接下标操作符(双层中括号 [[]] )检索键值都不会报错(失败返回 null ):

var anyObj = null;

//不报错
if( ! anyObj[["key"]][[1]]  ){
    print("没找到")
}

//报错
if( anyObj["key"] ){

}

表的只读成员

表的成员名称首字符为下划线并且长度大于 1 小于 256 个字节时表示只读字段。

示例:

var tab = {
    _readonlyMember = "赋为非 null 值后不能再修改";
    member = "可以修改值";
}

如下禁用只读保护:

tab@ = { _readonly = false}

使用 tab@ 可读写 tab 的元表(可用于重载操作符)。

表操作

var tab = {}

//按路径读
var v = table.get("key1.key2[1]",tab) 

//按路径写
table.set("key1.key2[]","v",tab)

//混合并返回
table.assign(tab/*null 创建新表*/,{name = "v"}) 

//深层混合
table.assignDeep(tab,{key1={key3=3})

纯数组

使用 [] 构造纯数组( pure-array ),例如:

var array = [123, "数组的值可以是任何其他对象"]

纯数组的类型名也是 "table" ,但主要用于处理稠密数组。仅纯数组传入 table.isArray 函数会返回 true ,纯数组或包含稠密数组成员的表对象传入 table.isArrayLike 函数都会返回 true 。

aardio 中提及数组默认指稠密数组。处理稠密数组一般都会默认使用纯数组,aardio 内置库或标准库返回新数组的函数都会返回纯数组。

纯数组构造器 [] 内嵌套的 [] 也表示纯数组,例如:

var array = [
    ["nested pure-array item","item 2"]  //嵌套纯数组
]

表构造器 {} 内可以用 [] 包围的表达式作为,例如:

var tab = {
    ["table key or index"] = "value";
    array = [1,2,3];
}

 取数组或字符串长度

var array = [123,456];
print("数组长度",#array);

var str  = "abcd";
print("字符串长度",#str);

print( #nullOrStringOrBufferOrArray ) //=> 0

数组操作

//创建长度为 3 的数组(多维数组指定多个长度参数),最后一个参数指定所有元素初始值
var arr = table.array(3,0); //等价于 [0,0,0]

//创建数组,添加从 1 循环到 10 步进为 2 的数值。
var arr = table.range(1,10,2); //等价于 [1,3,5,7,9]

var arr = [1, 2, 3];

//第一个元素
var firstItem = arr[1]

//最后一个元素
var lastItem = arr[ #arr ]

//追加元素
table.push(arr, "world");

//删除末尾元素
var last = table.pop(arr);

// 在开头插入元素
table.unshift(arr, 0);

// 在开头移除元素
var first = table.shift(arr);

// 在索引 2 处插入元素
table.insert(arr,"aardio", 2);

// 移除索引 3 处的元素 
table.remove(arr, 3);  

// 查找元素
var index = table.find(arr, "value"); 

// 遍历数组
for(i=1;#arr;1){
    print( arr[i] );
}

//自参数 2 指定的位置截取到参数 3 指定的位置(负数表示自尾部倒计数),返回新数组
var arr2 = table.slice(arr,2,-1)

//参数(数组,删除位置,删除个数,任意个添加项)
table.splice(arr,1,2,"newItem1","newItem2")

//将一到多个数组参数追加拼接到参数 1 指定的数组
table.append([1,2,3],[4,5,6]) //返回参数 1,返回数组值为: [1,2,3,4,5,6]

数组排序

table.sort 函数用于排序数组,可选用第二个参数自定义排序函数。

var arr = [789,123,456];

//默认排序(较小元素在前)
table.sort(arr)

//自定义排序,隐式传递的 owner 参数表示当前元素,参数 next 表示下一个元素
table.sort(arr,lambda(next) owner > next ); //不要用 <= 或 >= 比较,相等不能返回 true

调用标准库解析 JSON

import JSON;

var jsonString = `{
    a: 123,
    b: 456,
    c: [1,2,3]
}`

// 解码
var tabObject = JSON.parse(jsonString);

tabObject = {
    a: 123,
    b: 456,
    c: [1,2,3]
}

// 编码
jsonString = JSON.stringify(tabObject); 

// 参数 2 转 JSON 并存为文件
JSON.save("/config.json",{ name = "value"},true/*缩进排版*/)

// 解析 JSON 文件
tabObject = JSON.load("/config.json")

 原生类型与结构体

原生类型主要用于 raw 库函数或原生 DLL 提供的 API 函数。

aardio 支持的原生类型:

要点:

在表的字段名前添加原生类型以声明结构体字段,示例:

var struct = {
    int num = 1;
    WORD chars[] = [1,2,3]; //可指定数组值或字符串值
    WORD wstr[] = "字符串"; // UTF-16 <=> UTF-8 双向转换
    BYTE bytes[5] = [1,2,3];//定长数组(超长截断,不足补 0),可指定数组值或字符串值
    double array[/*不能指定变量值*/] = [length:10]; //用 length 属性(或数组的实际长度)确定弹性数组长度
    float f = 1.1;
}

//取结构体大小
print( raw.sizeof(struct) );

//复制结构体到 buffer
var buffer = raw.buffer(struct);

//复制结构体到字符串
var str = raw.tostring(struct);

//复制内存数据到结构体,参数 1 可指定字符串、buffer、指针
var newStruct = raw.convert(buffer,{
    int num;
    WORD chars[] = [length:3];
    WORD wstr[3]
});

//反向复制
raw.convert(newStruct,buffer)

print(newStruct.chars); //输出 [1,2,3]
print(newStruct.wstr); //输出 "字符串",已转为 UTF-8

//将字符串(或 buffer ,结构体)转为参数 2 指定的结构体。
var struct = raw.convert('\x00\x01\x02\x03\x04',{
    INT num; 
},1/*内存偏移自 0 开始,省略默认为 0*/)

//输出数值 0x04030201
print( struct.num  )

//输出反转字节序的数值 0x01020304
print( raw.swap(struct.num,"INT") )

//将 struct 写入 buffer 
raw.convert(struct,buffer/*,offset*/)

自文件读写结构体:


//打开文件
var file = io.file("/example.bin","w+b")

//写结构体到文件
file.write( {
    int x = 12;
    int y = 23;
    float arr = [1.1,2.2,3.3];
    // _struct_aligned = 1; //指定结构体对齐字节( 省略则默认自动对齐,设为 1 禁用自动对齐 )
})

//移到文件头
file.seek("set",0)

//读结构体
var st = file.read({
    int x;
    int y;
})

 调用原生 API

//加载 DLL
::Msvcrt := ..raw.loadDll("Msvcrt.dll",,"cdecl");

//调用 API
var r = ::Msvcrt.memcmp("abc",raw.buffer("abc"),3);

//也可先声明 API
memcmp = ::Msvcrt.api( "memcmp", "int(ptr buffer1,ptr buffer2,INT size)") 

需要全局重用的 DLL 模块通常使用 :: 声明为全局保留常量(不能是小写字母或下划线)

aardio 已经默认加载了 ::User32, ::Kernel32, ::Shell32::Ntdll 等系统 DLL 模块:

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

日期时间

//取系统运行毫秒数
var tk = time.tick();

//取当前时间
var now = time()

/*
创建时间对象
- 参数 @1 可以是数值时间戳、字符串
- 参数 @2 指定时间格式化串,省略则默认为'%Y/%m/%d %H:%M:%S'
*/
var tm = time("2017-05-27T16:56:01Z",'%Y/%m/%d %H:%M:%S') 

//复制
var tmCopy = time(tm)

//时间戳(秒)转时间对象
var tm2 = time(123456)

//转为 UTC 时间
var tm3 = tm.utc(true)

//解析 RFC 1123 ,RFC 850 格式时间
var tm3 = time.gmt("Sun,07Feb2016 081122 +7")

//解析 ISO8601 时间
var tm4 = time.iso8601("20170822 123623 +0700")

//解析 ISO8601 时间(14 或 12 位数字)
var tm5 = time("20170822123623")

//时间格式化为字符串
var str  = tostring( time() )

//使用参数 2 指定的格式化串转换
var str = tostring( time(),"%Y/%m/%d %H:%M:%S")

//时间对象支持连接操作符
var str = "时间:" ++ time()

//时间对象必须先转换为数值才能参与算术运算。
var num = tonumber( time.now() ) % 30

//取 Unix 时间戳,以秒为单位
var unixTimestamp = tonumber( time() )

//返回毫秒时间戳
var unixTimestampMillis = time.stamp()

//毫秒转为秒才能作为 time 构造参数
var now = time( unixTimestampMillis/1000 ) 

 if 语句

if ( _AARDIO_VERSION >= 40 ) {
    print('内核主版本号 >= ' + _AARDIO_VERSION);
}

在条件判断中,非 0、非 null、非 falsetrue,反之为 false。在 aardio 中 ""==true[]==true 的值都为 true

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

var enabled = false;

if (enabled === false ) {
  print('值为 false,不是 0,也不是 null');
}
elseif( enabled ){ 
  print('值为真');
}

for 循环语句( Numeric for )

基于数值范围的 for 循环:

for(i = initialValue;finalValue;incrementValue){
    // 循环执行的语句
}

示例:

for(i=1;10;2){
    print(i); // 输出 1,3,5,7,9。 循环变量 i 的作用域为当前循环体
}

var v = i + 1; //报错 , i 为 null 值

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

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

//遍历表对象的所有键值
for(k,v in tab){
    print(k,v)
}

in 后可以指定:

aardio 中所有创建迭代器的工厂函数名字都以 each 为前缀,示例:

import process

//遍历已运行进程,参数支持模式匹配
for processEntry in process.each( ".*.exe" ) {
    print( processEntry.szExeFile,processEntry.th32ProcessID );
}

import winex;

//遍历顶层窗口,参数可选指定搜索类名与标题,支持模式匹配。
for hwnd,title,threadId,processId in winex.each("","") {    
    print(hwnd,title)
}

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

//按字典序输出表中的名值对
for k,v in table.eachName(tab){
    print(k,v)
}

 while 循环语句

var i = 0;

//i 的值小于 100 时循环执行
while (i < 100) {  
    i++;
    print(i);

    if(i==10){
        break; //中断
    }
}

select case 语句

select 语句选定一个值,并执行首个匹配选定值的 case 语句。 case 语句不会向下穿透( fall-through ), 不能用 break 退出 。

var selectValue = 0;

select( selectValue ) {

    case 1 { 
        print("1")
    }
    case 2,3,4{
        print("2,3,4 之一")
    }
    case 5;10 {  
        print("5 到 10 范围的值")
    }
    case !== 0 { 
        print("不等于 0")
    }
    else{ 
        print("以上都不符合")
    }
}

 类

import console;

//定义类
class className { 

    // ctor 之前不能出现属性、方法、var 语句!!!

    //可选的构造函数 `ctor`,必须写在类体最前面; 不能定义多个 ctor 函数
    ctor(name, ...){ 
        /*
        class 语句定义了一个新的“类命名空间”。
        类的所有实例共享同一“类命名空间”,“类命名空间”的成员就是类的静态成员。
        */
        staticMethod(); //正确: 静态成员名称 == 当前类命名空间直接使用的名称
        self.staticMethod(); //正确: self == 当前命名空间
        //className.staticMethod(); //错误: 类内部并未定义 className

        //通过 `..` 前缀访问外部命名空间
        ..console.log( ..className.staticProperty ); //正确: global.className 已定义 

        /*
        ctor 函数的作用域也是“类作用域”,
        类作用域范围是整个 `class` 语句块。

        局部变量 privateVar 也是整个类作用域的私有变量,
        privateVar 的生命周期与当前实例绑定(闭包机制)。
        */
        var privateVar = staticProperty; 

        //通过 this 访问当前对象实例
        this.property1 = "当前对象的 property1 属性值"; 

        //在构造函数内可提前定义对象实例的方法(成员函数)
        this.method1 = function(){}; 

        this.method1();//可以调用已定义的方法

        // this.method2(); //不能调用在构造函数之后定义的方法

        this.args = [...];
    };

    /*
    ctor 语句之后的部分是一个表构造器。
    每次执行构造函数以后都会按顺序执行以下代码以完全初始化 this 实例。
    不能将下面的属性或方法移到构造函数之前。
    */

    property2 = "this.property2 的值"; //遵守表构造器规则,这里用分号隔开
    property4 = this.property1; //引用前面已定义的属性
    property5 = staticProperty; //静态成员名称属于当前命名空间,可以直接使用
    property3 = privateVar; //私有变量 privateVar 在类作用域范围有效

    //实例方法(成员函数)必须写为名值对格式,不能省略等号或 function 关键字。
    method2 = function(v){

        if(v === null) v = staticProperty; //类命名空间内可直接访问静态成员

        this.property1 = v; //通过 this 访问当前实例

        ..console.log( //通过 .. 访问全局命名空间
            privateVar //ctor 函数内定义的私有变量 privateVar 在类作用域范围有效
        ); 
    };
}

//打开类命名空间
namespace className {

    //添加静态成员
    staticProperty = "类的静态属性值"; 

    //添加静态方法
    staticMethod = function(){
        ..console.log(staticProperty);
    } 
}

//调用类创建对象
var object = className("构造参数值");

//调用对象的成员函数
var v = object.method2("参数值");

//在类定义外添加实例方法。
object.onSomeEvent  = function(){
    //这里 this 为 null ( this 是类内私有变量 )
    ..console.log( owner.property1 ) // owner 指向 object
}

object.onSomeEvent();

//在类作用域之外通过 className 访问类的静态成员
className.staticMethod();

console.pause();

使用类名作为命名空间时一定要先定义类,namespace 语句可以打开已有的命名空间或者类命名空间,而 class 语句则是直接赋值

self,this,owner 对象

self 表示当前命名空间。

this 仅在类定义内部表示当前构造的实例对象。

每个通过 ownerObject.method() 形式调用的语句( owner call )都会隐式传递 owner 参数,点号 .前面的 ownerObject 对象就是被调用的成员函数的 owner 参数,以其他方式调用( 例如 ownerObject["method"]() )时 owner 参数为 null

示例:

var ownerObject = {
    property = "value";

    method = function(a,b){
        print("我的 property:",owner.property);
        return a+b;
    }
}

//owner call 方式: owner 参数指向 ownerObject
ownerObject.method(123,456);

//通过  call, callex, invoke 调用函数,可用参数 2 自定义 owner 参数
invoke(ownerObject["method"],ownerObject,123,456);

//非 owner call 方式: owner 参数为 null 
ownerObject["method"](123,456); 

独立加载的 aardio 代码也相当于一个匿名的函数,这个匿名函数的 owner 参数默认为 null 。使用 import 语句加载库文件时, 文件层级的 owner 参数为库路径或资源文件数据( 编译后的内嵌库 )。

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

在迭代器函数中, owner 表示迭代的集合对象。

使用 aardio 模式匹配

请参考:模式匹配快速入门

aardio 中所有查找替换字符串有关的函数基本都支持模式匹配。

要点:

模式转义符:

模式匹配使用 \ 作为转义符(运行时处理转义),例如 \<\> 表示普通的尖括号而不是表示非捕获组,\n 则表示换行。

查找字符串:

var str = "abc ||| 123";

/*
`(\a+)` 匹配连续的字母,此捕获组增加一个返回值
`<\s+>|<\s*\|+\s*>` 匹配连续的 "|||" 或者空白分隔符
`(\d+)` 匹配连续的数字,此捕获组增加一个返回值
*/
var letters, numbers = string.match(str, `^(\a+)<\s+>|<\s*\|+\s*>(\d+)$`);
print(letters, numbers)

查找 HTML 标签:

var str = "<title>标题</title>"

//使用 `\<` 转义  `<` 以表示字面意义,避免被识别为非捕获组
var title = string.match(str,"\<title\>(.+)\</title\>")

替换字符串:

var str = "1hello 2world";

//将捕获组 2 移到 捕获组 1 前面
var str2 = string.replace(str, "(\d)(\a+)"
    ,"\2\1" //`\1` 到 `\9` 前引用捕获组(aardio 模式匹配使用反斜杠,正则使用 $ 符号), \0 表示整个匹配结果
); 

//等价写法
var str2 = string.replace(str, "(\d)(\a+)"
    ,lambda (c1,c2) c2++c1 //第一个参数 c1 对应第一个捕获组
); 

在替换串中用 \1\9 表示向前引用捕获组。

非捕获组:

/*
用尖括号创建非捕获组(也是原子分组),
将连续的字符或多个子模式合变为单个子模式,只能对单个子模式使用量词等运算符
*/
var s = string.match(`中文 abc`, `<中文\s+[a-z]+>+`)  

对称匹配:匹配成对的括号:

//% 后面的两个子模式必须首尾成对匹配
var s = string.match(`func(a(b)cd)`, `%()`) //返回 (a(b)cd)

//最近对称匹配
var s = string.match(`(a(b)cd)`, `%()?`) //返回最里层的 (b)

先排除其再替换:

var str = "123(456)789"

//参数 @2 匹配的部分替换为参数 @3,参数 @4 以及之后的参数匹配的部分被事先排除
str = string.replaceUnmatched(str,"\d"/*替换模式串*/,"我不会出现在括号内","%()"/*排除模式串*/)
print(str);

用表对象指定多个替换对:

//将 a1 替换为 b2,将 b2 替换为 a1
str = string.replace("a1b2","<a1>|<b2>",{
    "a1":"b2",
    "b2":"a1"
})

连续匹配:

var str = string.reduce("print(a,(1+2))",
    "\w+(%())",//先匹配对称括号
    "^\((.+)\)$",//进一步细分匹配
    "(\d)\)" //更多模式参数
);

连续匹配并替换:

//math.format.thousands 函数关键代码
var numberWithCommas = string.reduceReplace(
        "-123456.12345",
        "^-?\d+",  //查找模式
        "(\d)<<\d\d\d>+\d?!>?=", //在上一步查找的结果中继续查找
        "\1," //替换串,\1 表示捕获组 1
    );

 文件路径


//路径开始于单个斜杠(或反斜杠)表示应用程序根目录
var str = string.load("\res\test.txt");

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

//首字符为波浪线表示启动 exe 所在目录
var str = string.load("~/res/test.txt");

//"\res\test.txt" 可以是资源文件路径,也可以是硬盘文件
var str = string.load("\res\test.txt");

//将文件转换为完整路径。
var path = io.fullpath("\res\test.txt");

//文件是否存在
var path = io.exist(path)

import fsys;
var isDir = fsys.isDir(path); //是否已存在的目录
var isFile = fsys.isFile(path); //是否已存在的文件

拆分文件路径:

/*
pathInfo 包含以下字段:
- file: 文件名(有后缀)
- name: 文件名(无后缀)
- ext: 后缀名,含 . 号
- dir: 目录路径
*/
var pathInfo = io.splitpath(path)

获取常用路径:

var desktopDir = io.getSpecial(0/*_CSIDL_DESKTOP*/);
print( desktopDir );

//获取 %localappdata%\aardio\std 目录,可增加任意个子目录参数,返回正确拼接后的路径
var dataDir = io.getSpecial(0x1c/*_CSIDL_LOCAL_APPDATA*/,"aardio\std");
print( dataDir );

//展开环境变量
var path = string.expand("%localappdata%\aardio\std") 

//获取 %LOCALAPPDATA% 目录下的路径,可选用参数 2 指定文件的更新数据(未更新不写文件)
var path = io.appData("aardio\std\test.txt")

//获取应用程序根目录完整路径
var appDir  = io.fullpath("/")

//获取当前 EXE 目录,aardio 在开发调试时不需要先生成 EXE 文件而是以 aardio.exe 创建进程运行代码
var exeDir = io.fullpath("~/")

//仅在 path 参数是以单个 `\` 、 `/` 或  `~` 字符开始的 aardio 格式路径才转换为绝对路径,否则返回 null
var path = io.localpath(path) 

//生成临时文件路径,可选指定前缀与后缀
var tempPath = io.tmpname("prefix",".txt")

//取当前目录,等价于 io.fullpath("./")
var cd = io.curDir()

//修改当前目录
io.curDir("/")

文件对话框

import fsys.dlg;

//打开
var path = fsys.dlg.open("图像|*.jpg;*.png|","标题",/*"/默认目录"*/,winform/*父窗口*/,/*flags*/,"默认文件名.png");

//保存
var path2 = fsys.dlg.save("图像|*.jpg;*.png|","标题",/*"/默认目录"*/,winform/*父窗口*/,/*flags*/,"默认文件名.png");

//所有参数可省略
path2 = fsys.dlg.save(,,,winform,,"默认文件名.png");

遍历文件

fsys.enum(path,fileMask,callback,subDirectory) 用于遍历目录下的文件。 path 指定目录,fileMask 指定通配符或通配符数组,callback 指定回调函数,subDirectory 指定是否递归搜索子目录(默认为 true )。

import fsys.dlg.dir;

var dir = fsys.dlg.dir("/可选指定当前目录",winform,"对话框标题");
if(!dir) return;

import fsys;
fsys.enum( dir, "*.*",
    function(dirname,filename,fullpath,findData){ 
        if(filename) print("文件名:" + filename); 
        else print( "目录名:" + dirname);
    } 
);

fsys.each(path,pattern,fileMask,findOption) 用于创建遍历文件的迭代器(不会递归搜索子目录)。 path 指定目录。可选用 pattern 指定匹配模式串。可选用 fileMask 指定通配符或通配符数组。findOption 为 "file" 则仅限查找匹配的文件,为 "dir" 则仅限查找匹配的目录,不指定则无限制。

import fsys;

for i,filename in fsys.each("/"){
    print(filename);
}

aardio 中名字以 enum 为前缀的通常是深度遍历集合的枚举函数,而名字以 each 为前缀的通常是创建迭代器的工厂函数。

 读写文件

直接读写文件全部数据:

//写文件,二进制模式,参数可以是字符串或 buffer 
string.save("/test.txt","要保存在文件的字符串");


//读文件,二进制模式,返回字符串
var str = string.load("/test.txt");

//读文件,返回 buffer
var bytes = string.loadBuffer("/test.txt");

使用 io.file 创建文件对象,然后读写:

//创建文件对象
var file = io.file("/example.txt","w+b");

//写结构体,成功返回 true,失败返回 null,错误信息
file.write({int x=1,int y=2});

//写文本
file.write("写入内容",'\r\n');

//移动文件指针
file.seek("set",0);

//读结构体
var struct = file.read({int x=1,int y=2});
print(struct.x,struct.y);

//读文本行
var line = file.read("%s");
print(line);

//关闭文件
file.close();

注意 io.file("/example.txt","w+b").write("写入内容").close() 这样写是错的, write 函数返回的是布尔值,无法进行链式调用。一次性写入文件可以使用 io.file.write("/example.txt","写入内容") 函数,可指定 1 到 N 个要写入的字符串、buffer、数值、结构体类型参数。

io.file.write("/test.zip",{
    INT sign = 0x06054B50;
    BYTE bytes[22]
}) 

io.file,fsys.file,fsys.stream 主要接口相同,传入 type.isFile( file ) 的参数都会返回 true 。

区别是 io.file 更轻快,fsys.file 支持系统句柄参数,fsys.stream 实现了 IStream 接口(支持内存文件)。

操作剪贴板

import win.clip;

//复制文本
win.clip.write("文本");

//写入表(对象、数组)到剪贴板
win.clip.write({ arr =[1,2,3]})

WMI 查询

import com.wmi;

//WQL 参数化查询,返回首个查询结果(COM 对象)
var user = com.wmi.get("SELECT * FROM Win32_UserAccount WHERE Status=@status",{ 
    status = "OK"
})

//user.PasswordExpires = false; //修改 COM 对象属性
//user.Put_(); //调用 WMI 象方法

//WQL 参数化查询
var dataTable = com.wmi.getTable("SELECT * FROM Win32_Process WHERE CreationDate >= @creationDate",{ 
    creationDate = time().addhour(-1)
})

//数组元素包含属性名值对的表
print(dataTable[1].ExecutablePath)

//参数 1 为单个类名时可使用 wmic 别名,参数 2 指定属性名则返回单个属性值(自动解析 CIM 时间)。
var date = com.wmi.get("os","installDate");
print( date.format("%Y-%m-%d") );

调用 .NET(C#) 程序集

import dotNet;
dotNet.import("System.Xml");

var xmlDoc = System.Xml.XmlDocument();
xmlDoc.LoadXml(`<?xml version="1.0" ?>
<Country id="china">
    <Province><Details id="shanxi">Shaanxi</Details></Province>
</Country>`)

// XPath 查询
var ele = xmlDoc.SelectSingleNode(`//Province/Details[@id="shanxi"]`);
print("",ele.OuterXml);

在 aardio 窗口中嵌入 .NET 的 WinForm 界面控件:

import System.Windows.Forms; 

//将 aardio 窗口的 custom 控件指定为第 1 个构造参数
var dataGridView = System.Windows.Forms.DataGridView(winform.custom); 

//调用 .NET 属性或方法
dataGridView.EditMode = 2; 

调用 Python 函数

import py3; 

var pyCode = /** 
import struct
def getPack():  
    return struct.pack("@b7s",9,b"hello")
**/

//执行 Python 代码或文件
py3.exec( pyCode ) 

//调用 main 模块函数
var buffer = py3.main.getPack();

py3 是调用扩展库自带 Python.dll 。

process.python 则可以调用系统或自带的 Python.exe :

import process.python; 
var python = process.python.exec(`
import json;print( json.dumps([1,2]) )`);
var result,err = python.json();
print(result);

二进制打包解包

import raw.pack;

//打包,参数 1 指定格式化字符串(格式化语法与 Python 的 struct.pack 相同,不需要转换)
var buffer = raw.pack("@b7s",9
    ,"超长截断,不足补零"
)

//上面的代码等价于:
var buffer = raw.buffer( {
    byte field1=9;
    byte field2[7]="超长截断,不足补零";
}); 

//解包
var byte,str = raw.pack.unpack("@b7s",buffer)

import string.builder;
var stringBuilder = string.builder();

//等价于 stringBuilder.write( raw.pack(...) ); 必须事先导入 raw.pack 库
stringBuilder.pack("<B3s",1,"s1"/*超长截断,不足补零*/).pack("<B3s",2,"s2"); //支持链式调用

//参数 2 指定二维数组时批量打包
stringBuilder.pack("<B3s",[
    [1,"s1"],
    [2,"s2"],
])

//在尾部写入一个或多个参数( 支持结构体、buffer、字符串等非 null 类型参数 )
stringBuilder.write({
    uint32 b = 1;
    BYTE bytes[] = "ab"
})

print( stringBuilder.hex() )

//通过下标访问字节码,仅 string.builder 可用负索引表示尾部倒计数(字符串或 buffer 指定负索引返回 null )
var lastByte = stringBuilder[-1]

//转为字符串
var str = tostring(stringBuilder);

网站应用

aardio 有 3 种方式创建 HTTP 服务端:

这些服务端都兼容相同的接口,提供兼容的 request, response,seesion 对象。

创建 HTTP 服务端:

import console;
import wsock.tcp.simpleHttpServer; 
var app = wsock.tcp.simpleHttpServer("127.0.0.1",8081);

console.open();

app.run(

    function(response,request){
        response.write("hello!")
    }
);

web.view 可识别已导入的 HTTP 服务器模块,自动将单个斜杆或反斜杆开始的路径转为 HTTP 服务端路径,例如:

import wsock.tcp.simpleHttpServer; 
wb.go("/res/index.html") //自动调用 wsock.tcp.simpleHttpServer.startUrl("/res/index.html") 

输出网页:

<!doctype html>
<html>
<body>
<?
response.write("你好!")
?>

如果 aardio 代码文件以 HTML 开始则代码必须写在 <? ..... ?> 内。

request,response 对象的常见用法:


//取请求头,小写键名
var h = request.headers["name"]

//取 URL 参数,小写键名
var p = request.get["name"] 

//取表单参数,小写键名
p = request.post["name"] 

//取 URL 或表单参数
p = request.query("小写名称") 

//获取并解析请求 JSON
var data = request.postJson()

//获取原始上传数据
data = request.postData()

var ip = request.remoteAddr

//取请求 URL,不带参数
var url = request.url

//请求路径
var path = request.path

//自定义 MIME
response.contentType = "application/json"

//执行 aardio 文件或下载普通文件
response.loadcode("路径") 

//重定向
response.redirect("网址")

//发送响应数据,可输出一或多个参数,表参数自动转 JSON
response.write(arg1,arg2,...)

//添加响应头,键名首字母必须大写。
response.headers["Header-Name"] = stringOrStringArray 

//发送错误代码,可选指定错误信息,终止后续代码
response.errorStatu(code,message)

//关闭输出终止后续代码
response.close() 

Markdown 格式