Bagian 2 menjamin dimensi yang salah ditolak, tetapi masukan yang paling liar justru tiba sebelum dimensi diperiksa: satu baris teks bebas yang diketik manusia. Salah ketik pasti terjadi — titik koma lupa, huruf tercampur angka, atau tempel-an yang menyisakan koma. Pada bagian ini kita membangun gerbangnya: parser yang mengubah teks menjadi matriks dengan aman, pesan galat yang mengajari pengguna memperbaiki inputnya, dan try/catch yang menjaga program tetap hidup.

Yang kita bangun pada bagian ini:

  • parserMatriks — teks '1 2; 3 4' menjadi matriks tanpa eval,
  • validasi nilai dan batas — token wajib angka, nilai wajib berhingga, ukuran maksimum 10x10,
  • bacaMatriks — prompt yang mengulang sampai teks sah,
  • penanganan galat di loop utama — program tidak pernah berhenti karena salah ketik.

Mengapa Bukan eval atau str2num #

Cara paling cepat menerjemahkan '1 2; 3 4' adalah eval atau str2num — dan keduanya kita tolak. eval mengeksekusi teks sebagai kode MATLAB apa adanya. str2num terasa lebih sopan, tetapi dokumentasinya menyebut bahwa ia mengevaluasi teks lewat eval di balik layar, sehingga risikonya sama: teks '1 2; 3 4' dan '1 2; system(''perintah berbahaya'')' sama-sama dieksekusi.

Fungsi str2double punya satu kelemahan: ia cukup longgar. Di GNU Octave, str2double('1,2') menghasilkan 12 karena koma diabaikan; token seperti '2,' pun bisa lolos sebagai angka di satu mesin dan ditolak di mesin lain. Parser kita menutup celah itu dengan memeriksa pola tiap token lewat regexp sebelum str2double dipanggil — keputusan identik di semua mesin.

ParserMatriks: Dari Teks ke Matriks #

Seluruh penerjemahan dipegang satu fungsi, src/parserMatriks.m. Aturannya: angka dipisah spasi, baris dipisah titik koma, maksimum 10x10:

matlab
function M = parserMatriks(teks)
% PARSERMATRIKS Menerjemahkan teks menjadi matriks numerik.
%   M = parserMatriks('1 2; 3 4')  menghasilkan  [1 2; 3 4]
%
% Aturan format: angka dipisah spasi, baris dipisah titik koma.
% Teks diterjemahkan TANPA eval/str2num. Setiap token diuji dulu
% terhadap pola angka lewat regexp, lalu diubah dengan str2double,
% sehingga masukan pengguna tidak pernah dieksekusi sebagai kode.
% Batas ukuran: maksimum 10 baris x 10 kolom.

% ---- 1. pemeriksaan awal ----
if nargin < 1 || ~ischar(teks)
    error('parserMatriks:input', ...
        'Input harus berupa teks, contoh: 1 2; 3 4.');
end
teks = strtrim(teks);
if isempty(teks)
    error('parserMatriks:kosong', ...
        'Input kosong. Masukkan angka dipisah spasi, contoh: 1 2; 3 4.');
end

% ---- 2. pilah baris (pemisah titik koma) ----
barisTeks = regexp(teks, ';', 'split');
nBaris = numel(barisTeks);
if nBaris > 10
    error('parserMatriks:batas', ...
        'Matriks melebihi batas 10 baris x 10 kolom.');
end

% ---- 3. pilah angka pada tiap baris ----
polaAngka = '^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$';
nKolom = 0;
barisAngka = cell(1, nBaris);
for k = 1:nBaris
    barisK = strtrim(barisTeks{k});
    if isempty(barisK)
        error('parserMatriks:barisKosong', ...
            'Ada baris kosong. Hapus titik koma yang berlebih.');
    end
    token = regexp(barisK, '\s+', 'split');
    m = numel(token);
    if nKolom == 0
        nKolom = m;
    elseif m ~= nKolom
        error('parserMatriks:kolom', ...
            'Jumlah angka tiap baris harus sama: baris 1 punya %d, baris %d punya %d.', ...
            nKolom, k, m);
    end
    if m > 10
        error('parserMatriks:batas', ...
            'Matriks melebihi batas 10 baris x 10 kolom.');
    end
    for t = 1:m
        if isempty(regexp(token{t}, polaAngka))
            error('parserMatriks:bukanAngka', ...
                'Format tidak valid: ''%s'' bukan angka.', token{t});
        end
        nilai = str2double(token{t});
        if ~isfinite(nilai)
            error('parserMatriks:rentang', ...
                'Nilai di luar rentang: ''%s'' tidak dapat disimpan sebagai angka berhingga.', ...
                token{t});
        end
        barisAngka{k}(t) = nilai;
    end
