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
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
1for true,0for 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
The precedence order matters:
-
||(lowest) &&- Comparison operators
- Arithmetic operators
- 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
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
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
Available functions:
- Trig:
sin(),cos(),tan(),asin(),acos(),atan() - Log:
ln(),log(),log10(),log2(), custom base withlog(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
└──────────────────────────────────────────────────┘
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
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...
This structure ensures 2 + 3 > 4 || 0 parses correctly:
- Parse
2 + 3(addition has higher precedence) - Parse
> 4(comparison) - 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
}
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
Financial Math
» principal = 1000
» rate = 0.05
» compoundInterest = principal * (1 + rate)^10
Result: 1628.89
» isProfit = compoundInterest > principal
Result: 1
Engineering
» voltage = 12
» current = 2.5
» power = voltage * current
Result: 30
» resistance = voltage / current
Result: 4.8
» isSafeVoltage = voltage < 50
Result: 1
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
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
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
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
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
Built with Go and Cobra. Full source on GitHub.
Check out more at devuthman.vercel.app
Top comments (0)