1m1m0

Math.com() TIERS-based Calculator

Apr 11th, 2025 (edited)
446
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.38 KB | Software | 0 0
  1. -- MathParserModule.lua
  2.  
  3. local Math = {}
  4.  
  5. ------------------------------------
  6. -- Supported functions and constants
  7. ------------------------------------
  8. local mathFunctions = {
  9.     sin   = math.sin,
  10.     cos   = math.cos,
  11.     tan   = math.tan,
  12.     asin  = math.asin,
  13.     acos  = math.acos,
  14.     atan  = math.atan,
  15.     atan2 = math.atan2,
  16.     sinh  = math.sinh,
  17.     cosh  = math.cosh,
  18.     tanh  = math.tanh,
  19.     ceil  = math.ceil,
  20.     floor = math.floor,
  21.     abs   = math.abs,
  22.     deg   = math.deg,
  23.     rad   = math.rad,
  24.     exp   = math.exp, -- Also e^n
  25.     max   = math.max,
  26.     min   = math.min,
  27.     map   = math.map,
  28.     fmod  = math.fmod,
  29.     log   = math.log,
  30.     log10 = math.log10,
  31.     sign  = math.sign,
  32.     rand  = math.random,
  33.     round = math.round,
  34.     pow   = math.pow, -- Also x^y
  35.     noise = math.noise,
  36.     lerp  = math.lerp,
  37.     ldexp = math.ldexp,
  38.     clamp = math.clamp,
  39.  
  40.     -- Reciprocal trigonometric functions:
  41.     csc   = function(x) return 1 / math.sin(x) end,
  42.     sec   = function(x) return 1 / math.cos(x) end,
  43.     cot   = function(x) return 1 / math.tan(x) end,
  44.     -- Reciprocal hyperbolic trigs:
  45.     csch  = function(x) return 1 / math.sinh(x) end,
  46.     sech  = function(x) return 1 / math.cosh(x) end,
  47.     coth  = function(x) return 1 / math.tanh(x) end,
  48.     -- Obscure trigs (for funsies):
  49.     vsin  = function(x) return 1 - math.cos(x) end,
  50.     cvsin = function(x) return 1 - math.sin(x) end,
  51.     vcos  = function(x) return 1 + math.cos(x) end,
  52.     cvcos = function(x) return 1 + math.sin(x) end,
  53.     hvsin = function(x) return (1 - math.cos(x)) / 2 end,
  54.     hvcos = function(x) return (1 + math.cos(x)) / 2 end,
  55.     xsec  = function(x) return (1 / math.cos(x)) - 1 end,
  56.     xcsc  = function(x) return (1 / math.sin(x)) - 1 end,
  57.     chord = function(x) return 2 * math.sin(x / 2) end,
  58.     -- New root function:
  59.     root = function(...)
  60.         local args = { ... }
  61.         if #args == 1 then
  62.             return math.sqrt(args[1])
  63.         elseif #args == 2 then
  64.             local n, x = args[1], args[2]
  65.             return x^(1/n)
  66.         else
  67.             warn("root expects 1 or 2 arguments")
  68.         end
  69.     end,
  70.     -- Double-precision randomizer (alternative)
  71.     rng = function(...)
  72.         local args = { ... }
  73.         if #args == 1 then
  74.             return math.random() * args[1]
  75.         elseif #args == 2 then
  76.             local a, b = args[1], args[2]
  77.             if a > b then a, b = b, a end
  78.             return math.random() * (b - a) + a
  79.         else
  80.             warn("rand expects 1 or 2 arguments")
  81.         end
  82.     end,
  83.     -- Integer factorial (postfix operator '!')
  84.     fact = function(n)
  85.         if n < 0 then warn("factorial of negative number") end
  86.         if n ~= math.floor(n) then warn("factorial expects an integer") end
  87.         local res = 1
  88.         for i = 2, n do res = res * i end
  89.         return res
  90.     end,
  91.     --[[ Multi-output wrappers:
  92.     modf  = function(x)
  93.         local intPart, fracPart = math.modf(x)
  94.         return {intPart, fracPart}
  95.     end,
  96.     frexp = function(x)
  97.         local mantissa, exponent = math.frexp(x)
  98.         return {mantissa, exponent}
  99.     end,
  100.     ]]
  101. }
  102.  
  103. local mathConstants = {
  104.     pi     = math.pi,
  105.     tau    = math.pi * 2,
  106.     sdelta = math.sqrt(2) + 1, -- The silver ratio
  107.     phi    = (1 + math.sqrt(5)) / 2, -- The golden ratio
  108.     psi    = 1.46557123187676802665, -- Supergolden ratio
  109.     e      = math.exp(1), -- Euler's number
  110.     inf    = math.huge, -- Also equals to 1.0e+308 or 2^1024
  111.     nan    = 0/0, -- Not a number or imaginary
  112.     gamma  = 0.577215664901532, -- Euler–Mascheroni constant
  113.     beta   = 0.915965594177219, -- Catalan's constant
  114.     zeta   = 1.202056903159594, -- Apery’s constant
  115.     eps    = 1e-323, -- Infinitesimal number (infinitely small but never 0)
  116.     zar    = 1.7763568394001104e-15, -- Zarian threshold (A little discovery I made with the very edge of limits) [eps*(2^1023.9999999999999)]
  117.     -- Sciences
  118.     n      = 6.02214076e+23, -- Avogadro's constant
  119.     h      = 6.62607015e-34, -- Planck's constant
  120.     c      = 2.99792458e+08, -- SPeed of light
  121.     g      = 6.6743e-11, -- Gravitational constant (G, but constants are lowercase only)
  122.     k      = 1.380649e-23, -- Boltzmann constant
  123.     q      = 1.602176634e-19, -- Elementary charge (e, but it contradicts Euler's number)
  124.     alpha  = 7.2973525693e-03 -- Fine-structure constant
  125.    
  126. }
  127.  
  128. --------------------
  129. -- Step 1: Tokenizer
  130. --------------------
  131. local function tokenize(expr)
  132.     local tokens = {}
  133.     local i = 1
  134.     while i <= #expr do
  135.         local c = expr:sub(i, i)
  136.         if c:match("%s") then
  137.             i = i + 1
  138.         elseif c:match("[%d%.]") then
  139.             local num = ""
  140.             while i <= #expr and expr:sub(i, i):match("[%d%.]") do
  141.                 num = num .. expr:sub(i, i)
  142.                 i = i + 1
  143.             end
  144.             if i <= #expr and (expr:sub(i, i) == "e" or expr:sub(i, i) == "E") then
  145.                 num = num .. expr:sub(i, i)
  146.                 i = i + 1
  147.                 if i <= #expr and (expr:sub(i, i) == "+" or expr:sub(i, i) == "-") then
  148.                     num = num .. expr:sub(i, i)
  149.                     i = i + 1
  150.                 end
  151.                 while i <= #expr and expr:sub(i, i):match("%d") do
  152.                     num = num .. expr:sub(i, i)
  153.                     i = i + 1
  154.                 end
  155.             end
  156.             table.insert(tokens, { type = "number", value = tonumber(num) })
  157.         elseif c == "," then
  158.             table.insert(tokens, { type = "comma", value = c })
  159.             i = i + 1
  160.         elseif c:match("[%a]") then
  161.             local ident = ""
  162.             while i <= #expr and expr:sub(i, i):match("[%a_]") do
  163.                 ident = ident .. expr:sub(i, i)
  164.                 i = i + 1
  165.             end
  166.             ident = ident:lower()
  167.             if mathConstants[ident] then
  168.                 table.insert(tokens, { type = "constant", value = mathConstants[ident] })
  169.             elseif mathFunctions[ident] then
  170.                 table.insert(tokens, { type = "function", value = ident, argCount = 1 })
  171.             else
  172.                 table.insert(tokens, { type = "unknown", value = ident })
  173.             end
  174.         elseif c == "(" or c == ")" then
  175.             table.insert(tokens, { type = "paren", value = c })
  176.             i = i + 1
  177.         elseif c == "|" then
  178.             table.insert(tokens, { type = "bar", value = c })
  179.             i = i + 1
  180.         elseif c == "!" then
  181.             -- Postfix factorial
  182.             table.insert(tokens, { type = "unary", value = "!" })
  183.             i = i + 1
  184.         else
  185.             local op = nil
  186.             if i < #expr and expr:sub(i, i+1) == "//" then
  187.                 op = "//"
  188.                 i = i + 2
  189.             else
  190.                 op = c
  191.                 i = i + 1
  192.             end
  193.             table.insert(tokens, { type = "operator", value = op })
  194.         end
  195.     end
  196.     return tokens
  197. end
  198.  
  199. ------------------------------------------------
  200. -- Process tokens to detect and mark unary minus
  201. ------------------------------------------------
  202. local function processUnaryOperators(tokens)
  203.     for i = 1, #tokens do
  204.         local token = tokens[i]
  205.         if token.type == "operator" and token.value == "-" then
  206.             local prev = tokens[i - 1]
  207.             if (not prev)
  208.                 or prev.type == "operator"
  209.                 or prev.type == "comma"
  210.                 or (prev.type == "paren" and prev.value == "(")
  211.                 or prev.type == "bar"
  212.             then
  213.                 token.type = "unary"
  214.                 token.value = "u-"
  215.             end
  216.         end
  217.     end
  218.     return tokens
  219. end
  220.  
  221. ------------------------------------------------------
  222. -- Preprocess tokens to insert implicit multiplication
  223. ------------------------------------------------------
  224. local function insertImplicitMultiplication(tokens)
  225.     local i = 1
  226.     local barOpenLocal = true
  227.     while i < #tokens do
  228.         local curr    = tokens[i]
  229.         local nextTok = tokens[i + 1]
  230.  
  231.         -- Figure out if `curr` is a “value” (so a following value should imply a '*')
  232.         local currIsVal = false
  233.         if curr.type == "number"
  234.             or curr.type == "constant"
  235.             or (curr.type == "paren" and curr.value == ")")
  236.         then
  237.             currIsVal = true
  238.  
  239.         elseif curr.type == "bar" then
  240.             -- if it's a closing bar, it behaves like a ')'
  241.             currIsVal = not barOpenLocal
  242.             -- flip state for the next bar
  243.             barOpenLocal = not barOpenLocal
  244.         end
  245.  
  246.         -- Figure out if `nextTok` is a “value” (so a preceding value should imply a '*')
  247.         local nextIsVal = false
  248.         if nextTok.type == "number"
  249.             or nextTok.type == "constant"
  250.             or (nextTok.type == "paren" and nextTok.value == "(")
  251.         then
  252.             nextIsVal = true
  253.  
  254.         elseif nextTok.type == "bar" then
  255.             -- if it's an opening bar, it behaves like a '('
  256.             nextIsVal = barOpenLocal
  257.         end
  258.  
  259.         -- If a value is next to a value, insert a '*'
  260.         if currIsVal and nextIsVal then
  261.             table.insert(tokens, i + 1, { type = "operator", value = "*" })
  262.             i = i + 1
  263.         end
  264.  
  265.         i = i + 1
  266.     end
  267.  
  268.     return tokens
  269. end
  270.  
  271. ----------------------------------------
  272. -- Operator precedence and associativity
  273. ----------------------------------------
  274. local operators = {
  275.     ["E"]  = { precedence = 5,   associativity = "right" }, -- Scientific notation
  276.     ["!"]  = { precedence = 4.5, associativity = "left"  }, -- Factorial (postfix)
  277.     ["^"]  = { precedence = 4,   associativity = "right" },
  278.     ["u-"] = { precedence = 3.5, associativity = "right" },
  279.     ["*"]  = { precedence = 3,   associativity = "left"  },
  280.     ["/"]  = { precedence = 3,   associativity = "left"  },
  281.     ["//"] = { precedence = 3,   associativity = "left"  },
  282.     ["%"]  = { precedence = 3,   associativity = "left"  },
  283.     ["+"]  = { precedence = 2,   associativity = "left"  },
  284.     ["-"]  = { precedence = 2,   associativity = "left"  },
  285. }
  286.  
  287. ---------------------------------------------------
  288. -- Step 2: Parser using the shunting-yard algorithm
  289. ---------------------------------------------------
  290. local function shuntingYard(tokens)
  291.     local output = {}
  292.     local opStack = {}
  293.     local barOpen = true
  294.     for _, token in ipairs(tokens) do
  295.         if token.type == "bar" then
  296.             if barOpen then
  297.                 table.insert(opStack, { type = "bar_open" })
  298.                 barOpen = false
  299.             else
  300.                 while #opStack > 0 and opStack[#opStack].type ~= "bar_open" do
  301.                     table.insert(output, table.remove(opStack))
  302.                 end
  303.                 if #opStack == 0 then
  304.                     return nil, "Mismatched '|' absolute value bars"
  305.                 end
  306.                 table.remove(opStack)
  307.                 table.insert(output, { type = "function", value = "abs", argCount = 1 })
  308.                 barOpen = true
  309.             end
  310.  
  311.         elseif token.type == "number" or token.type == "constant" then
  312.             table.insert(output, token)
  313.  
  314.         elseif token.type == "function" then
  315.             table.insert(opStack, token)
  316.  
  317.         elseif token.type == "comma" then
  318.             local foundParen = false
  319.             while #opStack > 0 do
  320.                 if opStack[#opStack].type == "paren" and opStack[#opStack].value == "(" then
  321.                     foundParen = true
  322.                     if #opStack > 1 and opStack[#opStack-1].type == "function" then
  323.                         opStack[#opStack-1].argCount = opStack[#opStack-1].argCount + 1
  324.                     else
  325.                         return nil, "Misplaced comma or missing function token"
  326.                     end
  327.                     break
  328.                 else
  329.                     table.insert(output, table.remove(opStack))
  330.                 end
  331.             end
  332.             if not foundParen then
  333.                 return nil, "Misplaced comma or mismatched parentheses"
  334.             end
  335.  
  336.         elseif token.type == "operator" or token.type == "unary" then
  337.             local o1 = token.value
  338.             while #opStack > 0 do
  339.                 local top = opStack[#opStack]
  340.                 if top.type ~= "operator" and top.type ~= "unary" and top.type ~= "function" then
  341.                     break
  342.                 end
  343.                 local p2 = (top.type == "function") and 100 or (operators[top.value] and operators[top.value].precedence or 0)
  344.                 local p1 = operators[o1] and operators[o1].precedence or 0
  345.                 local assoc = operators[o1] and operators[o1].associativity or "left"
  346.                 if (assoc == "left"  and p1 <= p2) or
  347.                     (assoc == "right" and p1 <  p2) then
  348.                     table.insert(output, table.remove(opStack))
  349.                 else
  350.                     break
  351.                 end
  352.             end
  353.             table.insert(opStack, token)
  354.  
  355.         elseif token.type == "paren" then
  356.             if token.value == "(" then
  357.                 table.insert(opStack, token)
  358.             else
  359.                 while #opStack > 0 and not (opStack[#opStack].type == "paren" and opStack[#opStack].value == "(") do
  360.                     table.insert(output, table.remove(opStack))
  361.                 end
  362.                 if #opStack == 0 then
  363.                     return nil, "Mismatched parentheses"
  364.                 end
  365.                 table.remove(opStack)
  366.                 if #opStack > 0 and opStack[#opStack].type == "function" then
  367.                     table.insert(output, table.remove(opStack))
  368.                 end
  369.             end
  370.  
  371.         elseif token.type == "unknown" then
  372.             return nil, "Unknown identifier: " .. token.value
  373.         end
  374.     end
  375.  
  376.     while #opStack > 0 do
  377.         local top = table.remove(opStack)
  378.         if (top.type == "paren") or (top.type == "bar_open") then
  379.             return nil, "Mismatched parentheses or '|' bars"
  380.         end
  381.         table.insert(output, top)
  382.     end
  383.  
  384.     return output
  385. end
  386.  
  387. --------------------------------------------------
  388. -- Step 3: Evaluate Reverse Polish Notation (RPN)
  389. --------------------------------------------------
  390. local function evaluateRPN(rpn)
  391.     local stack = {}
  392.     for _, token in ipairs(rpn) do
  393.         if token.type == "number" or token.type == "constant" then
  394.             table.insert(stack, token.value)
  395.  
  396.         elseif token.type == "operator" then
  397.             if #stack < 2 then return nil, "Invalid expression" end
  398.             local b = table.remove(stack)
  399.             local a = table.remove(stack)
  400.             local res
  401.             if     token.value == "+"  then res = a + b
  402.             elseif token.value == "-"  then res = a - b
  403.             elseif token.value == "*"  then res = a * b
  404.             elseif token.value == "/"  then res = a / b
  405.             elseif token.value == "//" then res = math.floor(a / b)
  406.             elseif token.value == "^"  then res = a ^ b
  407.             elseif token.value == "E"  then res = a * (10 ^ b)
  408.             elseif token.value == "%"  then res = math.fmod(a, b)
  409.             else return nil, "Unknown operator: " .. token.value end
  410.             table.insert(stack, res)
  411.  
  412.         elseif token.type == "unary" then
  413.             if #stack < 1 then return nil, "Missing operand for unary operator: " .. token.value end
  414.             local a = table.remove(stack)
  415.             local res
  416.             if token.value == "u-" then
  417.                 res = -a
  418.             elseif token.value == "!" then
  419.                 if a < 0 then return nil, "factorial of negative number" end
  420.                 if a ~= math.floor(a) then return nil, "factorial expects an integer" end
  421.                 res = 1
  422.                 for i = 2, a do res = res * i end
  423.             else
  424.                 return nil, "Unknown unary operator: " .. token.value
  425.             end
  426.             table.insert(stack, res)
  427.  
  428.         elseif token.type == "function" then
  429.             local nArgs = token.argCount or 1
  430.             if #stack < nArgs then return nil, "Missing argument for function: " .. token.value end
  431.             local args = {}
  432.             for i = 1, nArgs do
  433.                 table.insert(args, 1, table.remove(stack))
  434.             end
  435.             local fn = mathFunctions[token.value]
  436.             if not fn then return nil, "Unknown function: " .. token.value end
  437.             local result = fn(table.unpack(args))
  438.             table.insert(stack, result)
  439.         end
  440.     end
  441.  
  442.     if #stack ~= 1 then
  443.         return nil, "Invalid expression"
  444.     end
  445.     return stack[1]
  446. end
  447.  
  448. --------------------------------------------------------------------------
  449. -- The universal parser function that catches errors and returns a message
  450. --------------------------------------------------------------------------
  451. function Math.com(expr)
  452.     local tokens = tokenize(expr)
  453.     tokens = processUnaryOperators(tokens)
  454.     tokens = insertImplicitMultiplication(tokens)
  455.     local rpn, err = shuntingYard(tokens)
  456.     if not rpn then return err end
  457.     local result, err2 = evaluateRPN(rpn)
  458.     if result == nil then return err2 end
  459.     return result
  460. end
  461.  
  462. return Math
Tags: Roblox
Advertisement
Add Comment
Please, Sign In to add comment