end

% ---- 4. susun matriks ----
M = vertcat(barisAngka{:});
end

Mari kita bedah baris-baris kode di atas:

  1. Baris 16 (teks = strtrim(teks);): spasi dan tab di kedua ujung dipangkas lebih dulu, sehingga ' 5 ' dan '5' diperlakukan sama. Teks yang hanya berisi spasi menjadi kosong dan ditolak pada baris 17-20 dengan pesan yang menunjukkan contoh benar.
  2. Baris 23 (barisTeks = regexp(teks, ';', 'split');): opsi 'split' pada regexp memotong teks menjadi cell array berdasarkan pemisah ; — setiap elemen adalah satu baris matriks. Batas 10 baris diperiksa segera setelahnya.
  3. Baris 31 (polaAngka = '^[+-]?' ...): pola angka yang kita izinkan: tanda opsional (^[+-]?), angka dengan titik desimal opsional (\d+\.?\d*|\.\d+ — menerima 5, 5., 1.5, .5), dan eksponen opsional ([eE][+-]?\d+ — menerima 2e1). Token di luar pola seperti dua, 2,, atau inf langsung ditolak.
  4. Baris 40 (token = regexp(barisK, '\s+', 'split');): satu baris dipilah lagi menjadi token; pola '\s+' menjadikan rangkaian spasi/tab sebagai satu pemisah, jadi '1 2 3' sah dan menghasilkan [1 2 3].
  5. Baris 42-48 (if nKolom == 0 ... elseif m ~= nKolom): baris pertama menetapkan jumlah kolom acuan; baris lain wajib sama. Pesan galatnya menyebut kedua jumlah: Jumlah angka tiap baris harus sama: baris 1 punya 3, baris 2 punya 2.
  6. Baris 54 (if isempty(regexp(token{t}, polaAngka))): regexp mengembalikan posisi kecocokan; hasil kosong berarti token tidak cocok dengan pola angka — inilah gerbang utama pengganti eval.
  7. Baris 58-63 (nilai = str2double(token{t}); if ~isfinite(nilai)): token yang lolos pola diterjemahkan str2double, lalu rentang nilainya diperiksa. Pola membolehkan eksponen besar, tetapi str2double('1e999') meluap menjadi tak berhingga — pemeriksaan ~isfinite menolaknya dengan pesan khusus.
  8. Baris 69 (M = vertcat(barisAngka{:});): semua baris sudah sama panjang (dijamin langkah 5), sehingga vertcat merangkai cell array menjadi satu matriks dalam satu panggilan.

Hasil gabungannya dirangkum tabel berikut:

Teks masukanHasil
'1 2; 3 4'[1 2; 3 4]
'5'5 (matriks 1x1)
'1 2 3'[1 2 3] (spasi ganda diizinkan)
'-1.5 2e1'[-1.5 20]
'' atau ' 'galat: input kosong
'1 dua; 3 4'galat: Format tidak valid: 'dua' bukan angka.
'1 2, 3'galat: koma bukan pemisah yang diizinkan
'1 2 3; 4 5'galat: jumlah angka tiap baris beda
'1 2;'galat: ada baris kosong
'1 2 3 4 5 6 7 8 9 10 11'galat: melebihi batas 10 kolom
'1e999 2'galat: nilai di luar rentang

Pesan Galat yang Ramah Pengguna #

