Module:Calculator widget: Difference between revisions

Content deleted Content added
regfull
m Reverted 1 edit by Nope2051 (talk) to last revision by Bawolff
 
(20 intermediate revisions by 2 users not shown)
Line 2:
 
-- Make a calculator widget using {{calculator}}
-- As a work around to the fact that buttons can only affect one field, store everything in a single number
 
local function getDisplay()
-- layout (status = bits 0-2)
-- Normal calculators don't display "Infinity" when dividing by 0.
-- 9 bits mem, 2 bits memexp, 17 bits x reg, 2 bits exp, 17 bits y reg, 2 bits exp, 1 bit decimal flag, 2 bit status.
return 'iffinite(ifzero(displayY,x,y),ifzero(displayY,x,y),nan)'
-- Negative means show Y reg instead of X reg.
-- status:
-- 0 = Add
-- 1 = sub
-- 2 = mul
-- 3 = div
 
local ALL_START = 0
local STATUS_START = 0
local STATUS_END = 2
local DECIMAL_START = 2
local DECIMAL_END = 3
local Y_EXP_START = 3
local Y_EXP_END = 5
local Y_START = 5
local Y_END = 22
local X_EXP_START = 22
local X_EXP_END = 24
local X_START = 24
local X_END = 41
local MEM_EXP_START = 41
local MEM_EXP_END = 43
local MEM_START = 43
local MEM_END = 52
local ALL_END = 52
 
-- These functions return {{calculator}} formulas
-- that are evaled client side.
 
-- Get value of packed variable
local function unpack(startIndex, endIndex)
local s = 2^startIndex
local e = 2^(endIndex-startIndex)
return "floor((reg/"..s..")%"..e..")"
end
 
local function packpressDecimal( value, offset, endOffset )
return {
local maxLen = 2^(endOffset-offset)
{'decimal', 'ifequal(decimal,0,1,decimal)'},
offsetMul = 2^offset
{ 'y', 'ifzero(equalFlag,y,0)' },
return 'ifgreaterorequal( value, ' .. maxLen .. ',NaN,' .. offsetMul .. '*' .. value .. ')'
{ 'x', 'ifzero(displayY,x,0)' },
{ 'equalFlag', 0 },
{ 'displayY', '0' }
}
end
 
-- This is sketch because x should happen before decimal.
-- select some bits with rest set to 0.
local function selectBitspressNumber(startIndex, endIndexn)
return { {'x', 'ifequal(decimal,0,not(displayY)*x*10+(ifpositive(not(displayY)*x,1,-1))*' .. n .. ','
return '('..unpack(startIndex,endIndex) .. '*' .. (2^startIndex).. ')'
.. 'x+(ifpositive(x,1,-1))*(' .. n .. '/pow(10,decimal)))'},
{'decimal','ifzero(decimal,0,decimal+1)'},
{ 'y', 'ifzero(equalFlag,y,0)' },
{ 'op', 'ifzero(equalFlag,op,0)' },
{ 'equalFlag', 0 },
{ 'displayY', '0' },
}
end
 
local function getgetClear(reg)
return {
local main_start, main_end, exp_start, exp_end
if { reg =='x', 'X0' then},
{ 'y', '0' },
main_start = X_START
{ 'op', '0' },
main_end = X_END
{ 'decimal', '0' },
exp_start = X_EXP_START
{ 'percentFlag', '0' },
exp_end = X_EXP_END
{ 'equalFlag', '0' },
else
{ 'displayY', '0' },
error( "unknown register" )
end}
return '(' .. unpack( X_START, X_END ) ..
'*(pow(10,' .. unpack( X_EXP_START, X_EXP_END ) .. ')))'
 
end
 
-- Compute a function
local function regFull(x)
-- 0: add 1: subtract 2: multiply 4: divide 5: square root.
if x ~= 'X' then
function compute(op)
error( "unimplemented" )
local res = {
end
{ 'x', 'ifzero(equalFlag,ifequal(percentFlag, 1, x*y/100, x),ifgreater(op,1,1,0))' },
 
{ 'y', 'switch(op,0,x+y,1,y-x,2,x*y,3,y/x,4,y)' },
return 'or('
--{ 'x', '0' },
.. 'ifgreaterorequal('
{ 'decimal', '0' },
.. unpack( X_START, X_END ) .. ','
{ 'displayY', '1' },
.. ( 2^(X_END-X_START) - 1 )
..{ '),percentFlag', 0 },
{ 'op', tostring(op) },
.. 'ifgreater('
{ 'equalFlag', 0 }
.. unpack( X_EXP_START, X_EXP_END ) .. ','
}
.. ( 2^(X_EXP_END-X_EXP_START) - 1 )
return res
.. ')'
.. ')'
 
end
 
