DEV Community

Cover image for Building a Terminal Calculator That Actually Does Logic: Axion
Uthman Oladele
Uthman Oladele

Posted on

Building a Terminal Calculator That Actually Does Logic: Axion

I built a calculator that does more than just add numbers. It's a full mathematical computing environment in your terminal with logical operations, comparison operators, unit conversions, and persistent memory.

This is an active project - I'm constantly working on it, adding features, fixing bugs, and improving performance.

Why Build Another Calculator?

Because most CLI calculators are just arithmetic. They add, subtract, maybe handle some trig functions, and that's it.

I wanted something that could handle real computational work:

  • Compare values and return boolean results
  • Chain logical operations with proper precedence
  • Convert between units without leaving the terminal
  • Store variables and reuse them across sessions
  • Handle scientific notation properly
  • Remember your calculation history

Basically, a calculator that works the way you think, not just the way computers add numbers.

What It Does

» 2 + 3 * 4
Result: 14

» sin(30) + cos(60)
Result: 1

» 5 > 3
Result: 1

» (5 > 3) && (2 < 4)
Result: 1

» x = sqrt(16)
Result: 4

» convert 100 cm to m
100 cm = 1 m
Enter fullscreen mode Exit fullscreen mode

It's not just a calculator - it's a mathematical environment.

The Core Features

1. Logical and Comparison Operations

This is what sets Axion apart. You can compare values and chain logical operations:

Comparison operators:

  • >, <, >=, <=, ==, !=
  • Returns 1 for true, 0 for false

Logical operators:

  • && (AND) - returns 1 if both operands are non-zero
  • || (OR) - returns 1 if at least one operand is non-zero

Proper precedence:

» 0 || 1 && 0
Result: 0  # evaluated as: 0 || (1 && 0)

» (5 > 3) && (2 < 4)
Result: 1

» temp = 25
» isComfortable = (temp > 18) && (temp < 28)
Result: 1
Enter fullscreen mode Exit fullscreen mode

The precedence order matters:

  1. || (lowest)
  2. &&
  3. Comparison operators
  4. Arithmetic operators
  5. Parentheses (highest)

2. Variable System with Persistence

Variables persist across sessions:

» radius = 5
Result: 5

» area = pi * radius^2
Result: 78.5398

» isLarge = radius >= 10
Result: 0
Enter fullscreen mode Exit fullscreen mode

Close the calculator, open it again, and your variables are still there. Stored in JSON, loaded automatically.

3. Unit Conversion System

Built-in conversions across three categories:

Length: m, cm, mm, km, in, ft, yd, mi
Weight: kg, g, mg, lb, oz, ton
Time: s, ms, min, h, d

» convert 5280 ft to mi
5280 ft = 1 mi

» convert 2.5 kg to lb
2.5 kg = 5.51156 lb
Enter fullscreen mode Exit fullscreen mode

The system prevents invalid conversions (like trying to convert kilograms to meters).

4. Scientific Computing

Full scientific notation support and mathematical functions:

» 2e-10 + 3.5E+12
Result: 3500000000000

» log(100) + ln(e) + log2(16)
Result: 9.60517

» mean(10, 20, 30, 40, 50)
Result: 30
Enter fullscreen mode Exit fullscreen mode

Available functions:

  • Trig: sin(), cos(), tan(), asin(), acos(), atan()
  • Log: ln(), log(), log10(), log2(), custom base with log(x, base)
  • Stats: mean(), median(), mode(), sum(), product()
  • Utility: abs(), ceil(), floor(), round(), sqrt(), exp()
  • Special: factorial with !, mod(), print()

5. Calculation History

Everything you calculate gets stored:

» history
┌─ Calculation History ────────────────────────────┐
│ 2 + 3 * 4                    = 14
│ sin(30) + cos(60)            = 1
│ x = sqrt(16)                 = 4
│ convert 100 cm to m          = 1 m
└──────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Stored in JSON, persists across sessions.

How It Works Under the Hood

Axion uses a proper compiler-style pipeline:

User Input
    ↓
Tokenizer → Breaks input into tokens (numbers, operators, functions)
    ↓
Parser → Builds Abstract Syntax Tree (AST) with proper precedence
    ↓
Evaluator → Walks the AST and computes the result
    ↓
Output → Color-coded terminal display
Enter fullscreen mode Exit fullscreen mode

The Parser

This was the hardest part. Getting operator precedence right took multiple attempts.

The parser uses recursive descent with precedence climbing. Each precedence level gets its own parsing function:

// Simplified version
func (p *Parser) parseExpression() *Node {
    return p.parseLogicalOr()  // Lowest precedence
}

func (p *Parser) parseLogicalOr() *Node {
    left := p.parseLogicalAnd()
    // Handle || operators
    return left
}

func (p *Parser) parseLogicalAnd() *Node {
    left := p.parseComparison()
    // Handle && operators
    return left
}

func (p *Parser) parseComparison() *Node {
    left := p.parseAddition()
    // Handle >, <, ==, etc.
    return left
}

// And so on...
Enter fullscreen mode Exit fullscreen mode