Semua pesan galat parser mengikuti satu resep: sebut apa yang salah, lalu tunjukkan cara memperbaikinya. Bandingkan dua kemungkinan reaksi terhadap input '1 dua; 3 4' — galat teknis Undefined function 'dua' membuat pengguna bingung, sedangkan Format tidak valid: 'dua' bukan angka. langsung menunjuk pelakunya. Tabel berikut memetakan seluruh kondisi galat proyek ini:

KondisiPesan yang tampil
Teks kosongInput kosong. Masukkan angka dipisah spasi, contoh: 1 2; 3 4.
Baris kosongAda baris kosong. Hapus titik koma yang berlebih.
Kolom tidak samaJumlah angka tiap baris harus sama: baris 1 punya 3, baris 2 punya 2.
Token bukan angkaFormat tidak valid: 'dua' bukan angka.
Nilai meluapNilai di luar rentang: '1e999' tidak dapat disimpan sebagai angka berhingga.
Ukuran melebihi batasMatriks melebihi batas 10 baris x 10 kolom.
Dimensi operasiUkuran tidak kompatibel: 2x2 vs 3x2. Perkalian memerlukan jumlah kolom A sama dengan jumlah baris B.

Membaca dengan Ulangi Sampai Benar #

Parser menolak teks buruk dengan melempar error; tugas pembaca adalah mengulang prompt sampai teks sah. Berikut src/bacaMatriks.m:

matlab
function M = bacaMatriks(nama, demo, teksDemo)
% BACAMATRIKS Membaca satu matriks dari pengguna.
%   M = bacaMatriks('A')                     : mode interaktif
%   M = bacaMatriks('A', true, '1 2; 3 4')   : mode demo
%
% Mode interaktif mengulang prompt sampai teks yang diketik dapat
% diterjemahkan parserMatriks. Mode demo memakai teks tetap dan
% mencetaknya agar keluaran terlihat seperti sesi sungguhan.
if nargin < 2
    demo = false;
end
if nargin < 3
    teksDemo = '';
end

while true
    if demo
        fprintf('Matriks %s (contoh: 1 2; 3 4): %s\n', nama, teksDemo);
        teks = teksDemo;
    else
        teks = input(sprintf('Matriks %s (contoh: 1 2; 3 4): ', nama), 's');
    end
    try
        M = parserMatriks(teks);
        return;
    catch err
        fprintf('%s\n', err.message);
        if demo
            % Data demo tidak bisa diketik ulang: serahkan galat ke
            % pemanggil (loop utama kalkulator yang menanganinya).
            error('bacaMatriks:demo', 'Data demo tidak valid: %s', teksDemo);
        end
    end
end
end

Mari kita bedah baris-baris kode di atas:

  1. Baris 9-14 (if nargin < 2 ... end): argumen demo dan teksDemo opsional — bacaMatriks('A') cukup untuk pemakaian interaktif, argumen ketiga hanya terisi pada mode demo.
  2. Baris 16 (while true): loop tanpa syarat; satu-satunya jalan keluar adalah return setelah parse berhasil. Salah ketik tidak mengulang program dari awal, hanya prompt yang aktif.
  3. Baris 17-22 (if demo ... else ... end): mode demo mencetak prompt diikuti teks tetap — gema yang membuat transkrip uji otomatis terbaca seperti sesi nyata; mode interaktif memakai input(..., 's') sehingga Enter saja pun aman.
  4. Baris 23-25 (try ... M = parserMatriks(teks); return;): parse yang sukses mengisi M lalu keluar dari fungsi.
  5. Baris 26-33 (catch err ... end): pesan galat parser dicetak untuk pengguna. Mode interaktif membiarkan loop mengulang prompt; mode demo justru melempar ulang galatnya — data demo tidak bisa “diketik ulang”, jadi kegagalan harus dilaporkan ke pemanggil.

Perilaku ulang terlihat pada sesi interaktif: salah ketik dijawab pesan, lalu prompt muncul kembali:

text
Matriks A (contoh: 1 2; 3 4): 4 7; 2 6;
Ada baris kosong. Hapus titik koma yang berlebih.
Matriks A (contoh: 1 2; 3 4): 4 7; 2 6

