Lua

Lua發音: /ˈlə/,葡萄牙语含义是月亮)是一个简洁、轻量、可扩展的脚本语言。Lua有着相对简单的C API而很容易嵌入应用中[2]。很多应用程序使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性[3]

Lua
多范型脚本指令式过程式基于原型面向对象),函数式
設計者Roberto Ierusalimschy
Waldemar Celes
Luiz Henrique de Figueiredo
1993年
穩定版本
5.4.2
( 2020年12月3日2020-12-03
型態系統动态, 强类型, 鸭子
實作語言ANSI C
作業系統跨平台
許可證MIT許可證
文件扩展名.lua
網站www.lua.org
主要實作產品
Lua, LuaJIT, LuaVela
衍生副語言
Metalua, Idle, GSL Shell, Luau
啟發語言
SchemeSNOBOLModula-2CLUC++
影響語言
GameMonkey, Io, JavaScript, Julia, MiniD, Red, Ring[1], Ruby, Squirrel, MoonScript, C--

历史

Lua是在1993年由Roberto Ierusalimschy、Luiz Henrique de Figueiredo和Waldemar Celes创建的,他们当时是巴西里约热内卢天主教大学的计算机图形技术组(Tecgraf)成员。Lua的先驱是数据描述/配置语言“SOL”(简单对象语言)和“DEL”(数据录入语言)[4]。他们于1992年–1993年在Tecgraf独立开发了需要增加灵活性的两个不同项目(都是用于工程应用的交互式图形程序)。在SOL和DEL中缺乏很多控制流结构,需要向它们增加完全的编程能力。

在《The Evolution of Lua》中,这门语言的作者写道[5]

在1993年,唯一真正的竞争者是Tcl,它已经明确的设计用于嵌入到应用之中。但是,Tcl有着不熟悉的语法,未对数据描述提供良好的支持,并且只在Unix平台上运行。我们不考虑LISPScheme,因为它们有着不友好的语法。Python仍处在幼年期。在Tecgraf的自由的自力更生氛围下,我们非常自然的尝试开发自己的脚本语言 ... 由于这门语言的很多潜在用户不是专业编程者,语言应当避免神秘的语法和语义。新语言的实现应当是高度可移植的,因为Tecgraf的客户有着非常多样的各种计算机平台。最后,由于我们预期Tecgraf的其他产品也需要嵌入脚本语言,新语言应当追随SOL的例子并提供为带有C API的库。

Lua的控制结构语法大部份引入自Modula-2ifwhilerepeat/until),但也受到来自CLU(多赋值和从函数调用的多个返回值,是对引用参数或显式指针的更简单的替代)、C++(“允许局部变量只在需要的地方声明的灵巧想法”[5])、SNOBOLAWK关联数组)的影响。在发表于《Dr. Dobb's Journal》的文章中,Lua的创立者还声称,有着单一且无所不在的数据结构机制(列表)的LISPScheme,对他们决定将表格开发为Lua的主要数据结构起到了主要影响[6]

Lua的语义久而久之日趋受到Scheme的影响[5],特别是介入了匿名函数和完全的词法作用域。Lua在版本5.0之前在类似BSD许可证之下发行。自从版本5.0以来,Lua采用了MIT许可证

特性

Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快。它用ANSI C语言编写[7],并以源代码形式开放,编译后的完整参考解释器只有大约247kB[7],可以很方便的嵌入别的程式裡。和许多“大而全”的语言不一样,網路通訊、图形界面等都没有預設提供。但是Lua可以很容易地被扩展:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。事实上,现在已经有很多成熟的扩展模块可供选用。

Lua是一个动态弱类型语言,支援增量式垃圾收集策略。有内建的,与操作系统无关的协作式多线程支援。Lua原生支援的数据类型很少,只提供了数值(缺省是双精度浮点数,可配置)、布尔量、字符串、表格、函数线程以及用户自定义数据这几种。但是其处理表和字符串的效率非常之高,加上元表的支援,开发者可以高效的模拟出需要的复杂数据类型(比如集合、数组等)。