This structure ensures 2 + 3 > 4 || 0 parses correctly:

  1. Parse 2 + 3 (addition has higher precedence)
  2. Parse > 4 (comparison)
  3. Parse || 0 (logical OR has lowest precedence)

Token Types

The tokenizer recognizes:

  • NUMBER - integers, decimals, scientific notation
  • OPERATOR - +, -, *, /, ^, !
  • COMPARISON - >, <, >=, <=, ==, !=
  • LOGICAL - &&, ||
  • FUNCTION - sin, cos, log, etc.
  • LPAREN/RPAREN - parentheses for grouping
  • COMMA - function argument separator

The Evaluator

Walks the AST recursively. Each node type has its own evaluation logic:

switch node.Type {
case NODE_NUMBER:
    return parseFloat(node.Value)

case NODE_OPERATOR:
    left := Eval(node.Left)
    right := Eval(node.Right)
    return applyOperator(node.Value, left, right)

case NODE_COMPARISON:
    left := Eval(node.Left)
    right := Eval(node.Right)
    if compareValues(node.Value, left, right) {
        return 1.0  // true
    }
    return 0.0  // false

case NODE_AND:
    left := Eval(node.Left)
    right := Eval(node.Right)
    if left != 0 && right != 0 {
        return 1.0
    }
    return 0.0
}
Enter fullscreen mode Exit fullscreen mode

Comparisons and logical operations return 1.0 (true) or 0.0 (false), which lets you use them in further calculations.

Real-World Use Cases

Physics Calculations

» F = 9.8 * 75  # Force = mass * acceleration
Result: 735

» isValidForce = F > 0 && F < 1000
Result: 1

» E = F * 10    # Energy = force * distance
Result: 7350
Enter fullscreen mode Exit fullscreen mode

Financial Math

» principal = 1000
» rate = 0.05
» compoundInterest = principal * (1 + rate)^10
Result: 1628.89

» isProfit = compoundInterest > principal
Result: 1
Enter fullscreen mode Exit fullscreen mode

Engineering

» voltage = 12
» current = 2.5
» power = voltage * current
Result: 30

» resistance = voltage / current
Result: 4.8

» isSafeVoltage = voltage < 50
Result: 1
Enter fullscreen mode Exit fullscreen mode

Project Structure

Axion/
├── main.go              # Entry point
├── cmd/                 # Cobra CLI commands
│   └── cmd.go          # REPL implementation
├── tokenizer/          # Lexical analysis
│   └── tokenizer.go
├── parser/             # Syntax analysis
│   └── parser.go
├── evaluator/          # Expression evaluation
│   └── evaluator.go
├── units/              # Unit conversion
│   └── units.go
├── history/            # History management
│   └── history.go
└── constants/          # Constants loading
    └── constants.go
Enter fullscreen mode Exit fullscreen mode

Each module has a single responsibility. The Cobra framework handles CLI commands and the interactive REPL.

Test Coverage

Core computational modules have solid test coverage:

  • Units: 100% (unit conversion system)
  • Tokenizer: 94% (lexical analysis)
  • Parser: 76.4% (AST construction)
  • Evaluator: 74.5% (mathematical computation)

The utility modules (constants, history, settings) and CLI handlers don't have tests yet. They're next on the list.

Installation

Quick install (Linux/macOS):

git clone https://github.com/codetesla51/Axion.git
cd Axion
chmod +x install.sh
./install.sh
source ~/.bashrc
Enter fullscreen mode Exit fullscreen mode

The script builds the binary, creates a symlink in ~/.local/bin, and adds it to your PATH.

Manual install:

git clone https://github.com/codetesla51/Axion.git
cd Axion
go build -o axion
./axion
Enter fullscreen mode Exit fullscreen mode

What I Learned

Operator precedence is harder than it looks. Getting && to bind tighter than || while both bind looser than comparisons required multiple parsing passes.

Recursive descent parsing is elegant. Once you understand the pattern (each precedence level calls the next higher level), it's straightforward to extend.

The Cobra framework is powerful. Building a CLI with subcommands, flags, and help text is trivial with Cobra. Would have taken way longer without it.

Testing matters. The bugs I caught with unit tests would have been nightmares to debug in production. Writing tests as you go saves time.

JSON is good enough for most storage needs. No need for a database when you're just storing variables and history. JSON files work fine and are human-readable.

What's Next

Potential additions:

  • Matrix operations
  • Complex numbers
  • Custom function definitions
  • Graphing capabilities
  • More unit categories (temperature, pressure, etc.)
  • Export calculations to different formats

But the core is solid. It does what it needs to do.

Try It

git clone https://github.com/codetesla51/Axion.git
cd Axion
./install.sh
axion
Enter fullscreen mode Exit fullscreen mode

Then try some calculations:

» x = 10
» y = 20
» (x > 5) && (y < 25)
Result: 1

» convert 5 km to mi
5 km = 3.10686 mi

» print(sin(45))
0.707107
Enter fullscreen mode Exit fullscreen mode

Built with Go and Cobra. Full source on GitHub.

Check out more at devuthman.vercel.app

Top comments (0)