diff --git a/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua b/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua index 8a5dcc616..ca96e704e 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/bin/ls.lua @@ -13,7 +13,7 @@ for i = 1, #dirs do if i > 1 then io.write("\n") end - io.write("/", path, ":\n") + io.write(path, ":\n") end local list, reason = fs.list(path) if not list then diff --git a/src/main/resources/assets/opencomputers/lua/rom/boot/02_os.lua b/src/main/resources/assets/opencomputers/lua/rom/boot/02_os.lua index e900d784b..b3e90436a 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/boot/02_os.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/boot/02_os.lua @@ -4,6 +4,8 @@ local fs = require("filesystem") local shell = require("shell") local unicode = require("unicode") +local env = {} + os.execute = function(command) if not command then return type(shell) == "table" @@ -15,6 +17,17 @@ function os.exit() error("terminated", 0) end +function os.getenv(varname) + return env[varname] +end + +function os.setenv(varname, value) + checkArg(1, varname, "string") + checkArg(2, value, "string", "nil") + env[varname] = value + return env[varname] +end + function os.remove(...) return fs.remove(...) end diff --git a/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua b/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua index aebcb3d32..c53ddaf49 100644 --- a/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua +++ b/src/main/resources/assets/opencomputers/lua/rom/lib/shell.lua @@ -61,65 +61,158 @@ local function findFile(name, ext) return false end -local function resolveAlias(program, args) - local lastProgram = nil +function expandVars(token) + local name = nil + local special = false + local ignore = false + local ignoreChar ='' + local escaped = false + local lastEnd = 1 + local doubleQuote = false + local singleQuote = false + 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) + 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 + else + name = {} + 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 + lastEnd = i+1 + 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 + lastEnd = i + end + end + if name then + table.insert(endToken, os.getenv(table.concat(name))) + name = nil + else + table.insert(endToken, unicode.sub(token, lastEnd, -1)) + end + return table.concat(endToken) +end + +local function resolveAlias(tokens) + local program, lastProgram = tokens[1], nil + table.remove(tokens, 1) while true do local alias = text.tokenize(shell.getAlias(program) or program) - if alias[1] == lastProgram then + program = alias[1] + if program == lastProgram then break end lastProgram = program - program = alias[1] - if args then - for i = 2, #alias do - table.insert(args, alias[i]) - end + table.remove(alias, 1) + for i = 1, #tokens do + table.insert(alias, tokens[i]) end + tokens = alias end - return program + return program, tokens end local function parseCommand(tokens) - local program, args, input, output = tokens[1], {}, nil, nil - if not program then + if #tokens == 0 then return end - program = resolveAlias(program, args) + -- Variable expansion for all command parts. + for i = 1, #tokens do + tokens[i] = expandVars(tokens[i]) + end + -- Resolve alias for command. + local program, args = resolveAlias(tokens) + + -- Find redirects. + local input, output, mode = nil, nil, "write" + tokens = args + args = {} + local function smt(call) -- state metatable factory + local function index(_, token) + if token == "<" or token == ">" or token == ">>" then + return "parse error near " .. token + end + call(token) + return "args" -- default, return to normal arg parsing + end + return {__index=index} + end + local sm = { -- state machine for redirect parsing + args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"}, + smt(function(token) + table.insert(args, token) + end)), + input = setmetatable({}, smt(function(token) + input = token + end)), + output = setmetatable({}, smt(function(token) + output = token + mode = "write" + end)), + append = setmetatable({}, smt(function(token) + output = token + mode = "append" + end)) + } + -- Run state machine over tokens. local state = "args" - for i = 2, #tokens do - if state == "args" then - if tokens[i] == "<" then - state = "input" - elseif tokens[i] == ">" then - state = "output" - elseif tokens[i] == ">>" then - state = "append" - else - table.insert(args, tokens[i]) - end - elseif state == "input" then - if tokens[i] == ">" then - if not input then - return nil, "parse error near '>'" - end - state = "output" - elseif tokens[i] == ">>" then - if not input then - return nil, "parse error near '>>'" - end - state = "append" - elseif not input then - input = tokens[i] - end - elseif state == "output" or state == "append" then - if not output then - output = tokens[i] - end + for i = 1, #tokens do + local token = tokens[i] + state = sm[state][token] + if not sm[state] then + return nil, state end end - return program, args, input, output, state + + return program, args, input, output, mode end local function parseCommands(command) @@ -146,6 +239,9 @@ local function parseCommands(command) for i = 1, #commands do commands[i] = table.pack(parseCommand(commands[i])) + if commands[i][1] == nil then + return nil, commands[i][2] + end end return commands