从上一回的探索中,我已经大概知道怎麽自订 CC: Tweaked 电脑开机跑的程序
也在过程中慢慢熟悉 Lua 的函数宣告语法
今天我想继续探索其他的函数宣告特性
主题则是,为何 CC: Tweaked 电脑懂我的指令?
请大家回想
当我打 help 的时候,电脑知道要执行 /rom/programs/help.lua
当我打 hello 的时候,电脑也知道要执行 /rom/programs/fun/hello.lua
它是怎麽知道的?!
首先我回头看 bios.lua 执行 shell.lua 之後
shell.lua 会先跑 startup.lua
接着就开始一段无穷回圈在 shell.lua #596
while not bExit do
-- 略 --
local sLine
if settings.get("shell.autocomplete") then
sLine = read(nil, tCommandHistory, shell.complete)
else
sLine = read(nil, tCommandHistory)
end
-- 略 --
shell.run(sLine)
end
read 是读取玩家的输入,这边看起来很复杂,我先不深入
先来看 shell.run
function shell.run(...)
local tWords = tokenise(...)
local sCommand = tWords[1]
if sCommand then
return shell.execute(sCommand, table.unpack(tWords, 2))
end
return false
end
如上述范例,宣告函数时给三个点就表示它可接收不定数量的参数值
而如果要原封不动地将这些参数,全部传入其他函数,也是相同的表示
tokenise() 是将使用者输入的整行文字切割,接着只取第一个 token 当作指令
并继续传给 shell.execute()
table.unpack 是 Lua 为 table 型别提供的基础函数
可以比较方便地将 table 内的部分元素取出并回传
这边 table.unpack(tWords, 2) 相当於
tWords[2], tWords[3], tWords[4] ....... 一直到最後一个数值
也就是将使用者输入的参数取出,并传入 shell.execute()
如果 table.unpack 有给第三个参数,例如
table.unpack(tWords, 2, 5)
那麽就相当於
tWords[2], tWords[3], tWords[4], tWords[5]
接下来继续看 shell.execute
它只有 command 是固定参数,其余都是变动参数,因为每个指令可接收的参数量是不同的
所以这里的 ... 代表该指令的所有参数
例如假设使用者输入
set motd.path "/mymotd.txt"
那麽 command = 'set', 後面的参数则会形成一个 table { "motd.path", "/mymotd.txt" }
function shell.execute(command, ...)
expect(1, command, "string")
for i = 1, select('#', ...) do
expect(i + 1, select(i, ...), "string")
end
local sPath = shell.resolveProgram(command)
if sPath ~= nil then
-- 略 --
local sDir = fs.getDir(sPath)
local env = createShellEnv(sDir)
env.arg = { [0] = command, ... }
local result = os.run(env, sPath, ...)
-- 略 --
return result
else
printError("No such program")
return false
end
end
上述有一小段 select 语法,这是 Lua 内建的函数之一,可以从 table 取得特定 index 的元素
例如这是取得第 3 个元素
print(select(3, ...))
而如果是给 #,则是取得 table 内元素的总个数
print(select('#', ...))
这相当於
args = { ... }
print(#args)
这在 shell.lua #575 也有用到这样的雨法
local tArgs = { ... }
if #tArgs > 0 then
-- "shell x y z"
-- Run the program specified on the commandline
shell.run(...)
接下来又到了上次提到的 shell.resolveProgram()
function shell.resolveProgram(command)
expect(1, command, "string")
-- Substitute aliases firsts
if tAliases[command] ~= nil then
command = tAliases[command]
end
-- If the path is a global path, use it directly
if command:find("/") or command:find("\\") then
local sPath = shell.resolve(command)
if fs.exists(sPath) and not fs.isDir(sPath) then
return sPath
else
local sPathLua = pathWithExtension(sPath, "lua")
if fs.exists(sPathLua) and not fs.isDir(sPathLua) then
return sPathLua
end
end
return nil
end
-- Otherwise, look on the path variable
for sPath in string.gmatch(sPath, "[^:]+") do
sPath = fs.combine(shell.resolve(sPath), command)
if fs.exists(sPath) and not fs.isDir(sPath) then
return sPath
else
local sPathLua = pathWithExtension(sPath, "lua")
if fs.exists(sPathLua) and not fs.isDir(sPathLua) then
return sPathLua
end
end
end
-- Not found
return nil
end
第二段的部分,注解已经说明了大概
若使用者输入的指令含有 / 或
则直接在根目录寻找指令
-- If the path is a global path, use it directly
第三段则是当使用者输入一般指令,就会去取 Path 环境变数
Path 的设定也是在 shell.lua
local sPath = parentShell and parentShell.path() or ".:/rom/programs"
我想可以先忽略 parentShell,应该可以认定 sPath 的预设值就是 .:/rom/programs
也就是说,当使用者输入任何指令的时候,并且没有给 / 或
那麽就会从当下的位置寻找 lua 程序
如果找不到,就会去 /rom/programs 的位置寻找
而 pathWithExtension 很显然则是自动帮我们加上 .lua 副档名
最後我来继续研究一下可变参数的特性
假设我宣告了一个函数,可接收两个固定参数,以及不固定数量的参数
以下的呼叫会怎麽对应呢?
function func(a, b, ...)
args = { ... }
print(a, b, args[1], args[2], #args)
end
func(1) -- 1 nil nil nil 0
func(1, 2) -- 1 2 nil nil 0
func(1, 2, 3) -- 1 2 3 nil 1
func(1, 2, 3, 4) -- 1 2 3 4 2
也就是说,会先对应固定参数,如果传入参数不够,则会给 nil
其余的参数则全都会形成 table
以上是今天的挖矿,下一回我仍会继续探索,电脑如何知道我的指令?
主题是指令码的自动完成!
我们终於进到写程序的部分了,前几天我们都在教学基本的使用,今天就让我来教大家吧!我们就先开到程序档的...
在上一篇文章介绍了ManoMotion的安装与介绍,今天我们要使用ManoMotion来制作打地鼠游...
前面介绍了那麽多内容,那接下来就让我们来实作第一个restful api server吧 在每个後端...
如果你有 Localhost 开发环境需要以 HTTPS 浏览时,可以参考以下方法: 方法一:vue...
Hello, 各位 iT邦帮忙 的粉丝们大家好~~~ 本篇是 Re: 从零开始用 Xamarin 技...