Advertisement
faubiguy

Calc

Oct 9th, 2012
173
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.99 KB | None | 0 0
  1. local tArgs = {...}
  2. local debugOn = false
  3. local mode = tArgs[1]=="buttons" and 1 or 2
  4. --debugFile = "/calc-debug"
  5. --file=fs.open(debugFile, "w") file.close()
  6. local termX, termY = term.getSize()
  7. local function display(...) 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 local args={...} for i=1,#args do args[i] = tostring(args[i]) end term.setCursorPos(1,1) term.clearLine() local text = table.concat(args) text = text:sub(#text-termX) term.write(text) end if debugOn then local args = {...} local textArgs = {} for i=1,#args do textArgs[i] = tostring(args[i]) end local text = table.concat(textArgs) local file = fs.open(debugFile, "a") file.writeLine(text) file.close() end end
  8. local function debug(...) if debugOn then display(...) end return ... end
  9.  
  10. local defaultFile = [=[
  11. --[[
  12. This is the custom settings file. Custom operators
  13. functions, and constants can be added like this:
  14.  
  15. Operator{
  16.   symbol = "+",
  17.   precedence = 32,
  18.   func = function(a,b) return a+b end,
  19.   associativity = "left"
  20. }
  21.  
  22. The fields precedence and associativity are
  23. optional. Precedence is a positive number, and
  24. operators with higher precedences are evaluated
  25. first. Default for precedence is 1. Associativity
  26. may be "left" or "right, and determines the
  27. operands to evaluate when two operators have equal
  28. precedence. Default for associativity is "left".
  29. In addition, there is another optional field,
  30. variables. If it is set to true, variables will
  31. be passed to operator function as the the name,
  32. as a string, instead of the value. Unless you're
  33. doing something special, you shouldn't use this.
  34. If you do need to interact with variables, they
  35. are stored in the table vars, and may be accessed
  36. by vars[name].
  37.  
  38. You may also add custom constants, like this:
  39.  
  40. Constant{
  41.   name = "thirteen",
  42.   value = 13
  43. }
  44.  
  45. Lastly, you can add custom functions:
  46.  
  47. Function{
  48.   name = "rand",
  49.   arguments = 1,
  50.   func = math.rand
  51. }
  52.  
  53. Functions must have a specified number of
  54. arguments, and any other arguments passed to them
  55. will be ignored. The variables field also applies
  56. to functions.
  57. ]]
  58.  
  59. --Content goes below here:
  60. ]=]
  61.  
  62. local function tokenize(expression) -- Done
  63.  debug("Tokenizing expression: " .. expression)
  64.  expression = string.gsub(expression, "%s+", "")
  65.  debug("Removed Whitespace\nNew expression: ".. expression)
  66.  local tokens = {}
  67.  local matched = false
  68.  for token1, number, token2 in string.gmatch(expression, "([^%d]*)(%d*%.?%d+)([^%d]*)") do
  69.   matched = true
  70.   debug("Found match\nSeperating first set of tokens")
  71.   while token1 ~= "" do
  72.    local match, pos = string.match(token1, "^(%a+)()")
  73.    if match then
  74.     table.insert(tokens, match)
  75.     token1 = token1:sub(pos)
  76.    else
  77.     table.insert(tokens ,token1:sub(1,1))
  78.     token1 = token1:sub(2)
  79.    end
  80.   end
  81.   table.insert(tokens, number)
  82.   debug("Seperating second set of tokens")
  83.   while token2 ~= "" do
  84.    local match, pos = string.match(token2, "^(%a+)()")
  85.    if match then
  86.     table.insert(tokens, match)
  87.     token2 = token2:sub(pos)
  88.    else
  89.     table.insert(tokens ,token2:sub(1,1))
  90.     token2 = token2:sub(2)
  91.    end
  92.   end
  93.  end
  94.  if not matched then
  95.   while expression ~= "" do
  96.    local match, pos = string.match(expression, "^(%a+)()")
  97.    if match then
  98.     table.insert(tokens, match)
  99.     expression = expression:sub(pos)
  100.    else
  101.     table.insert(tokens ,expression:sub(1,1))
  102.     expression = expression:sub(2)
  103.    end
  104.   end
  105.  end
  106.  debug("Returning from tokenize")
  107.  if tokens == {} then display("Unable to parse expression") return else return tokens end
  108. end
  109.  
  110. local functions = {
  111. sin = {func = math.sin, args = 1},
  112. cos = {func = math.cos, args = 1},
  113. tan = {func = math.tan, args = 1},
  114. random = {func = math.random, args = 0},
  115. abs = {func = math.abs, args = 1},
  116. acos = {func = math.acos, args = 1},
  117. asin = {func = math.asin, args = 1},
  118. atan = {func = math.atan, args = 1},
  119. atan2 = {func = math.atan2, args = 2},
  120. ceil = {func = math.ceil, args = 1},
  121. cosh = {func = math.cosh, args = 1},
  122. deg = {func = math.deg, args = 1},
  123. exp = {func = math.exp, args = 1},
  124. floor = {func = math.floor, args = 1},
  125. fmod = {func = math.fmod, args = 2},
  126. frexp = {func = math.frexp, args = 1},
  127. ldexp = {func = math.ldexp, args = 2},
  128. log = {func = math.log, args = 1},
  129. logten = {func = function(num) return math.log(num, 10) end, args = 1},
  130. logtwo = {func = function(num) return math.log(num, 2) end, args = 1},
  131. logbase = {func = math.log, args = 2},
  132. min = {func = math.min, args = 2},
  133. max = {func = math.max, args = 2},
  134. pow = {func = math.pow, args = 2},
  135. rad = {func = math.rad, args = 1},
  136. randomseed = {func = math.randomseed, args = 1},
  137. sinh = {func = math.sinh, args = 1},
  138. sqrt = {func = math.sqrt, args = 1},
  139. tanh = {func = math.tanh, args = 1},
  140. randomrange = {func = math.random, args = 2}
  141. }
  142.  
  143. local constants = {
  144. pi = math.pi,
  145. e = math.exp(1),
  146. inf = math.huge
  147. }
  148.  
  149. local operators = {
  150. ["+"] = {func = function(a,b) return a+b end, precedence = 32, leftAssociative = true, args = 2},
  151. ["-"] = {func = function(a,b) return a-b end, precedence = 32, leftAssociative = true, args = 2},
  152. ["*"] = {func = function(a,b) return a*b end, precedence = 64, leftAssociative = true, args = 2},
  153. ["/"] = {func = function(a,b) return a/b end, precedence = 64, leftAssociative = true, args = 2},
  154. ["^"] = {func = function(a,b) return a^b end, precedence = 96, leftAssociative = false, args = 2},
  155. ["%"] = {func = function(a,b) return a%b end, precedence = 64, leftAssociative = true, args = 2},
  156. ["="] = {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}
  157. }
  158.  
  159. setmetatable(functions, {__index = operators})
  160.  
  161. vars = {}
  162. setmetatable(vars, {__index = function(_, value) return tonumber(value) end})
  163.  
  164. local function shuntingYard(tokens) -- Done
  165.  debug("Shunting yard input: ", table.concat(tokens, " "))
  166.  local output = {}
  167.  local stack = {}
  168.  local x = #tokens
  169.  while #tokens>0 do
  170.   if x>0 then debug("Tokens left: ",#tokens) x=x-1 end
  171.   debug("Stack: ",table.concat(stack, " "))
  172.   local token = table.remove(tokens, 1)
  173.   if tonumber(token) and token~="-" then debug("Token is a number: ",token)
  174.    table.insert(output, tonumber(token))
  175.   elseif operators[token] then debug("Token is an operator: ",token)
  176.    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
  177.     table.insert(output, table.remove(stack))
  178.    end
  179.    table.insert(stack, token)
  180.   elseif functions[token] then debug("Token is a function: ",token)
  181.    table.insert(stack, token)
  182.   elseif token == "," then debug("Token is a comma")
  183.    local leftParen = false
  184.    while #stack > 0 do
  185.     if stack[#stack] == "(" then
  186.      leftParen = true
  187.      break
  188.     end
  189.     table.insert(output, table.remove(stack))
  190.    end
  191.    if not leftParen then
  192.     display("Error: misplaced comma or parentheses")
  193.     return false
  194.    end
  195.   elseif token == "(" then debug("Token is a left parentheses")
  196.    table.insert(stack, token)
  197.   elseif token == ")" then debug("Token is a right parentheses")
  198.    local leftParen = false
  199.    while #stack > 0 do
  200.     if stack[#stack] == "(" then
  201.      table.remove(stack)
  202.      leftParen = true
  203.      if functions[stack[#stack]] then
  204.       table.insert(output, table.remove(stack))
  205.      end
  206.      break
  207.     else
  208.      table.insert(output, table.remove(stack))
  209.     end
  210.    end
  211.    if not leftParen then
  212.     display("Error: mismatched parentheses")
  213.     return false
  214.    end
  215.   elseif constants[token] then debug("Token is a constant: ",constants[token])
  216.    table.insert(output, constants[token])
  217.   elseif vars[token] ~= token then debug("Token is a variable: ",token)
  218.    table.insert(output, "var:"..token)
  219.   elseif token == string.match(token, "%a+") then debug("Token is a possible variable: ",token)
  220.    table.insert(output, "var:"..token)
  221.   else
  222.    display("Error: unknown symbol or identifier")
  223.    return false
  224.   end
  225.  end
  226.  x=#stack
  227.  debug("Items in stack = ",x)
  228.  while #stack > 0 do
  229.   if x>0 then debug("Token in stack: ",stack[#stack]) x=x-1 end
  230.   if stack[#stack] == "(" or stack[#stack] == ")" then
  231.    display("Error: mismatched parentheses")
  232.    return false
  233.   end
  234.   table.insert(output, table.remove(stack))
  235.  end
  236.  debug("Shunting yard output: ", table.concat(output, " "))
  237.  return output
  238. end
  239.    
  240. local function operator(op)
  241.  if type(op) ~= "table" then return end
  242.  if type(op.symbol) == "string" and #op.symbol==1 and not string.match(op.symbol, "%w") and type(op.func == "function") then
  243.   if type(op.precedence)~="number" or op.precedence<=0 then op.precedence = 1 end
  244.   if op.associativity ~= "left" and op.associativity ~= "right" then op.associativity = "left" end
  245.   operators[op.symbol] = {func = op.func, precedence = op.precedence, leftAssociative = op.associativity == "left", args = 2}
  246.   if op.variables then operators[op.symbol].getVars = true end
  247.   debug("Added operator: ",op.symbol)
  248.  end  
  249. end
  250.  
  251. local function constant(con)
  252.  if type(con) ~= "table" then return end
  253.  if type(con.name) == "string" and type(con.value) == "number" then
  254.   constants[name] = value
  255.  end
  256. end
  257.  
  258. local function func(fun)
  259.  if type(fun) ~= "table" then return end
  260.  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
  261.   functions[fun.name] = {func = fun.func, args = fun.arguments}
  262.   if fun.variables then functions[fun.name].getVars = true end
  263.  end
  264. end
  265.  
  266. local function loadCustom() -- Done
  267.  display("Loading custom settings")
  268.  if not fs.exists("Programs/calculator/custom") then
  269.   if not pcall(fs.makeDir, "Programs/calculator") then
  270.    display("Unable to create directory /Programs/calculator")
  271.    return false
  272.   end
  273.   local file = fs.open("Programs/calculator/custom", "w")
  274.   file.write(defaultFile)
  275.   file.close()
  276.   display("Custom settings file created at /Programs/calculator/custom")
  277.  end
  278.  customFile = loadfile("Programs/calculator/custom")
  279.  if not customFile then display("Error: failed to load custom settings")
  280.   return false
  281.  end
  282.  setfenv(customFile, {Operator = operator, Constant = constant, Function = func})
  283.  ok = pcall(customFile)
  284.  if not ok then display("Error: failed to load custom settings")
  285.   return false
  286.  else display("Successfully loaded custom settings")
  287.  end
  288. end
  289.  
  290. local function evaluate(expression) -- Done
  291.  local stack = {}
  292.  local x=#expression
  293.  while #expression > 0 do
  294.   if x>0 then debug("Evaluating for token: ",expression[#expression]) x=x-1 end
  295.   table.insert(stack, table.remove(expression, 1))
  296.   if functions[stack[#stack]] then
  297.    debug("Applying function: ",stack[#stack]," to stack: ",table.concat(stack," "))
  298.    local func = functions[table.remove(stack)]
  299.    if #stack<func.args then display("Error: Invalid expression") return false end
  300.    local operands = {}
  301.    local newSize = #stack-func.args
  302.    while #stack > newSize do
  303.     table.insert(operands, table.remove(stack, newSize+1))
  304.    end
  305.    for index,operand in ipairs(operands) do
  306.     if string.sub(operand, 1, 4) == "var:" then
  307.      if not operand:sub(5):match("%a+") then display("Error: Invalid expression") return false end
  308.      if func.getVars then operands[index] = string.sub(operand, 5) else operands[index] = vars[string.sub(operand, 5)] end
  309.     end
  310.     if not operands[index] then display("Variable ", string.sub(operand, 5), " does not exist") return false end
  311.    end
  312.    if #operands ~= func.args then display("An unknown error occured while evaluating") return false end
  313.    table.insert(stack, func.func(unpack(operands)))
  314.   elseif type(stack[#stack])~="number" and string.sub(stack[#stack], 1, 4) ~= "var:" then
  315.    display("An unknown token was encountered while evaluating")
  316.    return false
  317.   end
  318.  end
  319.  if #stack ~= 1 then
  320.   display("Error: Invalid expression")
  321.   return false
  322.  end
  323.  local result = stack[1] debug("Result is ",result)
  324.  if string.sub(result, 1, 4)=="var:" then if not result:sub(5):match("%a+") then display("Error: Invalid expression") return false end debug("Result is variable: ",result) result = vars[string.sub(result,5)] debug("New result = ",result) end
  325.  if not result then result = "That variable is not defined" end
  326.  return result
  327. end
  328.  
  329. local function fixArgs(expression)
  330.  return expression
  331. end
  332.  
  333. local function fixNegative(expression)
  334.  local currentPos = 1
  335.  while true do
  336.   local startPos, endPos = string.find(expression, "%-%d+", currentPos)
  337.   if not startPos then break end
  338.   currentPos = endPos+1
  339.   if not string.match(string.sub(expression, startPos-1, startPos-1), "[%a%d%)]") then
  340.    expression = string.sub(expression, 1, startPos-1).."0"..string.sub(expression, startPos)
  341.   end
  342.  end
  343.  debug("fixNegative produced " .. expression)
  344.  return expression
  345. end
  346.  
  347. local function command(expression)
  348.  local cmd, param = string.match(expression, "(%a+)%s?(%a*)")
  349.  local commands = {
  350.   delete = function(var) if var == "" or var == " " then display("No variable specifed to delete") return "delete" end if vars[var] then vars[var] = nil display("Deleted variable: ",var) else display(var," is not a variable") end return "delete" end,
  351.   vars = function() local found = false for var,value in pairs(vars) do found = true display(var,"=",value) end if not found then display("There are no variables") end return "vars" end,
  352.   ["exit"] = function(e) if e == "" then return "exit" end end,
  353.   get = function(param) if functions[param] then display("Function") elseif constants[param] then display("Constant: ", constants[param]) elseif vars[param] then display("Variable: ", vars[param]) else display("Not a function, constant, or variable") end return "get" end,
  354.   buttons = function() mode = 1 return "exit" end,
  355.   --debug = function(on) if on=="on" then debugOn=true display("Debug Enabled") elseif on=="off" then debugOn=false display("Debug Disabled") end return "debug" end,
  356.   --log = function(load) if load == "reload" then reload = true end shell.run("edit", debugFile) return "exit" end,
  357.   --reload = function() reload = true return "exit" end,
  358.  }
  359.  commands.commands = function() local cmds = {} for key in pairs(commands) do table.insert(cmds, key) end local list = "Commands: "..table.concat(cmds, ", ") display(list) return "commands" end
  360.  if commands[cmd] then return commands[cmd](param) end
  361. end
  362.  
  363. local function textUI()
  364.  local history = {}
  365.  term.clear()
  366.  if ({term.getCursorPos()})[2] == termY then term.scroll(1) end
  367.  term.setCursorPos(1, termY-1)
  368.  term.write(string.rep("-", termX))
  369.  term.setCursorPos(1, termY-1)
  370.  term.setCursorPos(1, termY)
  371.  loadCustom()
  372.  local running = true
  373.  term.setCursorBlink(true)
  374.  while running do
  375.  while running do
  376.   if ({term.getCursorPos()})[2] == termY then term.scroll(1) end
  377.   term.setCursorPos(1, termY-1)
  378.   term.write(string.rep("-", termX))
  379.   term.setCursorPos(1, termY-1)
  380.   term.setCursorPos(1, termY)
  381.   write("Input: ")
  382.   local expression = read(nil, history)
  383.   term.setCursorPos(1, termY-1)
  384.   term.scroll(1)
  385.   term.clearLine()
  386.   table.insert(history, expression)
  387.   local cmd = command(expression)
  388.   if cmd then if cmd == "exit" then running = false end break end
  389.   expression = fixNegative(expression)
  390.   if not expression then break end
  391.   expression = fixArgs(expression)
  392.   if not expression then break end
  393.   expression = tokenize(expression)
  394.   if not expression then break end
  395.   expression = shuntingYard(expression)
  396.   if not expression then break end
  397.   expression = evaluate(expression)
  398.   if not expression then break end
  399.   display("Result: ", expression)
  400.  end
  401.  end
  402.  term.setCursorPos(1, termY)
  403. end
  404.  
  405. local function GUI()
  406.  if termY < 18 or termX < 23 then print("Error: Terminal is too small") return end
  407.  local running = true
  408.  term.setCursorBlink(false)
  409.  term.clear()
  410.  local row, column = 1, 1
  411.  local rowMax, columnMax = 5, 4
  412.  local symbols = {
  413.   {"<--", " ( ", " ) ", "adv"},
  414.   {" 7 ", " 8 ", " 9 ", " / "},
  415.   {" 4 ", " 5 ", " 6 ", " * "},
  416.   {" 1 ", " 2 ", " 3 ", " - "},
  417.   {" 0 ", " . ", " = ", " + "}
  418.  }
  419.  local function buttonFunctions(rowNum, columnNum)
  420.   if rowNum == 1 and columnNum == 1 then return function(e) return string.sub(e, 1, #e-1) end
  421.   elseif rowNum == 1 and columnNum == 4 then return function() running = false mode = 2 return "" end
  422.   elseif rowNum == 5 and columnNum == 3 then return function(expression)
  423.    term.setCursorPos(1,1) term.clearLine()
  424.    for _=1,1 do
  425.     expression = fixNegative(expression)
  426.     if not expression then break end
  427.     expression = fixArgs(expression)
  428.     if not expression then break end
  429.     expression = tokenize(expression)
  430.     if not expression then break end
  431.     expression = shuntingYard(expression)
  432.     if not expression then break end
  433.     expression = evaluate(expression)
  434.     if not expression then break end
  435.     display("Result: ",expression)
  436.    end
  437.    sleep(2)--while true do local e,p=os.pullEvent("key") if p~=1 then break end end
  438.    return ""
  439.   end
  440.   else return function(e) return e..symbols[rowNum][columnNum]:sub(2,2) end end
  441.  end
  442.  local yPos = {4, 7, 10, 13, 16}
  443.  local xPos = {1, 7, 13, 19}
  444.  for i=1,4 do xPos[i] = xPos[i] + math.floor((termX-23)/2) end
  445.  local function button(y, x, symbol, highlight)
  446.   local border = highlight and "=====" or "-----"
  447.   term.setCursorPos(x, y)
  448.   term.write(border)
  449.   term.setCursorPos(x, y+1)
  450.   term.write("|"..symbol.."|")
  451.   term.setCursorPos(x, y+2)
  452.   term.write(border)
  453.  end
  454.  for row=1,rowMax do
  455.  for column=1,columnMax do
  456.   button(yPos[row], xPos[column], symbols[row][column], (row == 1 and column == 1) and true)
  457.  end
  458.  end
  459.  local expression = ""
  460.  term.setCursorPos(1, 2) term.clearLine() term.write(string.rep("-", termX))
  461.  while running do
  462.  while running do
  463.   local e,p = os.pullEvent("key")
  464.   if p == 28 then
  465.    expression = buttonFunctions(row, column)(expression)
  466.    display(expression)
  467.   elseif p == 200 and row > 1 then
  468.    button(yPos[row], xPos[column], symbols[row][column], false)
  469.    row = row - 1
  470.    button(yPos[row], xPos[column], symbols[row][column], true)
  471.   elseif p == 208 and row < rowMax then
  472.    button(yPos[row], xPos[column], symbols[row][column], false)
  473.    row = row + 1
  474.    button(yPos[row], xPos[column], symbols[row][column], true)
  475.   elseif p == 203 and column > 1 then
  476.    button(yPos[row], xPos[column], symbols[row][column], false)
  477.    column = column - 1
  478.    button(yPos[row], xPos[column], symbols[row][column], true)
  479.   elseif p == 205 and column < columnMax then
  480.    button(yPos[row], xPos[column], symbols[row][column], false)
  481.    column = column + 1
  482.    button(yPos[row], xPos[column], symbols[row][column], true)
  483.   end
  484.  end
  485.  end
  486.  term.clear()
  487.  term.setCursorPos(1,1)
  488. end
  489.  
  490. local function main()
  491.  local currentMode
  492.  local modes = {GUI, textUI}
  493.  repeat
  494.   currentMode = mode
  495.   modes[mode]()
  496.  until currentMode == mode
  497. end
  498.  
  499. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement