Calculator source


The ProscriptLS source used in the Calculator example. Links to these source files are: calculator.pl and calculator_data.pl . There are three library packages used in the ProscriptLS source: dom.pl, listut.pl, and listut2.pl.

Javascript source for a similar web page is shown in the calculator.js file.

calculator.pl

% Purpose: Support a simple HTML Calculator. The calculator allows positive and negative
% numbers including exponent notation. It supports four operators: +, -, *, and /.
%
% Author: Lindsey Spratt, 2019.
%
% Implementation: This calculator implementation uses clauses to store
% state (implemented in calculator_data.pl).
% 'current' is the current register value. This is
% where digits input from the user are held and
% where results of operations are displayed.
% 'memory' holds the previous value of 'current'.
% Clicking on an operator in the interface records
% the index (1 - 4) of that operator, copies 'current'
% to 'memory', and resets 'current' to empty.
% Clicking on '=' in the interface runs the
% calculate/0 predicate that evaluates
% 'operator'('memory', 'current') to get new
% vaue for 'current'.


:- ensure_loaded(calculator_data).
:- ensure_loaded('../library/dom.pl').
:- ensure_loaded('../library/listut.pl').
:- ensure_loaded('../library/listut2.pl').

fix_current.

current_prop_string(has_e, "e").
current_prop_string(has_e0, "e0").
current_prop_string(has_period, ".").
current_prop_string(in_error, "!").
current_prop_string(is_blank, " ").
current_prop_string(has_eneg0, "e-0").
current_prop_string(has_eneg, "e-").

current_prop(has_neg) :-
    currentx(Current),
    append("-", _Current).

current_prop(too_long) :-
    !,
    current_length(Length),
    maxlengthx(Maxlength),
    Length > Maxlength.