Lua是一种多重编程范式的程式设计语言:它只提供了很小的一个特性集合来满足不同编程范式的需要,而不是为某种特定的编程范式提供繁杂的特性支援。例如,Lua并不提供继承这个特性,但是你可以用元表格来模拟它。诸如命名空间这些概念都没有在语言基本特性中实现,但是我们可以用表格结构(Lua唯一提供的复杂数据结构)轻易模拟。Lua可以在运行时随时构造出一个函数,并把它看作一个对象(正所谓头等函数),这个特性可以很好的满足函数式编程的需要。正是提供了这些基本的元特性,我们可以任意的对语言进行自需的改造。

语法

经典的Hello World!程序可以写为如下[8]

print("Hello World!")

或者如下:

print 'Hello World!'

在Lua中注释可以于双连字符并行至此行的结束,类似于AdaEiffelHaskellSQLVHDL。多行字符串和注释用双方括号来装饰。

下例中实现了一个阶乘函数:

function factorial(n)
  local x = 1
  for i = 2, n do
    x = x * i
  end
  return x
end

控制流

Lua有四种类型的循环:the while循环repeat循环(类似于do while循环),数值for循环,和通用for循环,和条件语句if exp then ... {elseif exp then ...} [else ...] end

--condition = true

while condition do
  --statements
end

repeat
  --statements
until condition

for i = first, last, delta do  --delta可以是负数,允许计数增加或减少的循环
  --statements
  --example: print(i)
end

通用for循环:

for key, value in pairs(_G) do
  print(key, value)
end

将在表格_G上使用标准迭代器函数pairs进行迭代,直到它返回nil

可以有嵌套的循环,就是在其他循环中的循环。

local grid = {
  { 11, 12, 13 },
  { 21, 22, 23 },
  { 31, 32, 33 }
}

for y, row in ipairs(grid) do
  for x, value in ipairs(row) do
    print(x, y, grid[y][x])
  end
end

函数

Lua将函数处理为头等值,在下例子中用print函数的表现可以修改来展示:

do
  local oldprint = print
  -- 存储当前的print函数为oldprint
  function print(s)
    --[[重新定义print函数。新函数只有一个实际参数。
      平常的print函数仍可以通过oldprint使用。]]
    oldprint(s == "foo" and "bar" or s)
  end
end

任何对print的进一步调用都要经由新函数,并且由于Lua的词法作用域,这个旧的print函数将只能被这个新的修改了的print访问到。

Lua还支持闭包,展示如下:

function addto(x)
  -- 返回一个把实际参数加到x上
  return function(y)
    --[=[ 在我们引用变量x的时候,它在当前作用域的外部,
      它的生命期会比这个匿名函数短,Lua建立一个闭包。]=]
    return x + y
  end
end
fourplus = addto(4)
print(fourplus(3))  -- 打印7

--这也可以通过如下方式调用这个函数来完成:
print(addto(4)(3))
--[[这是因为这直接用视件参数3调用了从addto(4)返回的函数。
    如果经常这么调用的话会减少数据消耗并提升性能。]]

每次调用的时候为变量x建立新的闭包,所以返回的每个新匿名函数都访问它自己的x参数。闭包由Lua的垃圾收集器来管理,如同任何其他对象一样。

function create_a_counter()
    local count = 0
    return function()
        count = count + 1
        return count
    end
end

create_a_counter()會返回一個匿名函数,這個匿名函數會把count加1後再回傳。在匿名函數中的變數count的值會一直被保存在匿名函數中。因此调用create_a_counter()时产生一个记数器函数,每次调用记数器函数,都会得到一个比上次大1的值。

变量类型

Lua是一种动态类型语言,因此语言中没有类型的定义,不需要声明变量类型,每个变量自己保存了类型。

有8种基本类型:nil、布尔值(boolean)、数字型(number)、字符串型(string)、用户自定义类型(userdata)、函数(function)、线程(thread)和表(table)。

    print(type(nil))                    -- 输出 nil
    print(type(99.7+12*9))              -- 输出 number
    print(type(true))                   -- 输出 boolean
    print(type("Hello Wikipedia"))      -- 输出 string
    print(type(print))                  -- 输出 function
    print(type{1, 2, test = "test"})    -- 输出 table

表格

表格(table)是Lua中最重要的数据结构(并且是设计中唯一内建的复合数据类型),并且是所有用户创建类型的基础。它们是增加了自动数值键和特殊语法的关联数组

