Proyek pertama kita adalah kalkulator terminal: sebuah program yang membaca ekspresi seperti 12 + 30 dari masukan, menghitungnya, lalu mencetak 42. Sebelum menulis satu baris logika aritmetika pun, kita tetapkan dulu alur program, struktur berkas, dan bentuk antarmuka antarbagiannya. Rancangan ini bukan formalitas — kalkulator terlihat sederhana, tetapi tepat karena itu ia menjadi tempat berlatih memilah tanggung jawab: bagian yang berbicara dengan pengguna dipisahkan dari bagian yang murni mengolah data. Pemisahan inilah yang membuat setiap bagian mudah diuji di bagian-bagian berikutnya.

Alur Program: Baca, Parse, Hitung, Cetak #

Kalkulator ini bekerja sebagai loop REPL — singkatan dari Read-Eval-Print Loop, pola yang sama dipakai shell dan interpreter Python. Program membaca satu baris, mengolahnya, mencetak hasilnya, lalu kembali membaca baris berikutnya sampai masukan berakhir. Untuk kalkulator kita, tahap “eval” kita pecah lagi menjadi tiga langkah kecil sehingga setiap langkah bisa dites terpisah:

Diagram di atas menunjukkan dua jalur keluaran yang penting kita bedakan sejak awal. Jalur sukses mengalir dari tokenisasi ke perhitungan lalu mencetak angka; jalur gagal berhenti di pemeriksaan validitas dan mencetak pesan galat yang menjelaskan masalahnya, bukan sekadar berhenti diam-diam. Setelah salah satu jalur selesai, program kembali ke titik baca — inilah yang membuatnya interactive ketika dijalankan langsung, sekaligus bisa diotomasi dengan menyalurkan berkas ke stdin.

Struktur Proyek #

Seluruh berkas proyek kita letakkan di dalam bundle proyek ini dengan susunan sederhana:

text
kalkulator-terminal/
├── _index.md           (halaman overview proyek — sudah ada)
├── part-1-merancang-alur-kalkulator/
├── part-2-menerapkan-operasi-aritmetika/
├── part-3-memvalidasi-input/
├── part-4-menangani-kesalahan/
├── src/
│   └── kalkulator.cpp   (program final, lengkap, teruji)
└── tests/
    └── uji-kalkulator.cpp (mini test harness)

Selama empat bagian ini kita membangun kalkulator.cpp bertahap: Bagian 2 menambahkan operasi aritmetika, Bagian 3 memperketat validasi, dan Bagian 4 menuntaskan penanganan kesalahan sekaligus mengunci perilakunya dengan pengujian. Berkas final selalu tersedia di src/, jadi Anda bisa melihat gambaran utuh kapan saja, sementara tiap bagian membedah potongan yang sedang dikerjakan.

Antarmuka Fungsi Murni #

Keputusan desain terpenting di proyek ini: logika pengolahan ditulis sebagai fungsi murni — function yang menerima masukan dan mengembalikan hasil tanpa menyentuh cin atau cout. Tiga function utama kita:

cpp rancangan antarmuka kalkulator.cpp (kerangka deklarasi)
// tahap 1: potong baris menjadi token berdasarkan spasi
std::vector<std::string_view> tokenisasi(std::string_view baris);

// tahap 2: urai satu token menjadi angka; gagal = galat, bukan crash
std::expected<double, Galat> parseAngka(std::string_view token);

// tahap 3 dan 4: evaluasi satu baris utuh menjadi satu angka
std::expected<double, Galat> evaluasi(std::string_view baris);

Mari kita bedah alasan tiap keputusan:

  1. std::string_view sebagai parameter — baris masukan dan token tidak perlu disalin; string_view hanyalah jendela ke teks yang sudah ada. Function tetap aman karena tidak menyimpan jendela itu lebih lama dari barisnya.
  2. std::expected<double, Galat> sebagai kembalian — penguraian bisa gagal, dan kegagalan itu kita jadikan bagian dari tipe kembalian, bukan peristiwa tersembunyi. Pemanggil tidak mungkin lupa memeriksa karena hasilnya tidak bisa dipakai sebagai angka sebelum dibuka. Tipe ini dibahas tuntas pada Chapter expected; untuk proyek ini cukup pahami polanya: berisi nilai bila sukses, berisi Galat bila gagal.
  3. Tanpa I/O di dalam logika — evaluasi tidak mencetak apa pun. Pencetakan dan pembacaan hanya terjadi di main. Dengan begitu harness pengujian di Bagian 4 bisa memanggil evaluasi langsung dengan ribuan masukan tanpa satu byte keluaran liar.

Kerangka Pertama yang Berjalan #

Sebelum berhitung, kita pastikan dulu kerangka REPL-nya benar. Simpan program berikut sebagai kerangka-awal.cpp, lalu kompilasi dan jalankan:

cpp kerangka-awal.cpp
// kerangka-awal.cpp — kerangka REPL kalkulator: alur sudah benar, logika menyusul.
#include <iostream>   // std::getline, std::cin
#include <print>      // std::println
#include <string>

int main() {
    std::println("Kalkulator Terminal (ketik ekspresi, Ctrl+D untuk keluar)");
    std::string baris;
    while (std::getline(std::cin, baris)) {
        if (baris.find_first_not_of(" \t") == std::string::npos) {
            continue;   // baris kosong atau hanya spasi: diabaikan
        }
        std::println("[kerangka] membaca: {}", baris);
        // TODO Bagian 2: tokenisasi -> parse -> hitung -> cetak
    }
    std::println("selesai.");
}
bash
g++ -std=c++23 -Wall -Wextra kerangka-awal.cpp -o kerangka-awal
printf '12 + 30\n100 / 4\n' | ./kerangka-awal

Output yang Dihasilkan:

text
Kalkulator Terminal (ketik ekspresi, Ctrl+D untuk keluar)
[kerangka] membaca: 12 + 30
[kerangka] membaca: 100 / 4
selesai.

Kerangka ini belum menghitung apa pun, tetapi fondasinya sudah benar: std::getline membaca per baris sampai masukan habis, baris yang hanya berisi spasi diabaikan lewat find_first_not_of, dan setiap baris sah diteruskan ke titik yang kelak diisi logika perhitungan. Perhatikan juga bahwa baris kosong tidak memunculkan apa pun di keluaran — keputusan kecil seperti ini sebaiknya ditetapkan di kerangka, bukan disibak dulu lalu dilupakan.

Dengan alur, struktur, dan antarmuka yang sudah disepakati, kita siap mengisi kerangka ini dengan operasi aritmetika sungguhan pada bagian berikutnya — mulai dari tokenisasi, penguraian angka, sampai evaluasi kiri ke kanan yang akan menentukan rasa “matematika” kalkulator kita.

Rujukan resmi: std::string_view (cppreference) , std::expected (cppreference) .