From 3facca083504bc8cb1d47d69974847c01d9b2477 Mon Sep 17 00:00:00 2001 From: Wobbo Date: Mon, 3 Mar 2014 21:34:43 +0100 Subject: [PATCH] Better variable expansion in besh --- .../assets/opencomputers/lua/rom/bin/besh.lua | 221 ++++++++++++++---- 1 file changed, 171 insertions(+), 50 deletions(-) diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua index 0e6287f17..ad5d7b25c 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua @@ -11,79 +11,200 @@ local term = require("term") local text = require("text") local unicode = require("unicode") -local function expandVars(token) - local name = nil - local special = false - local ignore = false - local ignoreChar ='' +function expandParam(param) + local par, word, op = nil, nil, nil + for _, oper in ipairs{':%-', '%-', ':=', '=', ':%?','%?', ':%+', '%+'} do + par, word = param:match("(.-)"..oper.."(.*)") + if word then + op = oper + break + end + end + if word then + local stat = os.getenv(par) + if op == ':%-' then + if stat ~= '' and stat ~= nil then + return stat + else + return word + end + elseif op == '%-' then + if stat ~= '' and stat ~= nil then + return stat + elseif stat == '' then + return nil + elseif stat == nil then + return expand(word) + end + elseif op == ':=' then + if stat ~= '' and stat ~= nil then + return stat + else + os.setenv(par, word) + return expand(word) + end + elseif op == '=' then + if stat ~= '' and stat ~= nil then + return stat + elseif stat == '' then + return nil + elseif stat == nil then + os.setenv(par, word) + return expand(word) + end + elseif op == ':%?' then + if stat ~= '' and stat ~= nil then + return stat + else + error(par.." is not set!") + end + elseif op == '%?' then + if stat ~= '' and stat ~= nil then + return stat + elseif stat == '' then + return nil + elseif stat == nil then + error(par.." is not set") + end + elseif op == ':%+' then + if stat ~= '' and stat ~= nil then + return expand(word) + else + return nil + end + elseif op == '%+' then + if stat ~= nil then + return expand(word) + else + return nil + end + end + elseif string.sub(param, 1,1) == '#' then + return #(os.getenv(param:sub(2, -1))) + else + return os.getenv(param) + end +end + +function expandCmd(cmd) + return cmd +end + +function expandMath(expr) + local success, reason = load("return "..expr, os.getenv("SHELL"), 't', {}) + if success then + return success() + else + return reason + end +end + +function expand(token) + local expr = {} + local matchStack = {} local escaped = false local lastEnd = 1 local doubleQuote = false local singleQuote = false + local mathBoth local endToken = {} for i = 1, unicode.len(token) do local char = unicode.sub(token, i, i) if escaped then - if name then - table.insert(name, char) + if expr then + table.insert(expr, char) end escaped = false elseif char == '\\' then escaped = not escaped table.insert(endToken, unicode.sub(token, lastEnd, i-1)) lastEnd = i+1 - elseif char == '"' and not singleQuote then - doubleQuote = not doubleQuote - table.insert(endToken, unicode.sub(token, lastEnd, i-1)) - lastEnd = i+1 - elseif char == "'" and not doubleQuote then - singleQuote = not singleQuote - table.insert(endToken, unicode.sub(token, lastEnd, i-1)) - lastEnd = i+1 - elseif char == "$" and not doubleQuote and not singleQuote then - if name then - ignore = true + elseif char == matchStack[#matchStack] then + local match + table.remove(matchStack) + if char == '}' then + local param = table.concat(table.remove(expr)) + match = expandParam(param) + elseif char == ')' then + if expr[#expr].cmd then + local cmd = table.concat(table.remove(expr)) + match = expandCmd(cmd) + elseif expr[#expr].math then + if not mathBoth then + mathBoth = i + elseif mathBoth == i - 1 then + local mth = table.concat(table.remove(expr)) + match = expandMath(mth) + mathBoth = nil + else + return nil, "Unmatched )" + end + end + elseif char == '`' then + local cmd = table.concat(table.remove(expr)) + match = expandCmd(cmd) + elseif char == "'" then + singleQuote = false + elseif char == '"' then + doubleQuote = false + end + if #expr > 0 then + table.insert(expr[#expr], match) else - name = {} + table.insert(endToken, match) + end + lastEnd = i+1 + elseif char == '"' and not singleQuote then + doubleQuote = true + if #expr <= 1 then table.insert(endToken, unicode.sub(token, lastEnd, i-1)) end - elseif char == '{' and #name == 0 then - if ignore and ignoreChar == '' then - ignoreChar = '}' - else - special = true - end - elseif char == '(' and ignoreChar == '' then - ignoreChar = ')' - elseif char == '`' and special then - ignore = true - ignoreChar = '`' - elseif char == '}' and not ignore and not doubleQuote and not singleQuote then - table.insert(endToken, os.getenv(table.concat(name))) - name = nil + table.insert(matchStack, '"') lastEnd = i+1 + elseif char == "'" and not doubleQuote then + singleQuote = not singleQuote + if #expr <= 0 then + table.insert(endToken, unicode.sub(token, lastEnd, i-1)) + end + table.insert(matchStack, "'") + lastEnd = i+1 + elseif char == "$" and not singleQuote then + table.insert(expr, {}) + if #expr <= 0 then + table.insert(endToken, unicode.sub(token, lastEnd, i-1)) + end + elseif char == '{' and #expr > 0 and #(expr[#expr]) == 0 then + table.insert(matchStack, '}') + if expr[#expr] == 0 then + expr[#expr].special = true + end + elseif char == '(' and #expr > 0 and #(expr[#expr]) == 0 then + table.insert(matchStack, ')') + if expr[#expr].cmd then + expr[#expr].cmd = false + expr[#expr].math = true + else + expr[#expr].cmd = true + end + elseif char == '`' then + table.insert(expr, {cmd = true}) + table.insert(matchStack, '`') elseif char == '"' and not singleQuote then doubleQuote = not doubleQuote elseif char == "'" and not doubleQuote then singleQuote = not singleQuote - elseif name and (char:match("[%a%d_]") or special) then - if char:match("%d") and #name == 0 then - error "Identifiers can't start with a digit!" - end - table.insert(name, char) - elseif char == ignoreChar and ignore then - ignore = false - ignoreChar = '' - elseif name then -- We are done with gathering the name - table.insert(endToken, os.getenv(table.concat(name))) - name = nil + elseif #expr > 0 and (char:match("[%a%d_]") or #matchStack > 0) then + table.insert(expr[#expr], char) + elseif #expr > 0 then -- We are done with gathering the name + table.insert(endToken, os.getenv(table.concat(table.remove(expr)))) lastEnd = i end end - if name then - table.insert(endToken, os.getenv(table.concat(name))) - name = nil - else + while #expr > 0 do + local xpr = table.remove(expr) + table.insert(expr[#expr] or endToken, os.getenv(table.concat(xpr))) + end + if lastEnd >= #token then table.insert(endToken, unicode.sub(token, lastEnd, -1)) end return table.concat(endToken) @@ -96,7 +217,7 @@ local function parseCommand(tokens) -- Variable expansion for all command parts. for i = 1, #tokens do - tokens[i] = expandVars(tokens[i]) + tokens[i] = expand(tokens[i]) end -- Resolve alias for command. @@ -348,4 +469,4 @@ local function execute(command, env, ...) end local env = setmetatable({os=setmetatable({execute=execute}, {__index=os})}, {__index=_ENV}) -shell.execute("/bin/sh", env, ...) \ No newline at end of file +shell.execute("/bin/sh", env, ...)