表格是键和数据的有序对的搜集,其中的数据用键来引用;换句话说,它是散列异构关联数组。

表格使用{}构造器语法来创建。

a_table = {} -- 建立一个新的空表格

表格总是用引用来传递的(参见传共享调用)。

键(索引)可以是除了nilNaN的任何值,包括函数。

a_table = {x = 10}  -- 建立一个新表格,具有映射"x"到数10的一个表项。
print(a_table["x"]) -- 打印于这个字符串关联的值,这里是10。
b_table = a_table
b_table["x"] = 20   -- 在表格中的值变更为20。
print(b_table["x"]) -- 打印20。
print(a_table["x"]) -- 还是打印20,因为a_table和b_table都引用相同的表格。

通过使用字符串作为键,表格经常用作结构(或记录)。由于这种用法太常见,Lua为访问这种字段提供了特殊语法[9]

point = { x = 10, y = 20 }   -- 建立一个新表格
print(point["x"])            -- 打印10
print(point.x)               -- 同上一行完全相同含义。易读的点只是语法糖。

通过使用表格来存储有关函数,它可以充当名字空间。

Point = {}

Point.new = function(x, y)
  return {x = x, y = y}  --  return {["x"] = x, ["y"] = y}
end

Point.set_x = function(point, x)
  point.x = x  --  point["x"] = x;
end

表格被自动的赋予了数值键,使得它们可以被用作数组数据类型。第一个自动索引是1而非0,因为很多其他编程语言都这样(尽管显式的索引0是允许的)。

数值键1不同于字符串键"1"

