ccalc
A fast terminal calculator with Octave/MATLAB syntax and script support β one binary, no runtime.
Current version: 0.25.0 β see CHANGELOG for history.
Why ccalc?
Octave is hundreds of megabytes. Python requires a runtime. ccalc is a single self-contained binary that starts instantly and works anywhere: interactive sessions, shell scripts, CI pipelines, Docker containers.
It speaks Octave/MATLAB syntax β familiar to engineers and scientists β without requiring a full language installation.
| Who | Typical use |
|---|---|
| Embedded / systems engineer | Arithmetic, hex/bin conversions, bit masks |
| DevOps / SRE | Quick calculations in scripts and pipelines |
| Scientist / student | Interactive session with variables and math functions |
| MATLAB / Octave user | Familiar syntax, no heavy installation |
Installation
The binary is placed at target/release/ccalc. Copy it anywhere on your PATH.
Usage
ccalc [OPTIONS] start interactive REPL
ccalc "EXPR" evaluate expression and print result
ccalc script.m run a script file
echo "EXPR" | ccalc pipe mode β silent, result only
ccalc < formulas.txt read expressions from file
| Option | Description |
|---|---|
-h, --help |
Show help |
-v, --version |
Show version |
Modes
Interactive REPL
Run without arguments:
$ ccalc
[ 0 ]:
Single expression
Pass an expression as a command-line argument:
$ ccalc "2 ^ 32"
4294967296
$ ccalc "sqrt(144)"
12
Script file
Pass a script file as an argument β any file that exists on disk:
$ ccalc script.m
$ ccalc script.calc
$ ccalc examples/mortgage.calc
Pipe / non-interactive mode
When stdin is not a terminal, ccalc runs silently β no prompt, one result per line. ans carries over across lines, so you can chain calculations:
$ echo "sin(pi / 6)" | ccalc
0.5
$ printf "10\n+ 5\n* 2" | ccalc
10
15
30
$ ccalc < formulas.txt
All commands work in script/pipe mode: exit/quit stop processing, who/clear/ws/wl/save/load manage variables, format controls number display, hex/dec/bin/oct/base control number base. cls is ignored.
How it works
The prompt shows ans β the result of the last expression. Every new expression updates it. Expressions that start with an operator automatically use ans as the left-hand operand (partial expressions):
[ 0 ]: 100
[ 100 ]: / 4
[ 25 ]: + 5
[ 30 ]: ^ 2
[ 900 ]:
Arithmetic
Operators
| Operator | Description | Precedence |
|---|---|---|
^ |
Power (right-associative) | highest |
* / |
Multiply, divide | medium |
+ - |
Add, subtract | lowest |
[ 0 ]: 2 + 3 * 4
[ 14 ]:
[ 0 ]: 2 ^ 3 ^ 2
[ 512 ]: (right-associative: 2^(3^2) = 2^9)
Grouping
[ 0 ]: (2 + 3) * 4
[ 20 ]:
Unary minus
[ 0 ]: -5
[ -5 ]:
[ 0 ]: -(3 + 2)
[ -5 ]:
Ergonomics
Implicit multiplication
A number or closing parenthesis immediately before ( multiplies without an explicit *:
[ 0 ]: 2(3 + 1)
[ 8 ]:
[ 0 ]: (2 + 1)(4 - 1)
[ 9 ]:
Constants
| Name | Value |
|---|---|
pi |
3.14159265358979... |
e |
2.71828182845904... |
nan |
Not-a-Number (IEEE 754 NaN) |
inf |
Positive infinity |
i, j |
Imaginary unit 0 + 1i (can be reassigned) |
ans |
Result of the last expression |
ans is the implicit accumulator β it is updated after every expression and can be used anywhere in an expression:
[ 9 ]: ans * 2 + 1
[ 19 ]:
[ 25 ]: sqrt(ans)
[ 5 ]:
Math functions
If called with empty parentheses, ans is used as the argument.
One-argument
| Function | Description |
|---|---|
sqrt(x) |
Square root |
abs(x) |
Absolute value |
floor(x) |
Round down to integer |
ceil(x) |
Round up to integer |
round(x) |
Round to nearest integer |
sign(x) |
Sign: β1, 0, or 1 |
log(x) |
Base-10 logarithm |
ln(x) |
Natural logarithm (base e) |
exp(x) |
e raised to the power x |
sin(x) |
Sine (radians) |
cos(x) |
Cosine (radians) |
tan(x) |
Tangent (radians) |
asin(x) |
Inverse sine (radians) |
acos(x) |
Inverse cosine (radians) |
atan(x) |
Inverse tangent (radians) |
Two-argument
| Function | Description |
|---|---|
atan2(y, x) |
Four-quadrant inverse tangent (radians) |
mod(a, b) |
Remainder, sign follows divisor (Octave convention) |
rem(a, b) |
Remainder, sign follows dividend |
max(a, b) |
Larger of two values |
min(a, b) |
Smaller of two values |
hypot(a, b) |
β(aΒ²+bΒ²), numerically stable |
log(x, base) |
Logarithm of x to an arbitrary base |
[ 0 ]: sqrt(144)
[ 12 ]:
[ 0 ]: sin(pi / 6)
[ 0.5 ]:
[ 0 ]: hypot(3, 4)
[ 5 ]:
[ 0 ]: atan2(1, 1) * 180 / pi
[ 45 ]:
[ 0 ]: mod(-1, 3)
[ 2 ]:
[ 16 ]: sqrt() same as sqrt(16)
[ 4 ]:
Functions can be nested and combined:
[ 0 ]: sqrt(abs(-25))
[ 5 ]:
[ 0 ]: max(hypot(3, 4), 6)
[ 6 ]:
Variables
Any identifier can be used as a variable. ans is the implicit variable
updated after every standalone expression.
Assignment
name = expr shows the assigned value and does not update ans.
Append ; to suppress output.
[ 0 ]: rate = 0.06 / 12
rate = 0.005
[ 0 ]: n = 360
n = 360
[ 0 ]: 200000 * 0.005
[ 1000 ]:
Using variables
[ 0 ]: rate = 0.07
rate = 0.07
[ 0 ]: 1000 * (1 + rate) ^ 10
[ 1967.1513573 ]:
View and clear
| Command | Action |
|---|---|
who |
Show all defined variables and their values |
clear |
Clear all variables |
clear name |
Clear a single variable |
ws / save |
Save workspace to ~/.config/ccalc/workspace.toml |
wl / load |
Load workspace from file |
save('path.mat') |
Save to explicit path |
save('path.mat', 'x', 'y') |
Save specific variables only |
load('path.mat') |
Load from explicit path |
[ 0 ]: rate = 0.05
[ 0 ]: n = 12
[ 0 ]: rate + n
[ 12.05 ]: who
ans = 12.05
n = 12
rate = 0.05
[ 12.05 ]: clear rate
[ 12.05 ]: clear
Matrices
Create matrices using bracket syntax. Separate elements with spaces or commas;
separate rows with ;:
[ 0 ]: A = [1 2; 3 4]
A =
1 2
3 4
[ [2Γ2] ]: B = [5 6; 7 8]
B =
5 6
7 8
[ [2Γ2] ]: A + B
ans =
6 8
10 12
Scalar operations are element-wise:
[ [2Γ2] ]: 2 * A
ans =
2 4
6 8
Matrix multiplication and transpose
[ [2Γ2] ]: A * B matrix multiplication
[ [2Γ2] ]: A' transpose
[ [2Γ2] ]: v' * v dot product (row Γ column)
Element-wise operators
[ [2Γ2] ]: A .* B element-wise multiply
[ [2Γ2] ]: A ./ B element-wise divide
[ [2Γ2] ]: A .^ 2 element-wise power
Matrix built-ins
| Function | Description |
|---|---|
zeros(m, n) |
All-zeros matrix |
ones(m, n) |
All-ones matrix |
eye(n) |
nΓn identity matrix |
size(A) |
[rows cols] as a 1Γ2 matrix |
size(A, dim) |
Number of rows (dim=1) or cols (dim=2) |
length(A) |
max(rows, cols) |
numel(A) |
Total number of elements |
trace(A) |
Sum of diagonal elements |
det(A) |
Determinant |
inv(A) |
Inverse matrix |
A \ b |
Solve linear system A*x = b |
qr(A) |
QR decomposition |
lu(A) |
LU decomposition with partial pivoting |
chol(A) |
Cholesky factor (SPD matrices) |
svd(A) |
Singular value decomposition |
eig(A) |
Eigenvalue decomposition |
rank(A) |
Numerical rank |
null(A) |
Null-space basis |
orth(A) |
Column-space orthonormal basis |
cond(A) |
Condition number |
pinv(A) |
Moore-Penrose pseudoinverse |
norm(A) |
Matrix 2-norm (largest singular value) |
The REPL prompt shows the matrix dimensions when ans is a matrix.
who displays dimensions: A = [2Γ2 double].
ws saves only scalar variables; matrices are not persisted.
Range operator
Generate row vectors with the : operator:
1:5 % [1 2 3 4 5]
1:2:9 % [1 3 5 7 9] (start:step:stop)
0:0.5:2 % [0 0.5 1 1.5 2]
5:-1:1 % [5 4 3 2 1]
Ranges work inside matrix literals and can be mixed with scalars:
[ 0 ]: v = 1:4
v =
1 2 3 4
[ [1Γ4] ]: [0, 1:3, 10]
ans =
0 1 2 3 10
linspace(a, b, n) generates n evenly spaced values between a and b:
linspace(0, 1, 5) % [0 0.25 0.5 0.75 1]
linspace(1, 5, 5) % [1 2 3 4 5]
Indexing
All indices are 1-based (Octave convention). A name that exists as a variable always indexes β variables shadow built-in function names.
[ 0 ]: v = 1:5
v =
1 2 3 4 5
[ [1Γ5] ]: v(3)
[ 3 ]: v(2:4)
ans =
2 3 4
[ [1Γ3] ]: A = [1 2 3; 4 5 6; 7 8 9]
[ [3Γ3] ]: A(2,3)
[ 6 ]: A(:,2)
ans =
2
5
8
[ [3Γ1] ]: A(1:2, 2:3)
ans =
2 3
5 6
Indexed assignment
All read forms work as write targets. A scalar RHS is broadcast to all selected positions:
= zeros;
= 42; % set element 3
= ; % slice assignment
= 99; % broadcast scalar to three positions
= 0; % reset all elements
= zeros;
= 7; % 2-D element
= ; % full column
= eye; % submatrix
Growing vectors β assigning beyond the current length pads with zeros.
end+1 is the idiomatic append:
= ;
for = 1:8
= ^2; % append k-squared
end
% v = [1 4 9 16 25 36 49 64]
Logical (boolean mask) indexing β a 0/1 mask selects positions for reading or writing:
= ;
= ; % read: [35 41 33]
= 30; % write: cap all hot values at 30
Vector & Data Utilities
Special constants and predicates
| Function / Constant | Description |
|---|---|
nan |
IEEE 754 Not-a-Number (propagates through arithmetic) |
inf |
Positive infinity (-inf for negative) |
nan(n) |
nΓn matrix filled with NaN |
nan(m, n) |
mΓn matrix filled with NaN |
isnan(x) |
1 if NaN, else 0 (element-wise) |
isinf(x) |
1 if Β±Inf, else 0 (element-wise) |
isfinite(x) |
1 if finite, else 0 (element-wise) |
Reductions
For vectors (1ΓN or NΓ1) these return a scalar. For MΓN matrices (M>1, N>1) they operate column-wise and return a 1ΓN row vector.
| Function | Description |
|---|---|
sum(v) |
Sum of elements |
prod(v) |
Product of elements |
mean(v) |
Arithmetic mean |
min(v) |
Minimum element |
max(v) |
Maximum element |
any(v) |
1 if any element is non-zero |
all(v) |
1 if all elements are non-zero |
norm(v) |
Euclidean (L2) norm |
norm(v, p) |
General Lp norm (p = inf supported) |
cumsum(v) |
Cumulative sum (same shape as input) |
cumprod(v) |
Cumulative product (same shape) |
Data manipulation
| Function | Description |
|---|---|
sort(v) |
Sort ascending (vectors only) |
reshape(A, m, n) |
Reshape to mΓn, column-major (MATLAB element order) |
fliplr(v) |
Reverse column order |
flipud(v) |
Reverse row order |
find(v) |
1-based column-major indices of non-zero elements |
find(v, k) |
First k non-zero indices |
unique(v) |
Sorted unique elements as a 1ΓN row vector |
end in index expressions
Inside (...) index positions, end resolves to the length of that dimension.
Arithmetic on end is fully supported:
[ 0 ]: v = [10 20 30 40 50];
[ [1Γ5] ]: v(end)
[ 50 ]: v(end-1)
[ 40 ]: v(3:end)
ans =
30 40 50
[ [1Γ3] ]: A = [1:4; 5:8; 9:12];
[ [3Γ4] ]: A(end, :)
ans =
9 10 11 12
[ [1Γ4] ]: A(1:end-1, 2:end)
ans =
2 3 4
6 7 8
[ 0 ]: data = [3 1 4 1 5 9 2 6];
[ [1Γ8] ]: sort(data)
ans =
1 1 2 3 4 5 6 9
[ [1Γ8] ]: find(data > 4)
ans =
5 6 8
[ [1Γ3] ]: cumsum(data)
ans =
3 4 8 9 14 23 25 31
Statistics & Random Numbers
Random number generation
| Function | Description |
|---|---|
rand() |
Scalar uniform in [0, 1) |
rand(n) |
nΓn uniform matrix |
rand(m, n) |
mΓn uniform matrix |
randn() / randn(n) / randn(m, n) |
Standard-normal scalar or matrix |
randi(max) |
Random integer in [1, max] |
randi(max, n) / randi(max, m, n) |
Matrix of random integers |
randi([lo hi], ...) |
Integers from [lo, hi] |
rng(seed) |
Seed RNG β same seed β same sequence |
rng('shuffle') |
Reseed from OS entropy |
= randn % reproducible 5-element sequence
= % ten dice rolls
Descriptive statistics
All functions operate column-wise on MΓN matrices and collapse to a scalar for vectors.
| Function | Description |
|---|---|
std(v) |
Sample standard deviation (nβ1 denominator) |
std(v, 1) |
Population standard deviation (n denominator) |
var(v) / var(v, 1) |
Sample / population variance |
median(v) |
Median (linear interpolation for even length) |
mode(v) |
Most frequent value; smallest wins on ties |
cov(v) |
Variance of a vector |
cov(A) |
NΓN covariance matrix of an mΓN data matrix |
skewness(v) |
Population skewness: m3 / m2^(3/2) β 0 = symmetric |
kurtosis(v) |
Population kurtosis: m4 / m2^2 β β 3 for normal |
prctile(v, p) |
p-th percentile; p can be a vector |
iqr(v) |
Interquartile range: prctile(75) β prctile(25) |
zscore(v) |
Standardise: (v β mean) / std, same shape |
hist(data) |
ASCII bar chart to stdout (10 bins default) |
hist(data, n) |
ASCII bar chart with n bins |
histc(data, edges) |
Count vector for user-supplied bin edges |
Normal distribution
| Function | Description |
|---|---|
normcdf(x) |
P(Z β€ x), Z ~ N(0, 1) |
normcdf(x, mu, s) |
P(X β€ x), X ~ N(mu, sΒ²) |
normpdf(x) |
Standard normal PDF |
normpdf(x, mu, s) |
General normal PDF |
erf(x) |
Gauss error function |
erfc(x) |
1 β erf(x) |
normcdf - normcdf % 0.6827 (68% rule)
normcdf - normcdf % 0.9545 (95% rule)
See examples/statistics.calc for a full worked example.
Bitwise Functions
All require non-negative integer arguments β combine naturally with 0xFF, 0b1010, 0o17 literals.
| Function | Description |
|---|---|
bitand(a, b) |
Bitwise AND |
bitor(a, b) |
Bitwise OR |
bitxor(a, b) |
Bitwise XOR |
bitshift(a, n) |
Left shift (n > 0) / logical right shift (n < 0) |
bitnot(a) |
NOT within 32-bit window |
bitnot(a, bits) |
NOT within bits-wide window (1β53) |
[ 0 ]: bitand(0xFF, 0x0F)
[ 15 ]: bitxor(0xFF, 0x0F)
[ 240 ]: bitshift(1, 8)
[ 256 ]: bitnot(5, 8)
[ 250 ]:
Comparison & Logical Operators
Comparison operators return 1 (true) or 0 (false):
| Operator | Meaning |
|---|---|
== |
Equal |
~= or != |
Not equal |
< |
Less than |
> |
Greater than |
<= |
Less or equal |
>= |
Greater or equal |
Logical operators:
| Operator | Meaning |
|---|---|
~expr or !expr |
Logical NOT (unary) |
&& |
Logical AND |
|| |
Logical OR |
! and != are C/shell-style aliases for ~ and ~=.
Precedence (low β high): || β && β comparisons β : β +/- β *// β ^ β unary (-, ~) β primary
[ 0 ]: 3 > 2
[ 1 ]:
[ 0 ]: 3 == 3
[ 1 ]:
[ 0 ]: 5 ~= 5
[ 0 ]:
[ 0 ]: ~0
[ 1 ]:
[ 0 ]: 2 > 1 && 3 > 2
[ 1 ]:
Element-wise on matrices β comparisons produce a 0/1 mask of the same shape:
[ 0 ]: v = [1 2 3 4 5];
[ 0 ]: v > 3
ans =
0 0 0 1 1
[ [1Γ5] ]: v .* (v > 3) % zero out elements <= 3
ans =
0 0 0 4 5
Strings
ccalc supports two string types, matching MATLAB/Octave:
Char arrays β single quotes
[ 0 ]: greeting = 'Hello!'
greeting = Hello!
[ 'Hello!' ]: length(greeting)
[ 6 ]:
Char arrays are numeric-compatible β arithmetic converts each character to its ASCII code:
[ 0 ]: 'a' + 0 % ASCII of 'a'
[ 97 ]:
[ 0 ]: 'abc' + 1 % shift each code by 1
ans =
98 99 100
String objects β double quotes
[ 0 ]: s = "Hello"
s = Hello
[ '"Hello"' ]: s + ", World!"
[ '"Hello, World!"' ]:
String objects use + for concatenation.
String built-ins
| Function | Description |
|---|---|
num2str(x) / num2str(x, N) |
Number β char array (N digits) |
str2num(s) |
Char array β number (error on failure) |
str2double(s) |
Char array β number (NaN on failure) |
strcat(a, b, ...) |
Concatenate strings |
strcmp(a, b) |
Case-sensitive equality β 0/1 |
strcmpi(a, b) |
Case-insensitive equality β 0/1 |
lower(s) / upper(s) |
Case conversion |
strtrim(s) |
Strip leading/trailing whitespace |
strrep(s, old, new) |
Find-and-replace |
sprintf(fmt, v1, ...) |
Format string (C printf), returns char array |
ischar(s) |
1 if char array, else 0 |
isstring(s) |
1 if string object, else 0 |
length(s), numel(s), and size(s) all work on strings.
Complex Numbers
i and j are pre-set to the imaginary unit 0 + 1i. Complex numbers
work with the same operators and functions as real numbers:
[ 0 ]: 3 + 4*i
[ 3 + 4i ]: abs(ans)
[ 5 ]: angle(3 + 4*i) * 180/pi
[ 53.1301023542 ]:
Create, decompose, and manipulate:
z = complex(3, 4) % 3 + 4i
real(z) % 3
imag(z) % 4
conj(z) % 3 - 4i
z' % 3 - 4i (conjugate transpose)
isreal(z) % 0
Arithmetic works for all combinations of complex and real scalars:
(3 + 4*i) * (1 - 2*i) % 11 - 2i
i^2 % -1 (exact integer exponentiation)
i^4 % 1
Complex built-ins
| Function | Description |
|---|---|
real(z) |
Real part (real(5) β 5) |
imag(z) |
Imaginary part (imag(5) β 0) |
abs(z) |
Modulus sqrt(reΒ²+imΒ²) |
angle(z) |
Argument atan2(im, re), radians |
conj(z) |
Complex conjugate re - im*i |
complex(re, im) |
Construct from two real scalars |
isreal(z) |
1 if im == 0, else 0 |
Note: Complex matrices (
[1+2i, 3]) are not yet supported.
Formatted Output
fprintf β print to stdout
fprintf(fmt, v1, v2, ...) prints formatted output using C-style conversion specifiers:
| Specifier | Meaning |
|---|---|
%d, %i |
Integer (truncated to whole number) |
%f |
Fixed-point decimal |
%e |
Scientific notation (1.23e+04) |
%g |
Shorter of %f and %e |
%s |
String (char array or string object) |
%% |
Literal % |
Width, precision, and alignment flags follow standard C printf conventions:
fprintf('%8.3f\n', pi) % 3.142
fprintf('%-10s|\n', 'hi') % hi |
fprintf('%+.4e\n', 1000) % +1.0000e+03
fprintf('%05d\n', 42) % 00042
Escape sequences: \n (newline), \t (tab), \\ (backslash).
When there are more arguments than conversion specifiers, the format string repeats (Octave behaviour):
fprintf('%d ', 1, 2, 3) % 1 2 3
sprintf β format to string
Same as fprintf but returns the result as a char array instead of printing:
label = sprintf('R = %.1f Ohm', R);
disp(label)
File I/O
File handles
= fopen; % open for writing; returns fd (β₯3) or -1 on failure
fprintf; % write formatted text to file
fclose; % close; returns 0 or -1
= fopen; % open for reading
line = fgetl; % read one line, newline stripped; -1 at EOF
= fgets; % read one line, newline kept
fclose;
fclose; % close all open file handles
Modes: 'r' read, 'w' write (create/truncate), 'a' append, 'r+' read+write.
File descriptor 1 = stdout, 2 = stderr.
Delimiter-separated data
dlmwrite; % write matrix, comma-separated
dlmwrite; % explicit delimiter
= dlmread; % read; auto-detect ',' / '\t' / whitespace
= dlmread;
CSV tables β readmatrix / readtable / writetable
Higher-level CSV functions with header row support and type inference.
% readmatrix β numeric data, auto-skip non-numeric header
= % header skipped automatically
= % explicit delimiter
% readtable β first row always the header; returns Struct of columns
=
= T.score % Matrix NΓ1 (numeric column)
= T.name % Cell (string column)
% writetable β write Struct to CSV with header row
T.name = ;
T.score = ;
Column type inference in readtable: if every cell in a column is parseable
as a number (empty cells β NaN), the column becomes a Matrix NΓ1; otherwise
a Cell of Str. writetable accepts the same column types and applies RFC 4180
quoting automatically for values containing the delimiter or quotes.
Filesystem queries
% 1 if the path is an existing file, else 0
% 1 if the path is an existing directory, else 0
pwd % current working directory as a char array
exist % 1 if variable x is in the workspace
exist % 2 if file exists on disk (MATLAB numeric code)
exist % checks workspace first, then filesystem
Workspace with explicit path
save % save all variables to default path
save % save all variables to named file
save % save specific variables only
load % load variables from named file
path = 'session.mat';
save % variable reference also accepted
Scalars, char arrays, and string objects are persisted. Matrices, complex values, and functions are always skipped.
JSON
Requires the
jsonfeature flag:Without this flag, calling either built-in returns an informative error. Both names always appear in tab completion.
% Decode β JSON string β ccalc Value
=
s.name % β 'Alice' (Str)
s.scores % β [88 92 75] (Matrix 1Γ3)
% β [1 2 3] (all-numeric β Matrix)
% β {1, 'two', 1} (mixed β Cell)
% β NaN
% β 1
% Encode β ccalc Value β compact JSON string
% β '42.0'
% β '"hello"'
% β '[1.0,2.0,3.0]'
% β '{"name":"Alice","scores":[88.0,92.0,75.0]}'
% β 'null'
Type mapping:
JSON β ccalc (jsondecode) |
ccalc β JSON (jsonencode) |
|---|---|
object {β¦} β Struct |
Struct β object |
all-numeric array β Matrix 1ΓN |
Matrix 1ΓN β flat array |
mixed array β Cell |
Matrix MΓN β array of row arrays |
string β Str |
Cell β array |
number β Scalar |
Scalar(NaN) β null |
true/false β Scalar (1/0) |
Str/StringObj β string |
null β Scalar(NaN) |
Inf/Complex/Function β error |
Reading a JSON file (combine with fgetl for single-line JSON):
= fopen;
= fgetl;
fclose;
= ;
MAT Files
Requires the
matfeature flag:Without this flag, calling
load('*.mat')returns an informative error.loadalways appears in tab completion.
% Assignment form β returns a Struct of all variables in the file
= load;
data.score % Scalar
data.readings % Matrix
data.label % Str (char array)
data.sensor.gain % nested Struct field
% Bare form β injects all variables directly into the workspace
load
% now a workspace variable
Type mapping:
| MAT type | ccalc value |
|---|---|
double (1Γ1) |
Scalar |
double (MΓN) |
Matrix |
char array |
Str |
struct |
Struct |
| struct array | StructArray |
cell array |
Cell |
| null / empty | Scalar(NaN) |
Backed by matrw = "=0.1.4". Complex and sparse matrices are not yet supported.
Control Flow
Multi-line control flow blocks are supported in both REPL and script mode.
REPL multi-line input
The REPL detects unclosed blocks and buffers lines with a continuation prompt until end is seen. Press Ctrl+C to cancel an incomplete block.
[ 0 ]: for k = 1:3
>> fprintf('%d\n', k)
>> end
1
2
3
if / elseif / else
= 73;
if >= 90
= 'A';
elseif >= 70
= 'C';
else
= 'F';
end
fprintf
for
Iterates over a range (or matrix columns):
= 0;
for = 1:10
+= ^ 2;
end
fprintf % 385
while
= 1.0;
while abs > 1e-12
= / 2;
end
fprintf
break and continue
for = 1:20
if mod == 0
continue % skip even numbers
end
if > 9
break % stop after 9
end
fprintf
end
switch / case / otherwise
switch
case 200
= 'OK';
case 404
= 'Not Found';
otherwise
= 'Unknown';
end
fprintf
No fall-through β only the first matching case runs. Works with scalars and strings. otherwise is optional.
do ... until
Octave post-test loop β the body always runs at least once, then the condition is checked:
= 1;
*= 2;
fprintf % 128
break and continue work inside do...until.
User-defined functions
Named functions use the function ... end block syntax:
function result =
if <= 1
= 1;
return
end
= * ;
end
% 5040
Multiple return values are separated with [...]:
function [mn, mx, avg]
= min;
= max;
= mean;
end
= ;
= ; % lo=1 hi=9 mu=5.14...
Use ~ to discard individual outputs:
= ; % top = 30
nargin β number of arguments actually passed; useful for optional parameters:
function y =
if nargin < 2
exp = 2;
end
= ^ exp;
end
% 25 (default exponent)
% 256
Functions are recursive. They see all other Function/Lambda values
defined in the workspace at the time of the call.
Anonymous functions (lambdas)
= @ ^ 2;
= @ sqrt;
% 49
% 5
Lambdas capture the enclosing environment at definition time (lexical closure):
= 0.05;
= @ * ^ ;
= 0.99; % does not affect the lambda
% 1628.89 (uses captured 5%)
Pass lambdas to named functions (higher-order functions):
function s =
= / ;
= 0;
for = 1:
= + * ;
+= ;
end
*= ;
end
midpoint % 0.333333
midpoint % 2.000001
Functions can return functions:
function f =
= @ + ;
end
= ;
% 8
% 16
Structs and Struct Arrays
Scalar structs group named fields of any type. Fields are accessed with . notation; intermediate structs are created automatically on write.
pt.x = 3;
pt.y = 4;
dist = sqrt % 5
car.engine. = 190; % nested struct created automatically
car.engine. % 190
= struct;
p.name % Alice
p.score % 98.5
Struct arrays store collections of records. Use indexed assignment to create or grow the array:
. = 1; . = 0;
. = 3; . = 4;
. = 0; . = 5;
numel % 3
. % 3
= pts.x % [1 3 0] β field collection across all elements
= pts.y % [0 4 5]
Struct utilities:
fieldnames % cell array of field names (insertion order)
isfield % 1 if field exists, else 0
rmfield % copy of s without field 'x'
isstruct % 1 if v is a struct or struct array, else 0
Structs are displayed MATLAB-style:
s =
struct with fields:
x: 3
y: 4
pts =
1Γ3 struct array with fields:
x
y
Nested or complex fields show compact inline: [1Γ1 struct], [1Γ3 double].
Cell Arrays
Cell arrays are heterogeneous 1-D containers where each element can be any value β scalar, matrix, string, or function handle.
= ; % cell literal
% 42 (brace indexing, 1-based)
% hello
% [1Γ3 double]
= 'new'; % auto-grows beyond current size
iscell % 1
numel % 4
cell % pre-allocated 1Γ5 cell of zeros
varargin / varargout β variadic functions:
function s =
= 0;
for = 1:numel
+= varargin;
end
end
% 6
% 30
cellfun / arrayfun β apply a function to each element:
cellfun % [1 2 3]
cellfun % [2 8 18]
arrayfun % [1 4 9 16]
@funcname handles β wrap any builtin or named function as a callable:
= @sqrt; % 4
= @abs; % 7.5
case {v1, v2} in switch β matches if the switch expression equals any element:
switch
case
disp
otherwise
disp
end
run() / source()
Execute a script file in the current workspace. Variables defined in the script persist in the caller's scope (MATLAB run semantics):
= 252; = 105;
run % looks for euclid_helper.calc, then .m
fprintf % g was set by the helper
% Octave alias for run()
Extension resolution for bare names: .calc is tried first (native), then .m (compatibility).
Search path (addpath / rmpath / path / genpath)
A session search path controls where run() looks for scripts. Entries are checked after the current working directory:
addpath % prepend β highest priority
addpath % append β lowest priority
rmpath % remove entry
path % display current path
addpath % add /my/libs and all its subdirectories
genpath(dir) returns dir and all its subdirectories as a path-separator-delimited string (; on Windows, : on Unix).
Persistent paths can be configured in ~/.config/ccalc/config.toml:
= [
"~/.config/ccalc/lib", # exact directory only
"/home/user/scripts/", # trailing slash β dir + all subdirectories
]
Error Handling
Scripts can raise, catch, and recover from runtime errors without crashing the session.
% Raise an error with a formatted message
error
% Print a warning and continue
warning
% try/catch β anonymous (swallow error)
try
=
catch
= 0
end
% try/catch β named: e.message holds the error string
try
= inv *
catch
fprintf
= zeros
end
% Inline fallback β default evaluated only on error
= try % 0 if s is not a valid number
% Protected call β returns [ok, value_or_message]
=
if ~
fprintf
end
% Last error message
lasterr % message from most recent error
lasterr % clear
Compound assignment operators
| Operator | Meaning |
|---|---|
x += e |
x = x + e |
x -= e |
x = x - e |
x *= e |
x = x * e |
x /= e |
x = x / e |
x++ |
x = x + 1 |
x-- |
x = x - 1 |
++x |
x = x + 1 |
--x |
x = x - 1 |
All forms desugar at parse time β no performance penalty.
REPL commands
| Command | Action |
|---|---|
exit, quit |
Quit |
cls |
Clear the screen |
help, ? |
Show cheatsheet |
help <topic> |
Detailed help (see below) |
who |
List all defined variables |
clear |
Clear all variables |
clear <name> |
Clear a single variable |
format |
Reset to short (5 significant digits) |
format <mode> |
Switch display mode (see below) |
format <N> |
N decimal places (e.g. format 4) |
hex / dec / bin / oct |
Switch display base |
base |
Show ans in all four bases |
ws / save |
Save workspace to disk |
wl / load |
Load workspace from disk |
save('path') |
Save to explicit file |
save('path', 'x', 'y') |
Save specific variables |
load('path') |
Load from explicit file |
| Ctrl+C / Ctrl+D | Quit |
Help topics: syntax functions userfuncs cells structs errors bases vars script format matrices files control examples
Keyboard shortcuts
| Key | Action |
|---|---|
| β / β | Browse input history |
| Ctrl+R | Reverse history search |
| β / β / Home / End | Cursor movement |
| Ctrl+A | Go to beginning of line |
| Ctrl+E | Go to end of line |
| Ctrl+W | Delete word before cursor |
| Ctrl+U | Delete from cursor to beginning of line |
| Ctrl+K | Delete from cursor to end of line |
| Ctrl+L | Clear screen |
| Ctrl+C / Ctrl+D | Quit |
Number formatting and bases
Display format
The format command controls how numbers are displayed (MATLAB-compatible):
| Command | Mode | Example for pi |
|---|---|---|
format |
short | 3.1416 (5 sig digits, default) |
format short |
short | 3.1416 |
format long |
long | 3.14159265358979 |
format shortE |
shortE | 3.1416e+00 |
format longE |
longE | 3.14159265358979e+00 |
format bank |
bank | 3.14 (2 decimal places) |
format rat |
rat | 355/113 (rational approx) |
format hex |
hex | 400921FB54442D18 (IEEE 754 bits) |
format + |
+ | + (sign only) |
format compact |
β | suppress blank lines |
format loose |
β | add blank line after outputs |
format N |
custom | format 4 β 0.3333 |
format affects disp(), assignment output, and the REPL prompt β not fprintf/sprintf (which use their own specifiers).
See help format for full documentation.
Very large (|n| >= 1e15) and very small numbers switch to scientific notation automatically in short/long modes.
Number bases
Input literals β mix bases freely in any expression:
| Prefix | Base | Example |
|---|---|---|
0x |
hex | 0xFF β 255 |
0b |
binary | 0b1010 β 10 |
0o |
octal | 0o17 β 15 |
Display base β controls how the prompt and results are shown:
| Command | Effect |
|---|---|
hex |
Switch display to hexadecimal |
dec |
Switch display to decimal (default) |
bin |
Switch display to binary |
oct |
Switch display to octal |
base |
Show ans in all four bases |
[ 0 ]: 0xFF + 0b1010
[ 265 ]: hex
[ 0x109 ]: + 0b10
[ 0x10B ]: dec
[ 267 ]:
Inline base suffix β evaluate an expression and switch display base in one step:
[ 0 ]: 0xFF + 0b1010 hex
[ 0x109 ]:
base command:
[ 10 ]: base
2 - 0b1010
8 - 0o12
10 - 10
16 - 0xA
Expression conversion β when the display base is non-decimal and the expression contains literals in other bases, the converted expression is printed before the result:
[ 0x6 ]: 0b11 + 0b11
0x3 + 0x3
[ 0x6 ]:
[ 0b110 ]: 2 + 0b110 + 0xa
0b10 + 0b110 + 0b1010
[ 0b10010 ]:
Examples
Implicit multiplication:
[ 0 ]: 2(3 + 1)
[ 8 ]:
[ 0 ]: (2 + 1)(4 - 1)
[ 9 ]:
Compound interest β 1000 at 7% for 10 years:
[ 0 ]: 1000 * 1.07 ^ 10
[ 1967.15135729 ]:
Pythagorean hypotenuse β sides 3 and 4:
[ 0 ]: sqrt(3^2 + 4^2)
[ 5 ]:
Variables β monthly mortgage:
[ 0 ]: rate = 0.06 / 12
rate = 0.005
[ 0 ]: n = 360
n = 360
[ 0 ]: factor = (1 + rate) ^ n
factor = 10.9357...
[ 0 ]: 200000 * rate * factor / (factor - 1)
[ 1199.1010503 ]:
Angle conversion β degrees to radians, then sine:
[ 0 ]: 30 * pi / 180
[ 0.5235987756 ]: sin()
[ 0.5 ]:
Script files
When reading from a file (ccalc < formula.txt) you have three tools to control output:
Comments
% starts a comment (Octave/MATLAB convention). # is a supported alias (Octave and shell style). Both can appear as the first character on the line (full-line comment) or inline after an expression.
% Cylinder volume: V = pi * r^2 * h
pi * 5^2 % pi * r^2, r = 5
# same as above β hash-style comment
pi * 5^2 # inline hash comment
Semicolon β suppress output
A trailing ; evaluates the expression and updates ans, but prints nothing.
Use it to silence intermediate steps.
rate = 0.06 / 12; % monthly rate β silent
n = 360; % 30-year term β silent
factor = (1 + rate) ^ n;
200000 * rate * factor / (factor - 1)
fprintf('Monthly payment ($): ')
disp(ans)
disp(expr) β print value
disp(expr) evaluates the expression and prints the result.
It does not update ans.
disp(ans) % print current ans
disp(rate * 12) % print expression result
fprintf β print formatted text
fprintf(fmt, v1, ...) prints formatted output using C-style specifiers.
No newline is added automatically β include \n explicitly.
fprintf('=== Resistors in series ===\n')
R_total = 100 + 220 + 470;
fprintf('Total resistance: %d Ohm\n', R_total)
fprintf('=== Parallel combination ===\n')
R_par = 1 / (1/100 + 1/220);
fprintf('Parallel resistance: %.2f Ohm\n', R_par)
Output:
=== Resistors in series ===
Total resistance: 790 Ohm
=== Parallel combination ===
Parallel resistance: 68.75 Ohm
Example files
The examples/ directory contains annotated formula files ready to run:
| File | Description |
|---|---|
cylinder.calc |
Volume and surface area of a cylinder |
mortgage.calc |
Monthly mortgage payment |
data_storage.calc |
Real GiB capacity of a "500 GB" drive |
resistors.calc |
Series, parallel resistance, voltage divider, power |
ac_impedance.calc |
AC impedance, phase angle, dB level, bit width |
matrix_ops.calc |
Rotation, linear system solve, element-wise ops |
sequences.calc |
Ranges, linspace, indexing, slicing, finite differences |
logic.calc |
Comparison, logical NOT, &&/||, masks, soft clipping |
bitwise.calc |
Bitmask construction, register bit fields, RGB colour packing |
vector_utils.calc |
nan/inf, reductions, sort/find/unique, end indexing, reshape/flip |
complex_numbers.calc |
Complex arithmetic, polar form, built-ins, AC circuit |
strings.calc |
Char arrays, string objects, arithmetic, built-ins, unit labels |
formatted_output.calc |
fprintf/sprintf specifiers, flags, escape sequences, data table |
format_modes.calc |
All format display modes: short/long/shortE/bank/rat/hex/+/compact |
file_io.calc |
File I/O: fopen/fclose/fgetl/fgets, dlmread/dlmwrite, isfile/isfolder/exist/pwd, save/load with path |
csv/csv.calc |
CSV tables: readmatrix (header auto-skip, NaN for empty), readtable (Struct of columns), writetable (RFC 4180 quoting), tab-separated variant |
json/json.calc |
JSON encode/decode: primitives, arrays, objects, nested structs, roundtrip, file I/O, dataset analysis β requires --features json |
control_flow.calc |
Core control flow: if/elseif/else, for, while, break/continue, compound operators; grade classifier, prime sieve, Newton-Raphson, Collatz |
extended_control_flow.calc |
Extended control flow: switch/case, do...until, run()/source(); exit-code classifier, unit converter, digit sum, Euclidean GCD |
user_functions.calc |
User-defined functions and lambdas: recursion, multiple return values, nargin, anonymous functions, lexical capture, midpoint integration, higher-order functions |
cell_arrays.calc |
Cell arrays: literals, brace-indexing, auto-grow, @funcname handles, cellfun/arrayfun, varargin/varargout, case {β¦}, function pipelines |
structs.calc |
Scalar structs: field assignment/read, nested structs, struct() constructor, fieldnames/isfield/rmfield/isstruct, 3-D vector example |
struct_arrays.calc |
Struct arrays: indexed creation, element access, field collection β matrix/cell, loop building, fieldnames/isfield, nested fields, inventory ledger |
error_handling.calc |
Error handling: error/warning, lasterr, try/catch, try(expr,default), pcall, nested and loop-safe error recovery |
indexed_assignment.calc |
Indexed assignment: element/slice/submatrix write, growing vectors with end+1, cell array growth, logical mask read/write |
Building and testing
Optional features:
Project structure
crates/
ccalc/src/
main.rs β entry point, mode detection (arg / pipe / REPL), CLI flags
repl.rs β REPL loop, run_pipe(), run_expr(), shared evaluate() core
help.rs β help text
ccalc-engine/src/
lib.rs β crate root, public module exports
env.rs β Value enum (Scalar/Matrix/Complex/Str/StringObj/Void/Lambda/Function/Tuple/Cell/Struct), Env type (HashMap<String, Value>), workspace save/load
eval.rs β AST types (Expr, Op) + evaluator returning Value + number formatters + Base/FormatMode enums + FnCallHook
exec.rs β block statement executor: exec_stmts(), Signal enum (Break/Continue/Return), call_user_function()
io.rs β IoContext (file descriptor table), fopen/fclose/fgetl/fgets/write_to_fd
parser.rs β lexer (tokenizer) + recursive descent parser, Stmt enum (incl. If/For/While/FunctionDef/Return/MultiAssign)
ccalc-engine/benches/
engine.rs β Criterion benchmark suite (scalar ops, fib, loop, matmul, inv, fn calls)
Cargo.toml β workspace manifest (single source of truth for version)
CHANGELOG.md β version history
License
MIT