Jump to content

Module:LogicUtils: Difference between revisions

From Logic World Wiki
use string:byte instead of searching a string
add support for algebraic style formulas
Line 27: Line 27:
end
end


local function eval(str, values)
local function isBinOp(c)
    return c == "&" or c == "|" or c == "^"
end
 
local function eval(expr, values)
     local stack = {}
     local stack = {}


     str:gsub("[^%s()]", function(c)
     for _, c in pairs(expr) do
         if c == "&" or c == "|" or c == "^" then
         if isBinOp(c) then
             local v = table.remove(stack)
             local v = table.remove(stack)
             if v == nil then error("Invalid expression: missing operand") end
             if v == nil then error("Invalid expression: missing operand") end
Line 58: Line 62:
             end
             end
         end
         end
     end)
     end


     return stack[1]
     return stack[1]
end
end


local function evalAll(str, num_vars, callback)
local function evalAll(expr, num_vars, callback)
     local totalCombinations = 2 ^ num_vars
     local totalCombinations = 2 ^ num_vars
     local bit32 = require( 'bit32' )
     local bit32 = require( 'bit32' )
Line 73: Line 77:
         end
         end


         local result = eval(str, values)
         local result = eval(expr, values)
         callback(values, result)
         callback(values, result)
     end
     end
end
local function algebraicToRPN(input)
    local output = {}
    local opstack = {}
    input:gsub("[^%s]", function(c)
        if isBinOp(c) then
            while opstack[#opstack] == "!" do
                table.insert(output, table.remove(opstack))
            end
            table.insert(opstack, c)
        elseif c == "!" or c == "(" then
            table.insert(opstack, c)
        elseif c == ")" then
            while #opstack > 0 do
                local op = table.remove(opstack)
                if op == "(" then
                    break
                end
                table.insert(output, op)
            end
        else
            table.insert(output, c)
        end
    end)
    -- Drain the operator stack to the output
    while #opstack > 0 do table.insert(output, table.remove(opstack)) end
    return output
end
end


Line 140: Line 176:


for i = 1,nOutputs do
for i = 1,nOutputs do
         local expr = args["expr"..i]
         local exprStr = args["expr"..i]
         if expr == nil then
         if exprStr == nil then
             error("Missing expression for output " .. i)
             error("Missing expression for output " .. i)
         end
         end


         evalAll(expr, nInputs, function(values, result)
         evalAll(algebraicToRPN(exprStr), nInputs, function(values, result)
    local row = tbl:tag('tr')
    local row = tbl:tag('tr')
             for j = 1,nInputs do
             for j = 1,nInputs do
Line 157: Line 193:


     return tostring(tbl)
     return tostring(tbl)
end
local function renderBinaryGraph(signals)
end
end


return p
return p

Revision as of 16:18, 8 September 2025

Documentation for this module may be created at Module:LogicUtils/doc

local p = {}

function parse_io(kind, row, str)
	local count = tonumber(str)
	local names = {}

	if count == nil then
		-- str is a list of IO names
		names = mw.text.split(str, ",")
	else
		-- str is a number
		if count == 1 then
			names[1] = kind
		else
			for i=1,count do
				names[i] = string.format("%s %i", kind, i)
			end
		end
	end

	for i, v in ipairs(names) do
		row:tag('th')
			:wikitext(v)
	end

	return #names
end

local function isBinOp(c)
    return c == "&" or c == "|" or c == "^"
end

local function eval(expr, values)
    local stack = {}

    for _, c in pairs(expr) do
        if isBinOp(c) then
            local v = table.remove(stack)
            if v == nil then error("Invalid expression: missing operand") end
            local v2 = table.remove(stack)
            if v2 == nil then error("Invalid expression: missing operand") end

            if c == "&" then
                table.insert(stack, v and v2)
            elseif c == "|" then
                table.insert(stack, v or v2)
            elseif c == "^" then
                table.insert(stack, v ~= v2)
            end
        elseif c == "!" then
            local v = table.remove(stack)
            if v == nil then error("Invalid expression: missing operand after '!'") end
            table.insert(stack, not v)
        else
            local index = c:lower():byte() - 96 -- 'a' = 1, 'b' = 2, etc.
            if index then
                if values[index] == nil and not ignoreMissing then error("Value for variable '" .. c .. "' not provided") end

                table.insert(stack, values[index] or false)
            else
                error("Invalid character in expression: " .. c)
            end
        end
    end

    return stack[1]
end

local function evalAll(expr, num_vars, callback)
    local totalCombinations = 2 ^ num_vars
    local bit32 = require( 'bit32' )

    for i = 0, totalCombinations - 1 do
        local values = {}
        for j = 1, num_vars do
            values[j] = bit32.band(i, bit32.lshift(1, num_vars - j)) ~= 0
        end

        local result = eval(expr, values)
        callback(values, result)
    end
end

local function algebraicToRPN(input)
    local output = {}
    local opstack = {}

    input:gsub("[^%s]", function(c)
        if isBinOp(c) then
            while opstack[#opstack] == "!" do
                table.insert(output, table.remove(opstack))
            end

            table.insert(opstack, c)
        elseif c == "!" or c == "(" then
            table.insert(opstack, c)
        elseif c == ")" then
            while #opstack > 0 do
                local op = table.remove(opstack)
                if op == "(" then
                    break
                end
                table.insert(output, op)
            end
        else
            table.insert(output, c)
        end
    end)

    -- Drain the operator stack to the output
    while #opstack > 0 do table.insert(output, table.remove(opstack)) end

    return output
end

local function renderCell(row, value)
    local style = "color:white; text-align:center;"
    if value == false or value == "0" then
        row:tag('td')
            :attr('style', 'background-color:#1f1e1e;'..style)
            :wikitext("0")
    elseif value == true or value == "1" then
        row:tag('td')
            :attr('style', 'background-color:#fd140f;'..style)
            :wikitext("1")
    else
        row:tag('td')
        	:cssText("text-align:center")
            :wikitext(value)
    end
end

p.truth_table = function(frame, args)
	local args = args or frame:getParent().args

    local tbl = mw.html.create('table')
        :addClass('wikitable')

	if args.caption ~= nil then
		tbl:tag('caption')
			:wikitext(args.caption)
	end

    local header = tbl:tag('tr')
	local nInputs = parse_io("Input", header, args.inputs)
	local nOutputs = parse_io("Output", header, args.outputs)

	local i = 1
	while args[i] ~= nil do
		local row = tbl:tag('tr')

		for token in string.gmatch(args[i], "[^%s]+") do
            renderCell(row, token)
		end

		i = i + 1
	end

    return tostring(tbl)
end

p.truth_table_auto = function(frame, args)
	local args = args or frame:getParent().args

    local tbl = mw.html.create('table')
        :addClass('wikitable')

	if args.caption ~= nil then
		tbl:tag('caption')
			:wikitext(args.caption)
	end

    local header = tbl:tag('tr')
	local nInputs = parse_io("Input", header, args.inputs)
	local nOutputs = parse_io("Output", header, args.outputs)

	for i = 1,nOutputs do
        local exprStr = args["expr"..i]
        if exprStr == nil then
            error("Missing expression for output " .. i)
        end

        evalAll(algebraicToRPN(exprStr), nInputs, function(values, result)
		    local row = tbl:tag('tr')
            for j = 1,nInputs do
                renderCell(row, values[j])
            end
            renderCell(row, result)
        end)

		i = i + 1
	end

    return tostring(tbl)
end

local function renderBinaryGraph(signals)

end

return p