array = { "a", "b", "c", "d" }   -- 索引被自动赋予。
print(array[2])                  -- 打印"b"。在Lua中自动索引开始于1。
print(#array)                    -- 打印4。#是表格和字符串的长度算符。
array[0] = "z"                   -- 0是合法索引。
print(#array)                    -- 仍打印4,因为Lua数组是基于1的。

表格的长度t被定义为任何整数索引n,使得t[n]不是nilt[n+1]nil;而且如果t[1]niln可以是0。对于一个正规表格,具有非nil值从1到给定n,它的长度就精确的是n,它的最后的值的索引。如果这个数组有“洞”(就是说在其他非nil值之间的nil值),则#t可以是直接前导于nil值的任何一个索引(就是说可以把任何这种nil值当作数组的结束[10])。

ExampleTable =
{
  {1, 2, 3, 4},
  {5, 6, 7, 8}
}
print(ExampleTable[1][3]) -- 打印"3"
print(ExampleTable[2][4]) -- 打印"8"

表格可以是对象的数组。

function Point(x, y)        -- "Point"对象构造器
  return { x = x, y = y }   -- 建立并返回新对象(表格)
end
array = { Point(10, 20), Point(30, 40), Point(50, 60) }   -- 建立point的数组
                        -- array = { { x = 10, y = 20 }, { x = 30, y = 40 }, { x = 50, y = 60 } };
print(array[2].y)                                         -- 打印40

使用散列映射来模拟数组通常比使用真正数组要慢;但Lua表格为用作数组而做了优化来避免这个问题[11]

元表格

可扩展的语义是Lua的关键特征,而“元表格”概念允许Lua的表格以强力方式来定制。下列例子展示了一个“无限”表格。对于任何nfibs[n]会给出第n斐波那契数,使用了动态规划记忆化

fibs = { 1, 1 }                                -- 给fibs[1]和fibs[2]初始值。
setmetatable(fibs, {
  __index = function(values, n)                --[[__index是Lua预定义的函数, 
                                                   如果"n"不存在则调用它。]]
    values[n] = values[n - 1] + values[n - 2]  -- 计算并记忆化fibs[n]。
    return values[n]
  end
})

面向对象编程

尽管Lua没有内建的的概念,可以用过两个语言特征实现面向对象编程头等函数和表格。通过放置函数和有关数据入表格,形成一个对象。继承(单继承和多重继承二者)可以通过使用元表格机制来实现,告诉这个对象在那些父对象中查找不存在的方法。

对于这些技术不采用“类”的概念,而是采用原型,类似于SelfJavaScript。建立新对象要么通过工厂方法(从头构造一个新对象)要么通过复制现存的对象。

Lua为便利对象定向提供了一些语法糖。要声明在原型表格内的成员函数,可以使用function table:func(args),它等价于function table.func(self, args)。调用类方法也使用冒号,object:func(args)等价于object.func(object, args)

建立一个基本的向量对象:

local Vector = {}
Vector.__index = Vector

function Vector:new(x, y, z)    -- 构造器
  return setmetatable({x = x, y = y, z = z}, Vector)
end

function Vector:magnitude()     -- 另一个方法
  -- 使用self引用隐蔽的对象
  return math.sqrt(self.x^2 + self.y^2 + self.z^2)
end

local vec = Vector:new(0, 1, 0) -- 建立一个向量
print(vec:magnitude())          -- 调用一个法方法 (输出: 1)
print(vec.x)                    -- 访问一个成员变量 (输出: 0)

实现

Lua程序不是从文本式的Lua文件直接解释的,而是编译字节码,接着把它运行在Lua虚拟机上。编译过程典型的对于用户是不可见并且是在运行时间进行的,但是它可以离线完成用来增加装载性能或通过排除编译器来减少对宿主环境的内存占用。Lua字节码还可以在Lua之内产生和执行,使用来自字符串库的dump函数和load/loadstring/loadfile函数。Lua版本5.3.4是用大约24,000行C代码实现的[3][7]

像大多数CPU,而不像多数虚拟机(它们是基于堆栈的),Lua VM是基于寄存器的,因此更加类似真实的硬件设计。寄存器架构既避免了过多的值复制又减少了每函数的指令的总数。Lua 5的虚拟机是第一个广泛使用的基于堆栈的纯VM[12]ParrotAndroidDalvik是另外两个周知的基于寄存器的VM。PCScheme的VM也是基于寄存器的[13]

下面的例子列出上面定义的阶乘函数的字节码(通过luac 5.1编译器来展示)[14]

function <factorial.lua:1,7> (9 instructions, 36 bytes at 0x8063c60)
1 param, 6 slots, 0 upvalues, 6 locals, 2 constants, 0 functions
	1	[2]	LOADK    	1 -1	; 1
	2	[3]	LOADK    	2 -2	; 2
	3	[3]	MOVE     	3 0
	4	[3]	LOADK    	4 -1	; 1
	5	[3]	FORPREP  	2 1	; to 7
	6	[4]	MUL      	1 1 5
	7	[3]	FORLOOP  	2 -2	; to 6
	8	[6]	RETURN   	1 2
	9	[7]	RETURN   	0 1

C API

Lua意图被嵌入到其他应用之中,为了这个目的而提供了C API。API被分成两部份:Lua核心库和辅助库[15]。Lua API的设计消除了对用C代码的手动引用管理的需要,不同于Python的API。API就像语言本身一样是极简主义的。高级功能通过辅助库来提供,它们很大程度上构成自预处理器,用以帮助做复杂的表格操作。

Lua C API是基于堆栈的。Lua提供压入和弹出最简单C数据类型(整数、浮点数等)进入和离开堆栈的函数,还有通过堆栈操作表格的函数。Lua堆栈稍微不同于传统堆栈,例如堆栈可以直接的被索引。负数索引指示从栈顶开始往下的偏移量。例如−1是在顶部的(最新进压入的值),而整数索引指示从底部(最旧的值)往上的偏移量。在C和Lua函数之间Marshalling数据也使用堆栈完成。要调用一个Lua函数,把实际参数压入堆栈,并接着使用lua_call来调用实际的函数。在写从Lua直接调用的C函数的时候,实际参数读取自堆栈。

下面是从C调用Lua函数的例子:

#include <stdio.h>
#include <lua.h> // Lua主要库 (lua_*)
#include <lauxlib.h> // Lua辅助库 (luaL_*)

int main(void)
{
    // 建立一个Lua状态
    lua_State *L = luaL_newstate();

    // 装载并执行一个字符串
    if (luaL_dostring(L, "function foo (x,y) return x+y end")) {
        lua_close(L);
        return -1;
    }

    // 压入全局"foo"(上面定义的函数)的值
    // 到堆栈,跟随着整数5和3
    lua_getglobal(L, "foo");
    lua_pushinteger(L, 5);
    lua_pushinteger(L, 3);
    lua_call(L, 2, 1); // 调用有二个实际参数和一个返回值的函数
    printf("Result: %d\n", lua_tointeger(L, -1)); // 打印在栈顶的项目的整数值
    lua_pop(L, 1); // 返回堆栈至最初状态
    lua_close(L); // 关闭Lua状态
    return 0;
}

如下这样运行这个例子:

$ cc -o example example.c -llua
$ ./example
Result: 8

C API还提供一些特殊表格,位于各种Lua堆栈中的“伪索引”之上。Lua 5.2之前[16],在LUA_GLOBALSINDEX之上是全局表格;_G来自Lua内部,它是主名字空间。还有一个注册表位于LUA_REGISTRYINDEX,在这里C程序可以存储Lua值用于以后检索。

还可以使用Lua API写扩展模块。扩展模块是通过向Lua脚本提供本地设施,用来扩展解释器的功能的共享对象。从Lua方面看来,这种模块出现为一个名字空间表格,它持有自己的函数和变量。Lua脚本可以使用require装载扩展模块[15],就像用Lua自身写的模块一样。一组仍在增长中的叫做“rock”的模块可以通过叫做LuaRocks软件包管理器获取到[17],类似于CPANRubyGemsPython eggs。对大多数流行的编程语言包括其他脚本语言,都存在预先写好的Lua绑定[18]。对于C++,有许多基于模板的方式和一些自动绑定生成器。

应用

视频游戏开发中,Lua被程序员广泛的用作脚本语言,主要由于它的可感知到的易于嵌入、快速执行,和短学习曲线[19]

在2003年,GameDev.net组织的一次投票,说明了Lua是游戏编程的最流行脚本语言[20]。在2012年1月12日,Lua被《游戏开发者》宣布为编程工具范畴的Front Line奖2011年度获奖者[21]

大量非游戏应用也出于可扩展性而使用Lua,比如TeX排版设置语言实现LuaTeX键-值数据库Redis、文本编辑器Neovimweb服务器Nginx

通过Scribunto扩展,Lua可获得为MediaWiki软件中的服务器端脚本语言,Wikipedia和其他wiki都基于了它[22][23]。它的应用包括允许从Wikidata集成数据到文章中[24],和助力于自动生物分类框系统

Lua可以用於嵌入式硬體,不僅可以嵌入其他編程語言,而且可以嵌入微處理器中,例如NodeMCU開源硬體項目將Lua嵌入到Wi-Fi SoC中[25]

编译成Lua的语言

参考资料

  1. Ring Team. . ring-lang.net. ring-lang. 5 December 2017 [2020-09-22]. (原始内容存档于2018-12-25).
  2. Yuri Takhteyev. . Foreign Affairs. 21 April 2013 [25 April 2013]. (原始内容存档于2014-10-24).
  3. Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Filho, Waldemar Celes. . Software: Practice and Experience. June 1996, 26 (6): 635–652 [24 October 2015]. doi:10.1002/(SICI)1097-024X(199606)26:6<635::AID-SPE26>3.0.CO;2-P. (原始内容存档于2020-10-04).
  4. . 2001 [2008-12-18]. (原始内容存档于2020-11-09).
  5. Ierusalimschy, R.; Figueiredo, L. H.; Celes, W. (PDF). . 2007: 2–1–2–26. ISBN 978-1-59593-766-7. doi:10.1145/1238844.1238846.
  6. Figueiredo, L. H.; Ierusalimschy, R.; Celes, W. . Dr. Dobb's Journal 21 (12). December 1996: 26–33 [2020-09-21]. (原始内容存档于2020-10-04).
  7. . Lua.org. [2011-08-11]. (原始内容存档于2011-08-09).
  8. . [2020-10-13]. (原始内容存档于2017-10-27).
  9. . 2014 [2014-02-27]. (原始内容存档于2021-01-30).
  10. . 2012 [2012-10-16]. (原始内容存档于2021-01-30).
  11. . 2006 [2011-03-24]. (原始内容存档于2020-08-03).
  12. Ierusalimschy, R.; Figueiredo, L. H.; Celes, W. . J. Of Universal Comp. Sci. 2005, 11 (7): 1159–1176 [2020-10-14]. (原始内容存档于2020-07-30).
  13. Texas Instruments. . 1990. ISBN 0-262-70040-9.
  14. Kein-Hong Man. (PDF). 2006 [20 December 2008]. (原始内容 (PDF)存档于2010-06-19).
  15. . Lua.org. [2012-10-23]. (原始内容存档于2020-11-12).
  16. . Lua 5.2 Reference Manual. Lua.org. [2014-05-09]. (原始内容存档于2020-11-12).
  17. . LuaRocks wiki. [2009-05-24]. (原始内容存档于2021-02-06).
  18. . Lua-users wiki. [2009-05-24]. (原始内容存档于2020-11-12).
  19. . [2017-04-22]. (原始内容存档于2013-08-20).
  20. . [2017-04-22]. (原始内容存档于2003-12-07).
  21. . [2017-04-22]. (原始内容存档于2013-06-15).
  22. . MediaWiki.org. [21 February 2019]. (原始内容存档于2020-12-25).
  23. . [2018-12-19]. (原始内容存档于2021-02-04).
  24. . www.wikidata.org. [2018-12-21]. (原始内容存档于2020-07-20).
  25. Huang R. . Github. [3 April 2015]. (原始内容存档于2015-08-15).
  26. . moonscript.org. [2020-09-25]. (原始内容存档于2020-11-19).
  27. leaf, , 2020-09-23 [2020-09-25], (原始内容存档于2020-12-02)
  28. Andre Alves Garzia. . AndreGarzia.com. [September 25, 2020]. (原始内容存档于2020-12-18).

延伸阅读

  • Ierusalimschy, R. 3rd. Lua.org. 2013 [2020-10-13]. ISBN 978-85-903798-5-0. (原始内容存档于2021-01-18). (The 1st ed. is available online 页面存档备份,存于.)
  • Gutschmidt, T. . Course Technology PTR. 2003. ISBN 978-1-59200-077-7.
  • Schuytema, P.; Manyen, M. . Charles River Media. 2005. ISBN 978-1-58450-404-7.
  • Jung, K.; Brown, A. . Wrox Press. 2007 [7 July 2018]. ISBN 978-0-470-06917-2. (原始内容存档于2018-07-08).
  • Figueiredo, L. H.; Celes, W.; Ierusalimschy, R. (编). . Lua.org. 2008 [2020-10-14]. ISBN 978-85-903798-4-3. (原始内容存档于2020-11-09).
  • Takhteyev, Yuri. . The MIT Press. 2012 [2020-10-14]. ISBN 978-0-262-01807-4. (原始内容存档于2012-11-02). Chapters 6 and 7 are dedicated to Lua, while others look at software in Brazil more broadly.
  • Varma, Jayant. . Apress. 2012. ISBN 978-1-4302-4662-6.
  • Matheson, Ash. . GameDev.net. 29 April 2003 [3 January 2013]. (原始内容存档于2012-12-18).
  • Fieldhouse, Keith. . ONLamp.com. O'Reilly Media. 16 February 2006 [28 February 2006]. (原始内容存档于2006-03-12).
  • Streicher, Martin. . developerWorks. IBM. 28 April 2006 [2020-10-14]. (原始内容存档于2009-07-02).
  • Quigley, Joseph. . Linux Journal. 1 June 2007 [2020-10-14]. (原始内容存档于2020-10-30).
  • Hamilton, Naomi. . Computerworld (IDG). 11 September 2008 [7 July 2018]. (原始内容存档于2018-07-08). Interview with Roberto Ierusalimschy.
  • Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar. . ACM Queue. 12 May 2011, 9 (5): 20–29 [7 July 2018]. doi:10.1145/1978862.1983083. (原始内容存档于2018-07-08). How the embeddability of Lua impacted its design.
  • Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar. . Communications of the ACM. November 2018, 61 (11): 114–123 [2020-10-14]. doi:10.1145/3186277. (原始内容存档于2021-01-09).
  • Lua papers and theses 页面存档备份,存于

外部链接

从维基百科的姊妹计划
了解更多有关
Lua”的内容
维基新闻上的新闻
维基教科书上的教科书和手册
维基学院上的學習资源

This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.