Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- MathParserModule.lua
- local Math = {}
- ------------------------------------
- -- Supported functions and constants
- ------------------------------------
- local mathFunctions = {
- sin = math.sin,
- cos = math.cos,
- tan = math.tan,
- asin = math.asin,
- acos = math.acos,
- atan = math.atan,
- atan2 = math.atan2,
- sinh = math.sinh,
- cosh = math.cosh,
- tanh = math.tanh,
- ceil = math.ceil,
- floor = math.floor,
- abs = math.abs,
- deg = math.deg,
- rad = math.rad,
- exp = math.exp, -- Also e^n
- max = math.max,
- min = math.min,
- map = math.map,
- fmod = math.fmod,
- log = math.log,
- log10 = math.log10,
- sign = math.sign,
- rand = math.random,
- round = math.round,
- pow = math.pow, -- Also x^y
- noise = math.noise,
- lerp = math.lerp,
- ldexp = math.ldexp,
- clamp = math.clamp,
- -- Reciprocal trigonometric functions:
- csc = function(x) return 1 / math.sin(x) end,
- sec = function(x) return 1 / math.cos(x) end,
- cot = function(x) return 1 / math.tan(x) end,
- -- Reciprocal hyperbolic trigs:
- csch = function(x) return 1 / math.sinh(x) end,
- sech = function(x) return 1 / math.cosh(x) end,
- coth = function(x) return 1 / math.tanh(x) end,
- -- Obscure trigs (for funsies):
- vsin = function(x) return 1 - math.cos(x) end,
- cvsin = function(x) return 1 - math.sin(x) end,
- vcos = function(x) return 1 + math.cos(x) end,
- cvcos = function(x) return 1 + math.sin(x) end,
- hvsin = function(x) return (1 - math.cos(x)) / 2 end,
- hvcos = function(x) return (1 + math.cos(x)) / 2 end,
- xsec = function(x) return (1 / math.cos(x)) - 1 end,
- xcsc = function(x) return (1 / math.sin(x)) - 1 end,
- chord = function(x) return 2 * math.sin(x / 2) end,
- -- New root function:
- root = function(...)
- local args = { ... }
- if #args == 1 then
- return math.sqrt(args[1])
- elseif #args == 2 then
- local n, x = args[1], args[2]
- return x^(1/n)
- else
- warn("root expects 1 or 2 arguments")
- end
- end,
- -- Double-precision randomizer (alternative)
- rng = function(...)
- local args = { ... }
- if #args == 1 then
- return math.random() * args[1]
- elseif #args == 2 then
- local a, b = args[1], args[2]
- if a > b then a, b = b, a end
- return math.random() * (b - a) + a
- else
- warn("rand expects 1 or 2 arguments")
- end
- end,
- -- Integer factorial (postfix operator '!')
- fact = function(n)
- if n < 0 then warn("factorial of negative number") end
- if n ~= math.floor(n) then warn("factorial expects an integer") end
- local res = 1
- for i = 2, n do res = res * i end
- return res
- end,
- --[[ Multi-output wrappers:
- modf = function(x)
- local intPart, fracPart = math.modf(x)
- return {intPart, fracPart}
- end,
- frexp = function(x)
- local mantissa, exponent = math.frexp(x)
- return {mantissa, exponent}
- end,
- ]]
- }
- local mathConstants = {
- pi = math.pi,
- tau = math.pi * 2,
- sdelta = math.sqrt(2) + 1, -- The silver ratio
- phi = (1 + math.sqrt(5)) / 2, -- The golden ratio
- psi = 1.46557123187676802665, -- Supergolden ratio
- e = math.exp(1), -- Euler's number
- inf = math.huge, -- Also equals to 1.0e+308 or 2^1024
- nan = 0/0, -- Not a number or imaginary
- gamma = 0.577215664901532, -- Euler–Mascheroni constant
- beta = 0.915965594177219, -- Catalan's constant
- zeta = 1.202056903159594, -- Apery’s constant
- eps = 1e-323, -- Infinitesimal number (infinitely small but never 0)
- zar = 1.7763568394001104e-15, -- Zarian threshold (A little discovery I made with the very edge of limits) [eps*(2^1023.9999999999999)]
- -- Sciences
- n = 6.02214076e+23, -- Avogadro's constant
- h = 6.62607015e-34, -- Planck's constant
- c = 2.99792458e+08, -- SPeed of light
- g = 6.6743e-11, -- Gravitational constant (G, but constants are lowercase only)
- k = 1.380649e-23, -- Boltzmann constant
- q = 1.602176634e-19, -- Elementary charge (e, but it contradicts Euler's number)
- alpha = 7.2973525693e-03 -- Fine-structure constant
- }
- --------------------
- -- Step 1: Tokenizer
- --------------------
- local function tokenize(expr)
- local tokens = {}
- local i = 1
- while i <= #expr do
- local c = expr:sub(i, i)
- if c:match("%s") then
- i = i + 1
- elseif c:match("[%d%.]") then
- local num = ""
- while i <= #expr and expr:sub(i, i):match("[%d%.]") do
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- if i <= #expr and (expr:sub(i, i) == "e" or expr:sub(i, i) == "E") then
- num = num .. expr:sub(i, i)
- i = i + 1
- if i <= #expr and (expr:sub(i, i) == "+" or expr:sub(i, i) == "-") then
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- while i <= #expr and expr:sub(i, i):match("%d") do
- num = num .. expr:sub(i, i)
- i = i + 1
- end
- end
- table.insert(tokens, { type = "number", value = tonumber(num) })
- elseif c == "," then
- table.insert(tokens, { type = "comma", value = c })
- i = i + 1
- elseif c:match("[%a]") then
- local ident = ""
- while i <= #expr and expr:sub(i, i):match("[%a_]") do
- ident = ident .. expr:sub(i, i)
- i = i + 1
- end
- ident = ident:lower()
- if mathConstants[ident] then
- table.insert(tokens, { type = "constant", value = mathConstants[ident] })
- elseif mathFunctions[ident] then
- table.insert(tokens, { type = "function", value = ident, argCount = 1 })
- else
- table.insert(tokens, { type = "unknown", value = ident })
- end
- elseif c == "(" or c == ")" then
- table.insert(tokens, { type = "paren", value = c })
- i = i + 1
- elseif c == "|" then
- table.insert(tokens, { type = "bar", value = c })
- i = i + 1
- elseif c == "!" then
- -- Postfix factorial
- table.insert(tokens, { type = "unary", value = "!" })
- i = i + 1
- else
- local op = nil
- if i < #expr and expr:sub(i, i+1) == "//" then
- op = "//"
- i = i + 2
- else
- op = c
- i = i + 1
- end
- table.insert(tokens, { type = "operator", value = op })
- end
- end
- return tokens
- end
- ------------------------------------------------
- -- Process tokens to detect and mark unary minus
- ------------------------------------------------
- local function processUnaryOperators(tokens)
- for i = 1, #tokens do
- local token = tokens[i]
- if token.type == "operator" and token.value == "-" then
- local prev = tokens[i - 1]
- if (not prev)
- or prev.type == "operator"
- or prev.type == "comma"
- or (prev.type == "paren" and prev.value == "(")
- or prev.type == "bar"
- then
- token.type = "unary"
- token.value = "u-"
- end
- end
- end
- return tokens
- end
- ------------------------------------------------------
- -- Preprocess tokens to insert implicit multiplication
- ------------------------------------------------------
- local function insertImplicitMultiplication(tokens)
- local i = 1
- local barOpenLocal = true
- while i < #tokens do
- local curr = tokens[i]
- local nextTok = tokens[i + 1]
- -- Figure out if `curr` is a “value” (so a following value should imply a '*')
- local currIsVal = false
- if curr.type == "number"
- or curr.type == "constant"
- or (curr.type == "paren" and curr.value == ")")
- then
- currIsVal = true
- elseif curr.type == "bar" then
- -- if it's a closing bar, it behaves like a ')'
- currIsVal = not barOpenLocal
- -- flip state for the next bar
- barOpenLocal = not barOpenLocal
- end
- -- Figure out if `nextTok` is a “value” (so a preceding value should imply a '*')
- local nextIsVal = false
- if nextTok.type == "number"
- or nextTok.type == "constant"
- or (nextTok.type == "paren" and nextTok.value == "(")
- then
- nextIsVal = true
- elseif nextTok.type == "bar" then
- -- if it's an opening bar, it behaves like a '('
- nextIsVal = barOpenLocal
- end
- -- If a value is next to a value, insert a '*'
- if currIsVal and nextIsVal then
- table.insert(tokens, i + 1, { type = "operator", value = "*" })
- i = i + 1
- end
- i = i + 1
- end
- return tokens
- end
- ----------------------------------------
- -- Operator precedence and associativity
- ----------------------------------------
- local operators = {
- ["E"] = { precedence = 5, associativity = "right" }, -- Scientific notation
- ["!"] = { precedence = 4.5, associativity = "left" }, -- Factorial (postfix)
- ["^"] = { precedence = 4, associativity = "right" },
- ["u-"] = { precedence = 3.5, associativity = "right" },
- ["*"] = { precedence = 3, associativity = "left" },
- ["/"] = { precedence = 3, associativity = "left" },
- ["//"] = { precedence = 3, associativity = "left" },
- ["%"] = { precedence = 3, associativity = "left" },
- ["+"] = { precedence = 2, associativity = "left" },
- ["-"] = { precedence = 2, associativity = "left" },
- }
- ---------------------------------------------------
- -- Step 2: Parser using the shunting-yard algorithm
- ---------------------------------------------------
- local function shuntingYard(tokens)
- local output = {}
- local opStack = {}
- local barOpen = true
- for _, token in ipairs(tokens) do
- if token.type == "bar" then
- if barOpen then
- table.insert(opStack, { type = "bar_open" })
- barOpen = false
- else
- while #opStack > 0 and opStack[#opStack].type ~= "bar_open" do
- table.insert(output, table.remove(opStack))
- end
- if #opStack == 0 then
- return nil, "Mismatched '|' absolute value bars"
- end
- table.remove(opStack)
- table.insert(output, { type = "function", value = "abs", argCount = 1 })
- barOpen = true
- end
- elseif token.type == "number" or token.type == "constant" then
- table.insert(output, token)
- elseif token.type == "function" then
- table.insert(opStack, token)
- elseif token.type == "comma" then
- local foundParen = false
- while #opStack > 0 do
- if opStack[#opStack].type == "paren" and opStack[#opStack].value == "(" then
- foundParen = true
- if #opStack > 1 and opStack[#opStack-1].type == "function" then
- opStack[#opStack-1].argCount = opStack[#opStack-1].argCount + 1
- else
- return nil, "Misplaced comma or missing function token"
- end
- break
- else
- table.insert(output, table.remove(opStack))
- end
- end
- if not foundParen then
- return nil, "Misplaced comma or mismatched parentheses"
- end
- elseif token.type == "operator" or token.type == "unary" then
- local o1 = token.value
- while #opStack > 0 do
- local top = opStack[#opStack]
- if top.type ~= "operator" and top.type ~= "unary" and top.type ~= "function" then
- break
- end
- local p2 = (top.type == "function") and 100 or (operators[top.value] and operators[top.value].precedence or 0)
- local p1 = operators[o1] and operators[o1].precedence or 0
- local assoc = operators[o1] and operators[o1].associativity or "left"
- if (assoc == "left" and p1 <= p2) or
- (assoc == "right" and p1 < p2) then
- table.insert(output, table.remove(opStack))
- else
- break
- end
- end
- table.insert(opStack, token)
- elseif token.type == "paren" then
- if token.value == "(" then
- table.insert(opStack, token)
- else
- while #opStack > 0 and not (opStack[#opStack].type == "paren" and opStack[#opStack].value == "(") do
- table.insert(output, table.remove(opStack))
- end
- if #opStack == 0 then
- return nil, "Mismatched parentheses"
- end
- table.remove(opStack)
- if #opStack > 0 and opStack[#opStack].type == "function" then
- table.insert(output, table.remove(opStack))
- end
- end
- elseif token.type == "unknown" then
- return nil, "Unknown identifier: " .. token.value
- end
- end
- while #opStack > 0 do
- local top = table.remove(opStack)
- if (top.type == "paren") or (top.type == "bar_open") then
- return nil, "Mismatched parentheses or '|' bars"
- end
- table.insert(output, top)
- end
- return output
- end
- --------------------------------------------------
- -- Step 3: Evaluate Reverse Polish Notation (RPN)
- --------------------------------------------------
- local function evaluateRPN(rpn)
- local stack = {}
- for _, token in ipairs(rpn) do
- if token.type == "number" or token.type == "constant" then
- table.insert(stack, token.value)
- elseif token.type == "operator" then
- if #stack < 2 then return nil, "Invalid expression" end
- local b = table.remove(stack)
- local a = table.remove(stack)
- local res
- if token.value == "+" then res = a + b
- elseif token.value == "-" then res = a - b
- elseif token.value == "*" then res = a * b
- elseif token.value == "/" then res = a / b
- elseif token.value == "//" then res = math.floor(a / b)
- elseif token.value == "^" then res = a ^ b
- elseif token.value == "E" then res = a * (10 ^ b)
- elseif token.value == "%" then res = math.fmod(a, b)
- else return nil, "Unknown operator: " .. token.value end
- table.insert(stack, res)
- elseif token.type == "unary" then
- if #stack < 1 then return nil, "Missing operand for unary operator: " .. token.value end
- local a = table.remove(stack)
- local res
- if token.value == "u-" then
- res = -a
- elseif token.value == "!" then
- if a < 0 then return nil, "factorial of negative number" end
- if a ~= math.floor(a) then return nil, "factorial expects an integer" end
- res = 1
- for i = 2, a do res = res * i end
- else
- return nil, "Unknown unary operator: " .. token.value
- end
- table.insert(stack, res)
- elseif token.type == "function" then
- local nArgs = token.argCount or 1
- if #stack < nArgs then return nil, "Missing argument for function: " .. token.value end
- local args = {}
- for i = 1, nArgs do
- table.insert(args, 1, table.remove(stack))
- end
- local fn = mathFunctions[token.value]
- if not fn then return nil, "Unknown function: " .. token.value end
- local result = fn(table.unpack(args))
- table.insert(stack, result)
- end
- end
- if #stack ~= 1 then
- return nil, "Invalid expression"
- end
- return stack[1]
- end
- --------------------------------------------------------------------------
- -- The universal parser function that catches errors and returns a message
- --------------------------------------------------------------------------
- function Math.com(expr)
- local tokens = tokenize(expr)
- tokens = processUnaryOperators(tokens)
- tokens = insertImplicitMultiplication(tokens)
- local rpn, err = shuntingYard(tokens)
- if not rpn then return err end
- local result, err2 = evaluateRPN(rpn)
- if result == nil then return err2 end
- return result
- end
- return Math
Advertisement
Add Comment
Please, Sign In to add comment