current_prop(Prop:-
    current_prop_string(PropString),
    currentx(Current),
    contains_list(CurrentString).


update_old_new(collapse_e0, "e0", "e").
update_old_new(collapse_eneg0, "e-0", "e-").
update_old_new(remove_neg_exponent, "e-", "e").
update_old_new(insert_neg_exponent, "e", "e-").


update_current(remove_neg) :-
    !,
    currentx(Current),
    append("-", NewCurrentCurrent),
    set_current(NewCurrent).

update_current(insert_neg) :-
    !,
    currentx(Current),
    append("-", CurrentNewCurrent),
    set_current(NewCurrent).

update_current(append_e0) :-
    !,
    currentx(Current),
    append(Current, "e0", NewCurrent),
    set_current(NewCurrent).

update_current(lowercase) :-
    !,
    currentx(Current),
    lowercase(CurrentNewCurrent),
    set_current(NewCurrent).

update_current(Key:-
    update_old_new(KeyOldNew),
    update_current(OldNew).


update_current(OldNew:-
    currentx(Current),
    select_list(OldCurrentNewNewCurrent),
    set_current(NewCurrent).


display_current :-
    currentx(Current),
    set_dom_name_path_value(["Calculator", "Display"], Current).

add_digit(Dig:-
    number_codes(DigDigCodes),
    (\+ current_prop(in_error)
      -> (current_prop(is_blank)
           -> set_current(DigCodes)
         ;
          eval_current(0),
          \+ current_prop(has_period)
           -> set_current(DigCodes)
         ;
          add_current(DigCodes)
         ),
         update_current(lowercase)
    ;
    set_current("HintPress 'AC'")
    ),
    (current_prop(has_e0)
      -> update_current(collapse_e0)
    ;
    current_prop(has_eneg0)
      -> update_current(collapse_eneg0)
    ; true
    ),
    (current_prop(too_long)
      -> set_current("AarghToo long")
    ; true
    ),
    display_current.

add_exponent :-
    update_current(append_e0),
    display_current.

dot :-
    (current_length(0)
      -> set_current("0.")
    ;
    \+ current_prop(has_period),
    \+ current_prop(has_e)
      -> add_current(".")
    ),
    display_current.

eval_current(R:-
    currentx(C),
    eval_codes(CR).

% eval codes with 'e': e.g. "2.1e-3"
eval_codes(CR:-
    split_list(CPrefix, "e", Suffix),
    !,
    eval_codes(PrefixP),
    eval_codes(SuffixS),
    R is P * 10 ^ S.

eval_codes(CR:-
    catch(
      (atom_codes(AC),
       atom_to_term(AT_),
       call(R is T)),
      _,
      fail).

clear :-
    set_current("0"),
    display_current.

all_clear :-
    set_memory("0"),
    set_current("0"),
    set_operation(0),
    display_current.

plus_minus :-
    (current_prop(has_e)
      -> (current_prop(has_eneg)
            -> update_current(remove_neg_exponent)
          ;
          update_current(insert_neg_exponent)
         )
    ;
    current_prop(has_neg)
      -> update_current(remove_neg)
    ;
    update_current(insert_neg)
    ),
    (eval_current(0),
    \+ current_prop(has_period)
      -> set_current("0")
    ; true
    ),
    display_current.

do_exponent :-
    current_prop(has_e)
      -> true
    ;
    add_exponent.

operate(Op:-
    operationx(CurrentOp),
    (CurrentOp \= 0 -> calculate;true),
    atom_codes(Op, [OpCode]),
    nth1(NewOp, "*/+-", OpCode),
    !,
    set_operation(NewOp),
    currentx(Current),
    set_memory(Current),
    set_current(" "),
    display_current.

calculate :-
    operationx(OpNum),
    memoryx(Memory),
    currentx(Current),
    eval_codes(MemoryMV),
    eval_codes(CurrentCV),
    eval_operation(OpNumMVCVValue),
    set_operation(0),
    set_memory("0"),
    (number(Value)
      -> number_codes(ValueValueCodes)
    ;
    atom_codes(ValueValueCodes)
    ),
    set_current(ValueCodes),
    display_current.

eval_operation(1, MVCVValue:- Value is MV * CV.
eval_operation(2, MVCVValue:- Value is MV / CV.
eval_operation(3, MVCVValue:- Value is MV + CV.
eval_operation(4, MVCVValue:- Value is MV - CV.

calculator_data.pl

:- dynamic([current/1, memory/1, operation/1, maxlength/1]).
:- initialization(init).

init :-
  asserta(current("0")),
  asserta(memory("0")),
  asserta(operation(0)),
  asserta(maxlength(30)).

currentx(X:-
    clause(current(X), true),
    !.
currentx("0").

current_length(L:-
    currentx(X),
    length(XL).

set_current(X:-
    retractall(current(_)),
    asserta(current(X)).

add_current(X:-
    retract(current(C)),
    append(CXY),
    asserta(current(Y)),
    !.

maxlengthx(X:-
    clause(maxlength(X), true).


memoryx(X:-
    clause(memory(X), true).

set_memory(X:-
    retractall(memory(_)),
    asserta(memory(X)).

operationx(X:-
    clause(operation(X), true).

set_operation(X:-
    retractall(operation(_)),
    asserta(operation(X)).

calculator.js

Memory  = "0";      // initialise memory variable
Current = "0";      //   and value of Display ("current" value)
Operation = 0;      // Records code for eg * / etc.
MAXLENGTH = 30;     // maximum number of digits before decimal!

function AddDigit(dig)          //ADD A DIGIT TO DISPLAY (keep as 'Current')
{ if (Current.indexOf("!") == -1)  //if not already an error
{ if (    (eval(Current) == 0)
    && (Current.indexOf(".") == -1)
) { Current = dig;
} else
{ Current = Current + dig;
};
    Current = Current.toLowerCase(); //FORCE LOWER CASE
} else
{ Current = "Hint! Press 'AC'";  //Help out, if error present.
};
    if (Current.indexOf("e0") != -1)
    { var epos = Current.indexOf("e");
        Current = Current.substring(0,epos+1) + Current.substring(epos+2);
    };
    if (Current.length > MAXLENGTH)
    { Current = "Aargh! Too long"; //don't allow over MAXLENGTH digits before "." ???
    };
    document.Calculator.Display.value = Current;
}

function Dot()                  //PUT IN "." if appropriate.
{
    if ( Current.length == 0)     //no leading ".", use "0."
    { Current = "0.";
    } else
    {  if (   ( Current.indexOf(".") == -1)
        &&( Current.indexOf("e") == -1)
    )
    { Current = Current + ".";
    };   };
    document.Calculator.Display.value = Current;
}

function DoExponent()
{
    if ( Current.indexOf("e") == -1 )
    { Current = Current + "e0";
        document.Calculator.Display.value = Current;
    };
}

function PlusMinus()
{
    if  (Current.indexOf("e") != -1)
    { var epos = Current.indexOf("e-");
        if (epos != -1)
        { Current = Current.substring(0,1+epos) + Current.substring(2+epos); //clip out -ve exponent
        } else
        { epos = Current.indexOf("e");
            Current = Current.substring(0,1+epos) + "-" + Current.substring(1+epos); //insert -ve exponent
        };
    } else
    {  if ( Current.indexOf("-") == 0 )
    { Current = Current.substring(1);
    } else
    { Current = "-" + Current;
    };
        if (    (eval(Current) == 0)
            && (Current.indexOf(".") == -1 )
        ) { Current = "0"; };
    };
    document.Calculator.Display.value = Current;
}

function Clear()                //CLEAR ENTRY
{ Current = "0";
    document.Calculator.Display.value = Current;
}

function AllClear()             //Clear ALL entries!
{ Current = "0";
    Operation = 0;                //clear operation
    Memory = "0";                  //clear memory
    document.Calculator.Display.value = Current;
}

function Operate(op)            //STORE OPERATION e.g. + * / etc.
{
    if (Operation != 0) { Calculate(); }; //'Press "=" if pending operation!
    // note that design is not good for showing *intermediate* results.

    if (op.indexOf("*") > -1) { Operation = 1; };       //codes for *
    if (op.indexOf("/") > -1) { Operation = 2; };       // slash (divide)
    if (op.indexOf("+") > -1) { Operation = 3; };       // sum
    if (op.indexOf("-") > -1) { Operation = 4; };       // difference

    Memory = Current;                 //store value
    // note how e.g. Current.value gives neither error nor value! ***
    Current = "";
    document.Calculator.Display.value = Current;
}

function Calculate()            //PERFORM CALCULATION (= button)
{
    if (Operation == 1) { Current = eval(Memory) * eval(Current); };
    if (Operation == 2)
    { if (eval(Current) != 0)
    { Current = eval(Memory) / eval(Current)
    } else
    { Current = "Aargh! Divide by zero"; //don't allow over MAXLENGTH digits before "." ???
    }
    };
    if (Operation == 3) { Current = eval(Memory) + eval(Current); };
    if (Operation == 4) { Current = eval(Memory) - eval(Current); };
    Operation = 0;                //clear operation
    Memory = "0";                  //clear memory
    Current = Current + "";       //FORCE A STRING!
    if (Current.indexOf("Infinity") != -1)        //eg "1e320" * 1
    { Current = "Aargh! Value too big";
    };
    if (Current.indexOf("NaN") != -1)        //eg "1e320" / "1e320"
    { Current = "Aargh! I don't understand";
    };
    document.Calculator.Display.value = Current;
    // NOTE: if no operation, nothing changes, Current is left the same!
}

function FixCurrent()
{
    Current = document.Calculator.Display.value;
    Current = "" + parseFloat(Current);
    if (Current.indexOf("NaN") != -1)
    { Current = "Aargh! I don't understand";
    };
    document.Calculator.Display.value = Current;
}