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 tanpaeval,- 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:
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{:});
endMari kita bedah baris-baris kode di atas:
- 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. - Baris 23 (
barisTeks = regexp(teks, ';', 'split');): opsi'split'padaregexpmemotong teks menjadi cell array berdasarkan pemisah;— setiap elemen adalah satu baris matriks. Batas 10 baris diperiksa segera setelahnya. - Baris 31 (
polaAngka = '^[+-]?' ...): pola angka yang kita izinkan: tanda opsional (^[+-]?), angka dengan titik desimal opsional (\d+\.?\d*|\.\d+— menerima5,5.,1.5,.5), dan eksponen opsional ([eE][+-]?\d+— menerima2e1). Token di luar pola sepertidua,2,, atauinflangsung ditolak. - 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]. - 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. - Baris 54 (
if isempty(regexp(token{t}, polaAngka))):regexpmengembalikan posisi kecocokan; hasil kosong berarti token tidak cocok dengan pola angka — inilah gerbang utama penggantieval. - Baris 58-63 (
nilai = str2double(token{t}); if ~isfinite(nilai)): token yang lolos pola diterjemahkanstr2double, lalu rentang nilainya diperiksa. Pola membolehkan eksponen besar, tetapistr2double('1e999')meluap menjadi tak berhingga — pemeriksaan~isfinitemenolaknya dengan pesan khusus. - Baris 69 (
M = vertcat(barisAngka{:});): semua baris sudah sama panjang (dijamin langkah 5), sehinggavertcatmerangkai cell array menjadi satu matriks dalam satu panggilan.
Hasil gabungannya dirangkum tabel berikut:
| Teks masukan | Hasil |
|---|---|
'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:
| Kondisi | Pesan yang tampil |
|---|---|
| Teks kosong | Input kosong. Masukkan angka dipisah spasi, contoh: 1 2; 3 4. |
| Baris kosong | Ada baris kosong. Hapus titik koma yang berlebih. |
| Kolom tidak sama | Jumlah angka tiap baris harus sama: baris 1 punya 3, baris 2 punya 2. |
| Token bukan angka | Format tidak valid: 'dua' bukan angka. |
| Nilai meluap | Nilai di luar rentang: '1e999' tidak dapat disimpan sebagai angka berhingga. |
| Ukuran melebihi batas | Matriks melebihi batas 10 baris x 10 kolom. |
| Dimensi operasi | Ukuran 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:
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
endMari kita bedah baris-baris kode di atas:
- Baris 9-14 (
if nargin < 2 ... end): argumendemodanteksDemoopsional —bacaMatriks('A')cukup untuk pemakaian interaktif, argumen ketiga hanya terisi pada mode demo. - Baris 16 (
while true): loop tanpa syarat; satu-satunya jalan keluar adalahreturnsetelah parse berhasil. Salah ketik tidak mengulang program dari awal, hanya prompt yang aktif. - 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 memakaiinput(..., 's')sehingga Enter saja pun aman. - Baris 23-25 (
try ... M = parserMatriks(teks); return;): parse yang sukses mengisiMlalu keluar dari fungsi. - 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:
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 6Menangkap 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:
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);
endBedahnya: 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:
% ---- 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:
== 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.