Menangkap Galat di Loop Utama #

Galat dari fungsi operasi maupun parser pada akhirnya bubbles sampai loop utama. Di situlah garis pertahanan terakhir berdiri — potongan kalkulator.m berikut membungkus seluruh dispatch:

matlab
    try
        selesai = jalankanPerintah(pilihan, demo, teksA, teksB);
    catch err
        % Galat dari validasi dimensi maupun parser tidak boleh
        % menghentikan program: tampilkan pesannya lalu kembali ke menu.
        fprintf('%s\n', err.message);
    end

Bedahnya: baris try menandai wilayah berisiko — pembacaan matriks, validasi dimensi, dan hitungan semuanya terjadi di dalam jalankanPerintah. Bila galat terjadi, catch err menangkapnya, err.message diambil, lalu dicetak; loop menu mengulang seperti tidak terjadi apa-apa. Tanpa blok ini, satu salah ketik akan menghentikan seluruh kalkulator dengan stack trace — program yang “tahan banting” justru dinilai dari cara menghadapi input yang salah.

Uji Validasi #

Berkas tests/uji_validasi.m menguji parser dengan tabel kasus — lima teks sah dan sembilan teks terlarang, masing-masing dengan potongan pesan yang diharapkan:

matlab
% ---- kasus nilai: teks sah harus menjadi matriks yang benar ----
kasus = { ...
    'parser "1 2; 3 4"',      @() assert(isequal(parserMatriks('1 2; 3 4'), [1 2; 3 4]));
    'parser "5"',             @() assert(isequal(parserMatriks('5'), 5));
    'parser spasi ganda',     @() assert(isequal(parserMatriks('1  2   3'), [1 2 3]));
    'parser desimal+eksponen', @() assert(isequal(parserMatriks('-1.5 2e1'), [-1.5 20]));
    'parser 2x3',             @() assert(isequal(parserMatriks('1 2 3; 4 5 6'), [1 2 3; 4 5 6]));
};

% ---- kasus galat: teks tidak sah harus ditolak dengan pesan jelas ----
barisSebelas = strjoin(repmat({'1'}, 1, 11), ';');
galat = { ...
    'teks kosong',       @() parserMatriks(''),                           'Input kosong';
    'hanya spasi',       @() parserMatriks('   '),                        'Input kosong';
    'token bukan angka', @() parserMatriks('1 dua; 3 4'),                 'Format tidak valid';
    'koma bukan pemisah', @() parserMatriks('1 2, 3'),                    'Format tidak valid';
    'kolom tidak sama',  @() parserMatriks('1 2 3; 4 5'),                 'Jumlah angka';
    'baris kosong',      @() parserMatriks('1 2;'),                       'baris kosong';
    'melebihi 10 baris', @() parserMatriks(barisSebelas),                 'melebihi batas';
    'melebihi 10 kolom', @() parserMatriks('1 2 3 4 5 6 7 8 9 10 11'),    'melebihi batas';
    'nilai meluap',      @() parserMatriks('1e999 2'),                    'di luar rentang';
};

Menjalankan berkas dari folder tests/ memberi hasil:

Output yang Dihasilkan:

text
== uji_validasi.m ==
LULUS: parser "1 2; 3 4"
LULUS: parser "5"
LULUS: parser spasi ganda
LULUS: parser desimal+eksponen
LULUS: parser 2x3
LULUS: teks kosong
LULUS: hanya spasi
LULUS: token bukan angka
LULUS: koma bukan pemisah
LULUS: kolom tidak sama
LULUS: baris kosong
LULUS: melebihi 10 baris
LULUS: melebihi 10 kolom
LULUS: nilai meluap

Ringkasan: 14 lulus, 0 gagal (total 14)

Gerbang kini lengkap: teks mentah tidak pernah dieksekusi, teks terlarang ditolak dengan pesan yang mengajar, dan program tetap hidup setelah galat apa pun. Satu pertanyaan tersisa — apakah semua ini bekerja bersama-sama saat program utuh berjalan? Bagian 4 menjawabnya dengan rangkaian uji dari unit sampai end-to-end.