APP / FREE / MIT EXAMPLE SOURCE

Pocket

A thoughtful little calculator

Decimal arithmetic, keyboard entry, and clear recovery from divide-by-zero errors.

Pocket, running as a native Yeho application on Windows
Actual native application capture. The downloadable source produces this example.

Take it for a spin.

Type digits and operators. Enter calculates; Backspace deletes. Click the buttons for clear and sign change.

Extract the Windows download to a writable folder and open calculator.exe. Escape closes it. Database examples keep their local data in the working folder.

See how it works.

State machines, decimal place value, events, and explicit error recovery.

A basic sequential calculator, without expression precedence or arbitrary precision.

READ / CHANGE / UNDERSTAND

The whole example is yours.

These are the exact files in the source download. Start with the model, then follow the window’s event loop. Comments explain the decisions.

model.yh
// A calculator is a state machine: entry, pending operator, and accumulator.
// Decimal parsing is explicit so the example teaches place value as well as UI.
ParseDecimal(text value) returns float
{
    float parsed=0
    float place=1
    bool fraction=false
    bool negative=false
    for i from 0 < Text.Length(value)
    {
        int c=Text.CodeUnitAt(value,i)
        if c==45
        {
            negative=true
        }
        else if c==46
        {
            fraction=true
        }
        else if c>=48 and c<=57
        {
            if fraction
            {
                place=place*0.1
                parsed=parsed+Math.IntToFloat(c-48)*place
            }
            else
            {
                parsed=parsed*10+Math.IntToFloat(c-48)
            }
        }
    }
    if negative
    {
        return -parsed
    }
    return parsed
}
class Calculator
{
    text entry="0"
    text operation=""
    float accumulator=0
    bool fresh=true
    bool fault=false
    Press(text key)
    {
        if key=="C"
        {
            this.entry="0"
            this.operation=""
            this.accumulator=0
            this.fresh=true
            this.fault=false
            return
        }
        if this.fault
        {
            return
        }
        // Percent is a unary operation on the current entry, not a second equals key.
        if key=="%"
        {
            this.entry=Text.From(ParseDecimal(this.entry)/100.0)
            this.fresh=false
            return
        }
        if key=="DEL"
        {
            if !this.fresh and Text.Length(this.entry)>1
            {
                this.entry=Text.Slice(this.entry,0,Text.Length(this.entry)-1)
            }
            else
            {
                this.entry="0"
            }
            return
        }
        if key=="+/-"
        {
            if this.entry!="0"
            {
                if Text.CodeUnitAt(this.entry,0)==45
                {
                    this.entry=Text.Slice(this.entry,1,Text.Length(this.entry))
                }
                else
                {
                    this.entry="-"+this.entry
                }
            }
            return
        }
        if key=="+" or key=="-" or key=="*" or key=="/" or key=="="
        {
            float value=ParseDecimal(this.entry)
            // Changing an operator before entering a new number does not calculate twice.
            if this.operation!="" and !this.fresh
            {
                if this.operation=="+"
                {
                    this.accumulator=this.accumulator+value
                }
                if this.operation=="-"
                {
                    this.accumulator=this.accumulator-value
                }
                if this.operation=="*"
                {
                    this.accumulator=this.accumulator*value
                }
                if this.operation=="/"
                {
                    if value==0
                    {
                        this.entry="Cannot divide by zero"
                        this.fault=true
                        return
                    }
                    this.accumulator=this.accumulator/value
                }
            }
            else if this.operation==""
            {
                this.accumulator=value
            }
            this.entry=Text.From(this.accumulator)
            this.operation=key
            if key=="="
            {
                this.operation=""
            }
            this.fresh=true
            return
        }
        if this.fresh
        {
            this.entry="0"
            this.fresh=false
        }
        if key=="."
        {
            if !Text.Contains(this.entry,".")
            {
                this.entry=this.entry+"."
            }
            return
        }
        if Text.Length(key)==1 and Text.CodeUnitAt(key,0)>=48 and Text.CodeUnitAt(key,0)<=57 and Text.Length(this.entry)<12
        {
            if this.entry=="0"
            {
                this.entry=key
            }
            else
            {
                this.entry=this.entry+key
            }
        }
    }
}