Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local isColor = term.isColor or function() end
- local tArgs = {...}
- local mode = tArgs[1]=="advanced" and 2 or 1
- local debugOn = false
- local log = false
- local debugFile = "/calc-debug"
- if log then file=fs.open(debugFile, "w") file.close() end
- local termX, termY = term.getSize()
- local function logText(...)
- text = {...}
- for i=1,#text do text[i] = tostring(text[i]) end
- text = table.concat(text)
- local file = fs.open(debugFile, "a")
- file.writeLine(text)
- file.close()
- end
- local function display(color, ...)
- if isColor() then term.setTextColor(colors[color]) end
- local args={...}
- for i=1,#args do
- args[i] = tostring(args[i])
- end
- local text = table.concat(args)
- if mode==2 then
- term.clearLine()
- print(...)
- if ({term.getCursorPos()})[2] == termY then
- term.scroll(1)
- term.setCursorPos(1, termY-1)
- term.write(string.rep("-", termX))
- term.setCursorPos(1, termY-1)
- end
- elseif mode==1 then
- term.setCursorPos(1,1)
- term.clearLine()
- if #text > termX then
- local newText = text:sub(#text-termX)
- else newText = text
- end
- term.write(newText)
- end
- if log then
- logText(text)
- end
- if isColor() then term.setTextColor(colors.black) end
- end
- if isColor() then term.setTextColor(colors.black) end
- if isColor() then term.setBackgroundColor(colors.lightGray) end
- local function debug(...) if debugOn then display("black", ...) else logText(...) end return ... end
- local defaultFile = [=[
- --[[
- This is the custom settings file. Custom operators
- functions, and constants can be added like this:
- Operator{
- symbol = "+",
- precedence = 32,
- func = function(a,b) return a+b end,
- associativity = "left"
- }
- The fields precedence and associativity are
- optional. Precedence is a positive number, and
- operators with higher precedences are evaluated
- first. Default for precedence is 1. Associativity
- may be "left" or "right, and determines the
- operands to evaluate when two operators have equal
- precedence. Default for associativity is "left".
- In addition, there is another optional field,
- variables. If it is set to true, variables will
- be passed to operator function as the the name,
- as a string, instead of the value. Unless you're
- doing something special, you shouldn't use this.
- If you do need to interact with variables, they
- are stored in the table vars, and may be accessed
- by vars[name].
- You may also add custom constants, like this:
- Constant{
- name = "thirteen",
- value = 13
- }
- Lastly, you can add custom functions:
- Function{
- name = "rand",
- arguments = 1,
- func = math.rand
- }
- Functions must have a specified number of
- arguments, and any other arguments passed to them
- will be ignored. The variables field also applies
- to functions.
- ]]
- --Content goes below here:
- ]=]
- local function tokenize(expression) -- Done
- debug("Tokenizing expression: " .. expression)
- expression = string.gsub(expression, "%s+", "")
- debug("Removed Whitespace\nNew expression: ".. expression)
- local tokens = {}
- local matched = false
- for token1, number, token2 in string.gmatch(expression, "([^%d]*)(%d*%.?%d+)([^%d]*)") do
- matched = true
- debug("Found match\nSeperating first set of tokens")
- while token1 ~= "" do
- local match, pos = string.match(token1, "^(%a+)()")
- if match then
- table.insert(tokens, match)
- token1 = token1:sub(pos)
- else
- table.insert(tokens ,token1:sub(1,1))
- token1 = token1:sub(2)
- end
- end
- table.insert(tokens, number)
- debug("Seperating second set of tokens")
- while token2 ~= "" do
- local match, pos = string.match(token2, "^(%a+)()")
- if match then
- table.insert(tokens, match)
- token2 = token2:sub(pos)
- else
- table.insert(tokens ,token2:sub(1,1))
- token2 = token2:sub(2)
- end
- end
- end
- if not matched then
- while expression ~= "" do
- local match, pos = string.match(expression, "^(%a+)()")
- if match then
- table.insert(tokens, match)
- expression = expression:sub(pos)
- else
- table.insert(tokens ,expression:sub(1,1))
- expression = expression:sub(2)
- end
- end
- end
- debug("Returning from tokenize")
- if tokens == {} then display("red", "Unable to parse expression") return else return tokens end
- end
- local functions = {
- sin = {func = math.sin, args = 1},
- cos = {func = math.cos, args = 1},
- tan = {func = math.tan, args = 1},
- random = {func = math.random, args = 0},
- abs = {func = math.abs, args = 1},
- acos = {func = math.acos, args = 1},
- asin = {func = math.asin, args = 1},
- atan = {func = math.atan, args = 1},
- atan2 = {func = math.atan2, args = 2},
- ceil = {func = math.ceil, args = 1},
- cosh = {func = math.cosh, args = 1},
- deg = {func = math.deg, args = 1},
- exp = {func = math.exp, args = 1},
- floor = {func = math.floor, args = 1},
- fmod = {func = math.fmod, args = 2},
- frexp = {func = math.frexp, args = 1},
- ldexp = {func = math.ldexp, args = 2},
- log = {func = math.log, args = 1},
- logten = {func = function(num) return math.log(num, 10) end, args = 1},
- logtwo = {func = function(num) return math.log(num, 2) end, args = 1},
- logbase = {func = math.log, args = 2},
- min = {func = math.min, args = 2},
- max = {func = math.max, args = 2},
- pow = {func = math.pow, args = 2},
- rad = {func = math.rad, args = 1},
- randomseed = {func = math.randomseed, args = 1},
- sinh = {func = math.sinh, args = 1},
- sqrt = {func = math.sqrt, args = 1},
- tanh = {func = math.tanh, args = 1},
- randomrange = {func = math.random, args = 2}
- }
- local constants = {
- pi = math.pi,
- e = math.exp(1),
- inf = math.huge
- }
- local operators = {
- ["+"] = {func = function(a,b) return a+b end, precedence = 32, leftAssociative = true, args = 2},
- ["-"] = {func = function(a,b) return a-b end, precedence = 32, leftAssociative = true, args = 2},
- ["*"] = {func = function(a,b) return a*b end, precedence = 64, leftAssociative = true, args = 2},
- ["/"] = {func = function(a,b) return a/b end, precedence = 64, leftAssociative = true, args = 2},
- ["^"] = {func = function(a,b) return a^b end, precedence = 96, leftAssociative = false, args = 2},
- ["%"] = {func = function(a,b) return a%b end, precedence = 64, leftAssociative = true, args = 2},
- ["="] = {func = function(a,b) debug("= recieved args: ",a," and ",b) if not tonumber(a) then vars[a] = vars[b] debug("Set variable ",a," to ",vars[b]) else debug("Did not set variable: ",a," to ",vars[b]) end return b end, precedence = 16, leftAssociative = false, args = 2, getVars = true}
- }
- setmetatable(functions, {__index = operators})
- vars = {}
- setmetatable(vars, {__index = function(_, value) return tonumber(value) end})
- local function shuntingYard(tokens) -- Done
- debug("Shunting yard input: ", table.concat(tokens, " "))
- local output = {}
- local stack = {}
- local x = #tokens
- while #tokens>0 do
- if x>0 then debug("Tokens left: ",#tokens) x=x-1 end
- debug("Stack: ",table.concat(stack, " "))
- local token = table.remove(tokens, 1)
- if tonumber(token) and token~="-" then debug("Token is a number: ",token)
- table.insert(output, tonumber(token))
- elseif operators[token] then debug("Token is an operator: ",token)
- while operators[stack[#stack]] and ((operators[token].leftAssociative and operators[token].precedence <= operators[stack[#stack]].precedence) or operators[token].precedence < operators[stack[#stack]].precedence) do
- table.insert(output, table.remove(stack))
- end
- table.insert(stack, token)
- elseif functions[token] then debug("Token is a function: ",token)
- table.insert(stack, token)
- elseif token == "," then debug("Token is a comma")
- local leftParen = false
- while #stack > 0 do
- if stack[#stack] == "(" then
- leftParen = true
- break
- end
- table.insert(output, table.remove(stack))
- end
- if not leftParen then
- display("red", "Error: misplaced comma or parentheses")
- return false
- end
- elseif token == "(" then debug("Token is a left parentheses")
- table.insert(stack, token)
- elseif token == ")" then debug("Token is a right parentheses")
- local leftParen = false
- while #stack > 0 do
- if stack[#stack] == "(" then
- table.remove(stack)
- leftParen = true
- if functions[stack[#stack]] then
- table.insert(output, table.remove(stack))
- end
- break
- else
- table.insert(output, table.remove(stack))
- end
- end
- if not leftParen then
- display("red", "Error: mismatched parentheses")
- return false
- end
- elseif constants[token] then debug("Token is a constant: ",constants[token])
- table.insert(output, constants[token])
- elseif vars[token] ~= token then debug("Token is a variable: ",token)
- table.insert(output, "var:"..token)
- elseif token == string.match(token, "%a+") then debug("Token is a possible variable: ",token)
- table.insert(output, "var:"..token)
- else
- display("red", "Error: unknown symbol or identifier")
- return false
- end
- end
- x=#stack
- debug("Items in stack = ",x)
- while #stack > 0 do
- if x>0 then debug("Token in stack: ",stack[#stack]) x=x-1 end
- if stack[#stack] == "(" or stack[#stack] == ")" then
- display("red", "Error: mismatched parentheses")
- return false
- end
- table.insert(output, table.remove(stack))
- end
- debug("Shunting yard output: ", table.concat(output, " "))
- return output
- end
- local function operator(op)
- if type(op) ~= "table" then return end
- if type(op.symbol) == "string" and #op.symbol==1 and not string.match(op.symbol, "%w") and type(op.func == "function") then
- if type(op.precedence)~="number" or op.precedence<=0 then op.precedence = 1 end
- if op.associativity ~= "left" and op.associativity ~= "right" then op.associativity = "left" end
- operators[op.symbol] = {func = op.func, precedence = op.precedence, leftAssociative = op.associativity == "left", args = 2}
- if op.variables then operators[op.symbol].getVars = true end
- debug("Added operator: ",op.symbol)
- end
- end
- local function constant(con)
- if type(con) ~= "table" then return end
- if type(con.name) == "string" and type(con.value) == "number" then
- constants[name] = value
- end
- end
- local function func(fun)
- if type(fun) ~= "table" then return end
- if type(fun.name) == "string" and string.match(fun.name, "%a+") == fun.name and type(fun.arguments) == "number" and fun.arguments >= 0 and type(fun.func) == "function" then
- functions[fun.name] = {func = fun.func, args = fun.arguments}
- if fun.variables then functions[fun.name].getVars = true end
- end
- end
- local function loadCustom() -- Done
- display("black", "Loading custom settings")
- if not fs.exists("Programs/calculator/custom") then
- if not pcall(fs.makeDir, "Programs/calculator") then
- display("red", "Unable to create directory /Programs/calculator")
- return false
- end
- local file = fs.open("Programs/calculator/custom", "w")
- file.write(defaultFile)
- file.close()
- display("black", "Custom settings file created at /Programs/calculator/custom")
- end
- customFile = loadfile("Programs/calculator/custom")
- if not customFile then display("red", "Error: failed to load custom settings")
- return false
- end
- setfenv(customFile, {Operator = operator, Constant = constant, Function = func})
- ok = pcall(customFile)
- if not ok then display("red", "Error: failed to load custom settings")
- return false
- else display("black", "Successfully loaded custom settings")
- end
- end
- local function evaluate(expression) -- Done
- local stack = {}
- local x=#expression
- while #expression > 0 do
- if x>0 then debug("Evaluating for token: ",expression[#expression]) x=x-1 end
- table.insert(stack, table.remove(expression, 1))
- if functions[stack[#stack]] then
- debug("Applying function: ",stack[#stack]," to stack: ",table.concat(stack," "))
- local func = functions[table.remove(stack)]
- if #stack<func.args then display("red", "Error: Invalid expression") return false end
- local operands = {}
- local newSize = #stack-func.args
- while #stack > newSize do
- table.insert(operands, table.remove(stack, newSize+1))
- end
- for index,operand in ipairs(operands) do
- if string.sub(operand, 1, 4) == "var:" then
- if not operand:sub(5):match("%a+") then display("red", "Error: Invalid expression") return false end
- if func.getVars then operands[index] = string.sub(operand, 5) else operands[index] = vars[string.sub(operand, 5)] end
- end
- if not operands[index] then display("red", "Variable ", string.sub(operand, 5), " does not exist") return false end
- end
- if #operands ~= func.args then display("red", "An unknown error occured while evaluating") return false end
- table.insert(stack, func.func(unpack(operands)))
- elseif type(stack[#stack])~="number" and string.sub(stack[#stack], 1, 4) ~= "var:" then
- display("red", "An unknown token was encountered while evaluating")
- return false
- end
- end
- if #stack ~= 1 then
- display("red", "Error: Invalid expression")
- return false
- end
- local result = stack[1] debug("Result is ",result)
- if string.sub(result, 1, 4)=="var:" then if not result:sub(5):match("%a+") then display("red", "Error: Invalid expression") return false end debug("Result is variable: ",result) result = vars[string.sub(result,5)] debug("New result = ",result) end
- if not result then result = "That variable is not defined" end
- return result
- end
- local function fixArgs(expression) -- Done
- --[[local failed = false
- expression = string.gsub(expression, "(%a+)%s*(%b%(%))",
- function(func, args) debug("fixArgs found function: ", func.."("..table.concat(argList, ",")..")")
- if not functions[func] then failed = true display("red", "No such function: ", func) return end
- local args = functions[func].args
- local argList = {}
- for arg in string.gmatch(args, "[^%(%),]+") do table.insert(argList, arg) end
- while argList[args+1] do
- table.remove(argList, args+1)
- end
- if #argList < args then failed = true display("red", "Function ", func, " requires ", args, " arguments.") return end
- return func.."("..table.concat(argList, ",")..")"
- end)
- if failed then return false else debug("fixArgs produced " .. expression) return expression end]]
- --[[local function findIn(collection, item)
- local postitons = {}
- for i=1,#collection do
- if collection[i] == item then table.insert(positions, i) end
- end
- end
- local wordPos = {}
- for i=1,#expression do
- if expression[i] = string.match(expression[i], "%a+") then table.wordPos[i] = true end
- end
- local leftParen = findIn(expression, "(")
- local rightParen = findIn(expression, ")")
- if #leftParen ~= #rightParen then display("red", "Error: mismatched parentheses") return false end
- local
- for ]]
- return expression
- end
- local function fixNegative(expression)
- local currentPos = 1
- while true do
- local startPos, endPos = string.find(expression, "%-%d+", currentPos)
- if not startPos then break end
- currentPos = endPos+1
- if not string.match(string.sub(expression, startPos-1, startPos-1), "[%a%d%)]") then
- expression = string.sub(expression, 1, startPos-1).."0"..string.sub(expression, startPos)
- end
- end
- debug("fixNegative produced " .. expression)
- return expression
- end
- local function command(expression) -- Done
- local cmd, param = string.match(expression, "(%a+)%s?(%a*)")
- local commands = {
- delete = function(var) if var == "" or var == " " then display("red", "No variable specifed to delete") return "delete" end if vars[var] then vars[var] = nil display("black", "Deleted variable: ",var) else display("red", var," is not a variable") end return "delete" end,
- vars = function() local found = false for var,value in pairs(vars) do found = true display("black", var,"=",value) end if not found then display("black", "There are no variables") end return "vars" end,
- ["exit"] = function(e) if e == "" then return "exit" end end,
- get = function(param) if functions[param] then display("black", "Function") elseif constants[param] then display("black", "Constant: ", constants[param]) elseif vars[param] then display("black", "Variable: ", vars[param]) else display("black", "Not a function, constant, or variable") end return "get" end,
- buttons = function() mode = 1 return "exit" end,
- --debug = function(on) if on=="on" then debugOn=true display("black", "Debug Enabled") elseif on=="off" then debugOn=false display("black", "Debug Disabled") end return "debug" end,
- --log = function(load) if load == "reload" then reload = true end shell.run("edit", debugFile) return "exit" end,
- --reload = function() reload = true return "exit" end,
- }
- commands.commands = function() local cmds = {} for key in pairs(commands) do table.insert(cmds, key) end local list = "Commands: "..table.concat(cmds, ", ") display("black", list) return "commands" end
- if commands[cmd] then return commands[cmd](param) end
- end
- local function textUI() -- Done
- local history = {}
- term.clear()
- if ({term.getCursorPos()})[2] == termY then term.scroll(1) end
- term.setCursorPos(1, termY-1)
- term.write(string.rep("-", termX))
- term.setCursorPos(1, termY-1)
- term.setCursorPos(1, termY)
- loadCustom()
- local running = true
- term.setCursorBlink(true)
- while running do
- while running do
- if ({term.getCursorPos()})[2] == termY then term.scroll(1) end
- term.setCursorPos(1, termY-1)
- term.write(string.rep("-", termX))
- term.setCursorPos(1, termY-1)
- term.setCursorPos(1, termY)
- write("Input: ")
- local expression = read(nil, history)
- term.setCursorPos(1, termY-1)
- term.scroll(1)
- term.clearLine()
- table.insert(history, expression)
- local cmd = command(expression)
- if cmd then if cmd == "exit" then running = false end break end
- expression = fixNegative(expression)
- if not expression then break end
- expression = fixArgs(expression)
- if not expression then break end
- expression = tokenize(expression)
- if not expression then break end
- expression = shuntingYard(expression)
- if not expression then break end
- expression = evaluate(expression)
- if not expression then break end
- display("black", "Result: ", expression)
- end
- end
- term.setCursorPos(1, termY)
- end
- local function GUI()
- if termY < 18 or termX < 23 then print("Error: Terminal is too small") return end
- local running = true
- term.setCursorBlink(false)
- term.clear()
- local row, column = 1, 1
- local rowMax, columnMax = 5, 4
- local symbols = {
- {"<--", " ( ", " ) ", "adv"},
- {" 7 ", " 8 ", " 9 ", " / "},
- {" 4 ", " 5 ", " 6 ", " * "},
- {" 1 ", " 2 ", " 3 ", " - "},
- {" 0 ", " . ", " = ", " + "}
- }
- local function buttonFunctions(rowNum, columnNum)
- if not rowNum or not columnNum or rowNum < 1 or rowNum > 5 or columnNum < 1 or columnNum > 4 then return function() end end
- if rowNum == 1 and columnNum == 1 then return function(e) return string.sub(e, 1, #e-1) end
- elseif rowNum == 1 and columnNum == 4 then return function() running = false mode = 2 return "" end
- elseif rowNum == 5 and columnNum == 3 then return function(expression)
- term.setCursorPos(1,1) term.clearLine()
- for _=1,1 do
- expression = fixNegative(expression)
- if not expression then break end
- expression = fixArgs(expression)
- if not expression then break end
- expression = tokenize(expression)
- if not expression then break end
- expression = shuntingYard(expression)
- if not expression then break end
- expression = evaluate(expression)
- if not expression then break end
- display("black", "Result: ",expression)
- end
- sleep(2)--while true do local e,p=os.pullEvent("key") if p~=1 then break end end
- return ""
- end
- else return function(e) return e..symbols[rowNum][columnNum]:sub(2,2) end end
- end
- local yPos = {4, 7, 10, 13, 16}
- local xPos = {1, 7, 13, 19}
- for i=1,4 do xPos[i] = xPos[i] + math.floor((termX-23)/2) end
- xPosReverse = {}
- local yPosReverse = {}
- for value,key in ipairs(yPos) do yPosReverse[key] = value end
- for value,key in ipairs(xPos) do xPosReverse[key] = value end
- local function button(y, x, symbol, highlight)
- local border = highlight and "=====" or "-----"
- term.setCursorPos(x, y)
- term.write(border)
- term.setCursorPos(x, y+1)
- term.write("|"..symbol.."|")
- term.setCursorPos(x, y+2)
- term.write(border)
- end
- local function getButton(x, y)
- debug("getButton called with: ",x,", ",y)
- x, y = x - (math.floor((termX-23)/2)+1), y - 4
- x, y = x%6 ~=5 and (x-(x%6))+math.floor((termX-23)/2)+1 or nil, (y-(y%3))+4
- x, y = xPosReverse[x], yPosReverse[y]
- debug("getButton returned: ",x or "nil",", ",y or "nil")
- return y, x
- end
- local allowedChars = {["+"]= true, ["-"] = true, ["*"] = true, ["/"] = true, ["."] = true}
- for char=0,9 do allowedChars[tostring(char)] = true end
- for row=1,rowMax do
- for column=1,columnMax do
- button(yPos[row], xPos[column], symbols[row][column], (row == 1 and column == 1) and true)
- end
- end
- local expression = "Calculator+. Press CTRL to exit."
- local msg = true
- local timer = os.startTimer(2)
- term.setCursorPos(1, 2) term.clearLine() term.write(string.rep("-", termX))
- while running do
- while running do
- display("black", expression)
- local e,p,p2,p3 = os.pullEvent()
- if e == "timer" and p==timer then
- if msg then msg = false expression = "" end
- elseif e == "key" then
- if msg then msg = false expression = "" end
- if p == keys.enter then
- expression = buttonFunctions(row, column)(expression)
- elseif p == keys.up and row > 1 then
- button(yPos[row], xPos[column], symbols[row][column], false)
- row = row - 1
- button(yPos[row], xPos[column], symbols[row][column], true)
- elseif p == keys.down and row < rowMax then
- button(yPos[row], xPos[column], symbols[row][column], false)
- row = row + 1
- button(yPos[row], xPos[column], symbols[row][column], true)
- elseif p == keys.left and column > 1 then
- button(yPos[row], xPos[column], symbols[row][column], false)
- column = column - 1
- button(yPos[row], xPos[column], symbols[row][column], true)
- elseif p == keys.right and column < columnMax then
- button(yPos[row], xPos[column], symbols[row][column], false)
- column = column + 1
- button(yPos[row], xPos[column], symbols[row][column], true)
- elseif p == keys.backspace then
- expression = string.sub(expression, 1, #expression-1)
- elseif p == keys.leftCtrl or p == keys.rightCtrl then
- display("black", "Thank you for using Calculator+")
- running = false
- sleep(2)
- end
- elseif e == "char" then
- if msg then msg = false expression = "" end
- if allowedChars[p] then expression = expression..p
- elseif p == "=" then expression = buttonFunctions(5,3)(expression) end
- elseif e == "click" then
- if msg then msg = false expression = "" end
- expression = buttonFunctions(getButton(p, p2))(expression) or expression
- end
- end
- end
- term.clear()
- term.setCursorPos(1,1)
- end
- local function main()
- local currentMode
- local modes = {GUI, textUI}
- repeat
- currentMode = mode
- modes[mode]()
- until currentMode == mode
- end
- main()
- if isColor() then term.setBackgroundColor(colors.black) end
- term.clear()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement