aardio专注于Windows桌面软件快速开发的动态编程语言
用aardio,让桌面软件开发快如闪电:一门被低估的“超级胶水”语言

在追求“大而全”的编程世界,有一门语言反其道而行之,凭借6.5MB的极致体积和“超级胶水”般的混编能力,在Windows桌面开发领域默默耕耘了22年(2004起),它就是aardio(阿帝鸥)。对于希望快速构建轻量级、独立EXE应用的开发者、教育者或技术爱好者而言,aardio提供了一个令人惊喜的高效选择。
一、项目简介
aardio是一门专注于Windows桌面软件快速开发的动态编程语言,以其极小的体积、极低的学习成本和强大的“胶水”能力著称,能帮助开发者在几分钟内拼凑出功能完备的应用程序。
二、语言诞生背景
aardio的故事始于2004年,作者Jacen He基于Lua开发了LAScript项目。2005年,他启动重构,项目更名为AAuto,并于2011年发布1.0正式版。2016年,正式定名为aardio,并推出了支持Unicode内核的10.0版本,开始支持网站服务端开发。
这门语言由作者一人独立开发维护,积累了海量的开源库和详尽文档。遗憾的是,2023年因作者妻子患癌,项目宣布暂停维护,但其成熟的生态和稳定的特性,使其至今仍是许多开发者的得力工具。作者至今仍会在公众号发布技术文章,也拥抱AI编程。
三、技术基础
- 是否基于现有虚拟机?
aardio并非基于现有虚拟机,它拥有独特的语言内核和运行机制。 - 是否自研中间码或编译器?
aardio拥有自研的编译系统,能够将源代码和资源文件直接编译生成独立的EXE文件,无需外部依赖。 - 是否支持跨平台?
aardio主要专注于Windows平台的桌面应用开发。其生成的EXE文件兼容从Windows XP到Windows 11的所有流行版本。虽然它可以通过嵌入浏览器控件等方式实现界面上的跨平台体验,但其运行时本身并非原生跨平台语言。
四、应用场景
aardio非常适合以下场景:
- 开发轻量级桌面工具
如小工具、辅助软件、管理程序等,追求单个独立EXE、体积小巧(通常几百KB到几MB) 的应用。 - 快速原型开发
需要快速验证想法或为现有脚本(如Python数据处理脚本)制作一个图形用户界面(GUI)。 - “胶水”式混合编程
作为主体,方便地调用C/C++、Python、Java、JavaScript、甚至批处理脚本等其他语言的功能,整合各方优势。 - 网站式桌面客户端
利用其强大的Web界面支持(如HTMLayout、CEF、WebView2等),像开发网站一样开发桌面软件,并内嵌HTTP服务器。


五、开发工具与环境
aardio本身即是一个绿色、免安装的集成开发环境(IDE),解压即可使用。该IDE内置了可视化窗体设计器、代码编辑器、调试器和发布工具,提供了从设计、编码到打包的一站式体验。

六、IDE / 编辑器插件
aardio的主要开发工作在其原生IDE中完成。该IDE功能全面,且针对AI辅助编程进行了深度优化。
|
|
|
|
|
|---|---|---|---|
| 官方IDE |
|
https://www.aardio.com/ |
|
| AI助手 |
|
|
|