-- UserEqual presseskey is a number.bit weird
-- If you press equal a second time, it should repeat operation "10 + 5 = = = =" gives 30
-- If status
-- If you press equal and then a number, we start a new calc "10 + 5 = 2 + 1" gives 3
local function pressNumber(x)
-- If you press an operation, you use the current result as the first number in operation "10 + 5 = + 2" gives 17
return selectBits( ALL_START, X_EXP_START ) .. '+' ..
function computeEqual()
selectBits( X_END, ALL_END ) .. '+' ..
local res = {
'ifzero( '
{ 'x', 'ifequal(percentFlag, 1, x*y/100, x)' },
.. regFull("X") .. ',' ..
{ 'y', 'switch(op,0,x+y,1,y-x,2,x*y,3,y/x,4,sqrt(y))' },
-- we are not full, so add something
--{ 'x', '0' },
pack(
{ 'decimal', '0' },
unpack( X_START, X_END ) .. '*10+' .. x,
{ 'displayY', '1' },
X_START,
{ 'percentFlag', 0 },
X_END
) ..{ '+equalFlag', 1 ..}
}
pack(
return res
unpack( X_EXP_START, X_EXP_END )
.. '+' .. unpack( DECIMAL_START, DECIMAL_END )
) ..
',' ..
-- Overflow, do not allow additional digits
selectBits( X_EXP_START, X_END )
.. ')'
end
 
-- only arg is "fallback". Set to "all" (Default) to show everything when no-js
-- set to "keypad" to show only keypad to non-js users. set to "none" to show nothing.
function p.getWidget( frame )
local buttons = {
{ "MC", {{"mem", "0"}}, "Memory clear" },
{ "MR", {{"x", "mem"},{"displayY", 0}}, "Memory recall" },
{ "M−", {{"mem", "ifzero(displayY,mem-x,mem-y)"}}, "Subtract from memory" },
{ "M+", {{"mem", "ifzero(displayY,mem+x,mem+y)"}}, "Add to memory" },
{ "C", getClear(), "Clear" },
-- Its a bit unclear what the expected behaviour of pressing ± immediately after equal.
{ "±", "" },
{ "%±", {{'x', '0-x'}}, "Change sign" },
{ "%", ""{{'percentFlag', '1'}} },
-- A bit hacky, sqrt is the only unary operator. We want to make
-- sure it does it again if you press 25 √ = =
{ "√", {{'x','sqrt(x)'},{'op','4'},{'equalFlag','1'},{'y','x'}} },
{ "7", pressNumber(7) },
{ "8", pressNumber(8) },
{ "9", pressNumber(9) },
{ "÷", ""compute(3) },
{ "4", pressNumber(4) },
{ "5", pressNumber(5) },
{ "6", pressNumber(6) },
{ "×", compute(2), "Multiply" },
{ "1", pressNumber(1) },
{ "2", pressNumber(2) },
{ "3", pressNumber(3) },
{ "−", compute(1), "Subtract" },
{ "0", pressNumber(0) },
{ ".", pressDecimal(), "Decimal point" },
{ "=", ""computeEqual() },
{ "+", compute(0), "Add" },
}
 
local calc = '<div class="calculatorwidget calculator-container" style="display:grid;grid-template-columns:repeat(4, 1fr);grid-gap:5px;min-width:256px;width:20ch;border:thin solid gray"><div;padding: style="grid-column:1/55px;">{{Calculator codex text|default=0|id=ans|style=text-align:right|readonly=1}}</div>'
if frame.args['fallback'] == 'none' then
-- if no js, hide.
calc = '<div style="display:none;" class="calculatorgadget-enabled">' .. calc
end
-- On Calculator page, we want to show the layout of buttons on print, but
-- not the answer field.
calc = calc .. '<div style="grid-column:1/5;'
if frame.args['fallback'] == 'keypad' then
-- for non-js users only show keypad but not answer widget.
calc = calc .. 'display:none" class="calculatorgadget-enabled'
end
calc = calc .. '">{{Calculator codex text|default=0|id=ans|formula=' .. getDisplay() .. '|style=text-align:right;font-weight:bold;|readonly=1|NaN-text=Error|aria-label=Result}}</div>'
for i, v in ipairs(buttons) do
local calcType, calcWeight
local type = v[1] == 'C' and 'destructive' or 'default'
if v[1] == 'C' then
calcType = 'destructive'
calcWeight = 'normal'
elseif v[1] == '=' then
calcType = 'progressive'
calcWeight = 'primary'
else
calcType = 'default'
calcWeight = 'normal'
end
-- maybe make "=" be progressive instead of default. Perhaps weight = primary?
local forVar = ''
calc = calc .. '{{calculator button|type='.. type ..'|weight=normal|for=reg|formula=' .. v[2] .. '|contents='.. v[1] .. '}}'
local formula = ''
for i2, v2 in ipairs( v[2] ) do
forVar = forVar .. ';' .. v2[1]
formula = formula .. ';' .. v2[2]
end
-- Should the alt text also be a title attribute to make a tooltip?
local alt = v[3] ~= nil and '|alt=' .. v[3] or ''
calc = calc .. '{{calculator button|type='.. calcType ..'|weight='.. calcWeight .. alt .. '|for=' .. string.sub(forVar,2) .. '|formula=' .. string.sub(formula,2) .. '|contents='.. v[1] .. '}}'
end
calc = calc .. '{{calculator|type=hidden|id=x|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=y|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=mem|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=op|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=decimal|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=displayY|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=percentFlag|default=0}}'
calc = calc .. '{{calculator|type=hidden|id=equalFlag|default=0}}'
calc = calc .. '<templatestyles src="Module:Calculator widget/style.css"/>'
calc = calc .. '</div>'
if frame.args['fallback'] == 'none' then
-- if no js, hide.
calc = calc .. '</div>'
end
return frame:preprocess(calc)
end