七、关键字与语法概览
aardio采用类C语法,接近Java,易于上手。
- 基本数据类型
动态类型,变量无需显式声明类型。支持数字、字符串、表(数组/字典)、函数、类对象等。 - 变量声明
使用 var关键字声明局部变量(如var name = “aardio”;)。 - 运算符
包含算术( +,-,*,/)、关系运算符/比较(>=,<=,>,<)、逻辑(and &&,or ||,not !)等标准运算符。
- 条件语句
if 语句
if 语句包含条件判断部分、执行代码部分。
执行代码部分可以是一句代码(不能是单个分号表示的空语句),或者一个语句块。
而 if 语句将条件表达式返回的结果与布尔值 true 进行比较,比较使用等式运算符( 支持自动类型转换、_eq 元方法 )。
if 语句可选包含任意多个 elseif 分支判断语句,可选在最后包含一个 else 语句。
if 语句可以嵌套使用。
一个标准的 if 语句示例:
import console;var a=1;if(b==1){if(a==1) {console.log("if");}}elseif(a==11){console.log("elseif");}else{console.log("else");}console.pause();注意:上面的 elseif 如果改为 else if 相当于在 else 语句嵌套了一个新的 if 语句,这通常是不必要的。
select case 语句
select case 语句是一种条件分支结构,根据表达式的值执行不同的语句块。
基本结构如下:
// test-expression 指定要匹配的条件表达式select( select-expression ) {// 一个或多个 case 语句,用于列举匹配条件表达式的值case case-expression {// 符合条件则执行这对应的语句或语句块}// 可选的 else 语句块else {// 所有条件都不符合时执行这里的默认语句或语句块}}case-expression 支持以下 3 种格式:
1.指定单个条件值,例如 case 123。默认使用恒等运算符比较,可在开始指定比较使用的运算符,例如 case != 10 。
2.指定逗号分隔的多个条件值, 例如 case 1,2,3,任意一个值匹配则满足条件。默认使用恒等运算符比较,可在开始指定比较使用的运算符,例如 case == 1,2,3 。
3.使用分号或 to 分隔的范围值。例如 case 1;10 表示使用恒等运算符匹配 1 到 10 范围(包含首尾数值 1,10)的任意数值是否相等。使用 >= 与 <= 运算符比较,不能自定义运算符。
恒等运算符要求要求数据类型绝对相等,不会自动转换类型进行比较。参考链接:恒等运算符
select case 语句的执行流程如下:
•第一个符合条件的 case 语句将会执行,然后退出 select case 语句。不会连续执行后面的 case 语句,没有穿透( fall-through )特性,break 没有中断 select 语句的作用。
•如果所有条件都不符合,则执行可选的 else 语句。else 也可以简写为 case else。
示例:
var selectValue = 0;select( selectValue ) {case 1 {print("selectValue 为 1")}case 2,3,4{print("selectValue 为 2,3,4 之一")}case 5;10 {print("selectValue 为 5 到 10 范围的值")}case !== 0 {print("selectValue 不等于 0")}else{print("以上 case 都不符合条件")}}select case 语句允许嵌套使用,但 select case 语句嵌套写可能影响可读性,应当避免这样做。
case 或 else 也可以执行单个语句,示例:
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("没有 case 语句符合条件");}aardio 提供的内置函数 switch 则可以自一个表对象快速检索指定值对象的函数并执行该函数,虽然 switch 函数仅支持表检索方式,但如果有大量需要比对的值, switch 函数检索的速度会快很多。
- 循环语句
一. for 计数循环语句( Numeric for )
计数 for 循环是基于数值范围的循环(Range-based for)。
基本结构:
for(i = initialValue;finalValue;incrementValue){//循环体}
示例:
import console;// 循环变量 i 从起始值 1 循环到终止值 10 ,每次递增步长为 2。for(i=1;10;2){// 在控制台输出 1,3,5,7,9console.log(i);}// 循环变量 i 从 1 循环到 10 ,省略递增步长则默认为 1 。for(i=1;10){i++; // 修改循环变量的值,使步长变为 2// 循环变量设为无效数值,下次循环以前自动修复为计数器值i = "无效数值"}// 循环变量 i 从起始值 10 循环到终止值 1 ,每次递减步长为 1。for(i=10;1;-1){// 在控制台输出 10,9,8,7,6,5,4,3,2,1console.log(i);}console.pause()
用 for 循环计算阶乘的示例:
//计算阶乘(指从 1 乘以 2 乘以 3 一直乘到所要求的数)math.factorial = function(n){var result = 1;//for 计数循环,递增步长为 1 时可省略,循环体可以为单个语句。for(i=2;n) result *= i;return result;}print( math.factorial(15) );
aardio 允许以多种不同的方式分隔循环参数,示例:
//使用逗号分隔循环参数,前后两个分隔符必须一致for i=1,10,1 {}//使用普通标识符分隔循环参数。for i=1 to 10 step 1 {}for 循环语句支持简化的纯 C 风格写法 #,基本结构如下:for (i = initialValue; condition; step){}
一般没必要使用这种纯 C 风格写法,aardio 的标准 for 循环语法更简洁一些。
二 泛型 for in 循环语句( Generic for )#
for in 语句有多种不同的用法
在 for in 语句里,in 后面可以指定任意表达式,简单来说:
-
in 后面如果是表对象(或数组),则循环遍历该表。
-
in 后面如果是函数对象(迭代器),则循环调用该函数直到函数返回 null 值。
-
in 后面如果是函数调用语句(迭代器工厂),则必须返回一个函数对象(迭代器),循环调用返回的迭代器函数直到迭代器返回 null 值。
-
in 后面如果是其他任何值,则忽略不报错。
下面是具体用法:
1.in 后面写单个表达式
格式:
for k,v in anyExpression {}
anyExpression 的类型:
anyExpression 必须是单个表达式,不能是用逗号分隔的多个表达式。不能是函数调用语句。可以是其他任意表达式,任意值。
对应执行的操作:
-
anyExpression 的值如果是表,则遍历该表的所有成员。
-
anyExpression 的值如果是函数,则将该函数作为迭代器函数,循环调用直到函数返回 null 值。
-
anyExpression 的值如果不是表也不是函数,则 fon in 语句不执行任何操作(不会报错)。
也就是说,即使下面这样写也符合语法规则:
2.in 后面写单个函数,或多个表达式
格式:
for k,v in iterator,ownerObject,initialValue {}
-
iterator 必须是迭代器函数。
-
ownerObject 是迭代器的 owner 参数,可选值。
-
initialValue 是首次调用 iterator 初始值,可选值。
3.in 后面是函数调用语句
for k,v in iteratorFactory() {}
iteratorFactory() 语句的返回值依次为 iterator,ownerObject,initialValue , 返回的 iterator 必须是函数对象,而 ownerObject , initialValue 则可以省略。
aardio 中的所有迭代器工厂函数(Iterator Factory)的名字都以 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)}
泛型 for 与迭代器
泛型 for 循环是基于迭代器的循环( Iterator-based for )。只不过在 in 后面指定表对象时 aardio 使用了默认的迭代器,而在 in 后面指定非函数、非表类型的对象时,for in 只是简单地跳过了没有执行任何操作。
泛型 for 直接用于迭代器的结构如下:
for controlVariable in iterator,ownerObject,initialValue {}
“迭代”是指循环取值( 指循环调用迭代器 iterator )并不断逼近最终目标的过程,每次取值的结果( 指控制变量 controlVariable )总是作为下一次迭代调用的参数。
一般我们不会在 in 后面直接写迭代器,而是先写一个创建并初始化迭代器的迭代器工厂函数,这是因为很多时候我们都需要利用迭代器工厂做一些循环前的准备工作并设定要迭代的数据。
使用迭代器工厂的基本结构如下。
//迭代器工厂,可选指定初始化参数var iteratorFactory = function(args){//返回迭代器,接受上次返回的循环控制变量作为参数return function(controlVariable){//迭代器实现代码//返回新的值return controlVariable;}}//循环调用迭代器工厂创建的迭代器for controlVariable in iteratorFactory(expList) {}
更多关于 for in 语句的细节与示例请查看:泛型 for 与迭代器
三. while 循环语句
while 语句包含条件判断部分、执行代码的循环体部分。
while( condition ) {//循环体(Code block)}
要点:
•condition 是一个返回循环条件的表达式。
•condition 为 true 则继续循环,为 false 则停止循环。
•循环体可以是一个单独的语句,可以是用 {} 包含的语句块 。
示例代码:
import console;// 定义循环变量var countLoop = 0;// 循环条件判断while( countLoop<10 ){// 递增循环变量countLoop++;// 在控制台显示循环变量console.log("循环次数", countLoop);};console.pause();
二. do while 循环语句
do while 循环语句的基本结构如下:
do{//循环体(Code block)} while( condition )
要点:
•condition 是一个返回循环条件的表达式。
•condition 为 true 则继续循环,为 false 则停止循环。
•循环体可以是一个单独的语句,可以是用 {} 包含的语句块 。
•do … while 语句首先执行循环体,然后再判断循环条件。 循环体至少会执行一次。
下面是 do..while 语句示例:
import console;//循环do{//显示循环变量console.log(countLoop)//递增循环变量countLoop++}while( countLoop<123 ); //判断循环条件console.pause();
循环中断语句
1.break 语句
break语句中断并退出循环并跳转到指定循环的结束点以后开始执行。
2.continue 语句
continue 语句跳过循环体剩下的部分,跳转到循环体的开始处并继续执行下一次循环。类似一种不执行循环体剩余部分代码的条件语句。 可以在循环体的开始处使用 continue 语句是一种好的习惯,可以避免将循环体代码包含在一个大的if语句中。使程序拥有清晰的结构。
3.带标号的 break、continue 语句(labeled break、labeled continue)
aardio 支持带标号的 break、continue 语句。
示例代码:
import console;while( true ){循环体2: //可以在循环体的开始指定一个标号console.log("循环体2开始" );while( true ){console.log("循环体1开始" );//中断上层循环break 2;//这句的作用与上面的 break 2 作用是一样的break 循环体2;console.log("循环体1结束" );};console.log("循环体2结束" );}console.pause();
- 函数定义
定义函数的基本语法:
function name(parameter1, parameter2, parameter3,...) {//函数内部代码//返回一个或多个值return result1,result2;}函数也可以定义为局部变量。局部函数像局部变量一样作用域限于当前语句块。
用具名函数定义局部函数的基本语法:
var function func() {}用匿名函数赋值到局部变量以定义局部函数的语法:
var func = function() {}在 aardio 中第二种写法更为常见,第一种写法较少使用。
调用函数
result1,result2 = func(arg1,arg2,arg3)•在函数对象后添加调用操作符 () 写为 func() 格式表示调用函数。
说明:
◦调用类构造函数、调用 lambda 函数 的语法与调用普通函数完全一样。
◦调用操作符 () 不允许直接写在函数定义或字面量后面,也不能直接写在类或 lambda 定义后面。如果要立即调用匿名函数或 lambda 表达式则必须将其置于一对括号内,例如 ( function(){} )()。
•在调用函数时可以指定零个或多个调用实参,例如上面的 arg1 到 argN。调用实参的数目如果多于形参的数目,多余部分被丢弃。 调用实参的数目如果少于形参的个数,不足的部分添加 null 值。
•函数可以返回零个或多个值,例如上面的,例如上面的 result1 到 resultN。
•调用函数的代码是一个语句,但函数的返回值是右值表达式。
函数参数
形参 ( parameter ) 指函数定义参数列表中预定义的形式参数,形参可以在函数体内部作为局部变量使用。
示例:
//这里的a,b,c称为形参,可以将形参看成函数内部的局部变量名字function test(a,b,c){return a+b+c; //形参可以在函数体内部作为局部变量使用}实参( argument)指调用时括号中指定的实际调用参数。
示例:
print(1,2,3); //这里的 1,2,3,4 称为调用实参
- 类
class 关键字定义。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 语句则是直接赋值
- 注释
单行注释用 //,多行注释用 /*块注释(可包含换行),首尾标记的星号数目必须相同。
*/。
八、学习资源与社区
- 官方文档与范例
aardio IDE内置了极其详细的标准库文档和大量可直接运行的范例,这是最核心的学习资源。当然,也有官网在线文档: https://www.aardio.com/zh-cn/doc/?q=library-guide%2Fstd%2Fweb%2Frest%2FaiChat.html

- 官方公众号
提供教程链接和更新信息。 
- 第三方博客与笔记
如“popdes的aardio学习笔记”、“苏扬博客”等,有开发者分享的经验。 - 开源项目参考
在GitHub上可以找到用aardio开发的开源项目,如 ImTip(输入法提示)、Gif123(录屏)、WubiLex(五笔工具)等,这些都是极佳的学习范本。 
九、示例代码运行流程
让我们体验一个经典的“Hello World”流程:
- 编写文件
import console;// 导入控制台库console.log("Hello, aardio World!");// 输出到控制台console.pause();// 暂停,等待按键
控制台程序
使用 console 库:import console;console.showLoading("请稍候");thread.delay(1000); //暂停 1 秒console.log('你好');//支持多参数,可输出纯数组内容//输出表的数据console.dump({a=123;b=456});//输出为 JSONconsole.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 窗口类。
网页应用:
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");
实现抖音上比较火的罗盘时钟做桌面背景 aardio封装的web程序

结语
aardio或许不是最炫酷的语言,但它“快速实现Windows桌面小工具” 这个细分领域做到了极致。它的“小、轻、快”和“超级胶水”特性,让编程回归到解决问题的本质——高效、直接。
夜雨聆风
