---
# System prepended metadata

title: Praktikum Teknik Kompilasi

---

# Membangun Bahasa Pemrograman Bahasaku dengan Python
https://hackmd.io/@WmG5F-YOQkmu6CtOO9BApw/Sy1jZqnaxe/edit
https://colab.research.google.com/drive/1UI-c6bJXPoYQ3F1b_mCCaqr9GAchCxXs?usp=sharing

## Pendahuluan
### Definisi Bahasa
Sebelum membuat bahasa pemrograman sendiri, kita perlu memahami apa itu bahasa.
Bahasa adalah jembatan antara pikiran dan ekspresi — kumpulan simbol yang diatur oleh aturan agar kita bisa menyampaikan makna.

Seiring waktu, bahasa manusia berevolusi dari gestur menjadi bentuk terstruktur seperti Bahasa Indonesia dan Inggris. Evolusi serupa terjadi pada bahasa pemrograman: dari Assembly yang sulit dibaca, hingga Python yang lebih sederhana karena banyak aspek kompleksnya diabstraksikan.

Baik bahasa manusia maupun bahasa pemrograman terdiri dari tiga komponen utama:
- Kosakata (Vocabulary) = Kumpulan simbol atau kata
:::info 
**Contoh:** Pada Python kita mengenal if, else, for, dan return memiliki fungsi spesifik. 
:::
- Tata bahasa (Grammar\Syntax) = aturan penulisan yang harus diikuti dengan ketat.
:::info
**Contoh:** Pada Python
```python
if x > 5:
    print("Hello")
```
Dalam Python, jika Anda lupa menambahkan titik dua ( : ), atau menghapus indentasi, akan terjadi kesalahan sintaksis. Tidak ada ruang untuk improvisasi—aturan tata bahasa harus diikuti 100% dengan benar setiap saat.
:::
- Makna (Semantics) = Arti dari susunan kode.

:::info
**Contoh:**
```python
x = 5
y = 0
result = x / y
```
Komputer akan error karena kita tidak memberi logika pengecekan y != 0.
:::

Jika ketiga komponen ini digabungkan, terbentuklah bahasa yang memungkinkan kita berkomunikasi, baik antar manusia maupun dengan komputer.
Bahasa manusia fleksibel dan kontekstual, sedangkan bahasa pemrograman kaku dan hanya memahami instruksi yang jelas.

## Teknik Kompilasi
Teknik adalah suatu metode atau cara, sedangkan kompilasi merupakan proses menggabungkan serta menerjemahkan suatu source program menjadi bentuk lain
>*Compile – to translate a program written in a high-level programming language*

Perkembangan penerjemah (translator) sudah dimulai sejak awal ditemukannya komputer pada tahun 1950-an. Sejak itu, teknik serta metode pembentukan penerjemah berkembang pesat, bersamaan dengan munculnya berbagai program bantu (tools) untuk mempermudah prosesnya.

Translator (penerjemah) adalah program yang berfungsi mengubah program sumber (source program) menjadi program sasaran (target program).
Bahasa mesin merupakan bentuk bahasa paling dasar karena berhubungan langsung dengan komponen komputer seperti bit dan register. Bahasa ini sangat primitif dan sulit dipahami oleh manusia. Oleh karena itu, diperlukan translator agar kode dalam bahasa mesin dapat diubah menjadi bentuk yang lebih mudah dimengerti.

Sebagai contoh, jika bahasa sumber (source language) adalah bahasa tingkat tinggi seperti COBOL, Pascal, atau Fortran, maka bahasa hasilnya (object language) adalah bahasa tingkat rendah atau bahasa mesin. Penerjemah jenis ini disebut compiler. 

![image](https://hackmd.io/_uploads/BJQ1gdVCxl.png)

### Pengertian dan Model dari Compiler, Interpreter & Assembler

A. Pengertian Compiler
Kompilator adalah suatu program yang menerjemahkan bahasa program kedalam bahasa objek. Kompilator memerlukan waktu untuk membuat suatu program dapat dieksekusi oleh komputer, program yang dieksekusi oleh compiler dapat berjalan lebih cepat dibanding program yang diproduksi oleh interpreter, disamping itu juga bersifat independen. Istilah Compiler biasa digunakan untuk program komputer yang menerjemahkan program yang ditulis dalam bahasa pemrograman tingkat tinggi (Contoh bahasa C++, Pascal, FORTRAN, Visual C#, COBOL, Visual Basic, Java, atau xBase) diterjemahkan menjadi bahasa mesin, biasanya menggunakan bahasa Assembly sebagai perantara. salah satu contoh Compiler dalam Bahasa C++ adalah Visual C++, GNU Compiler Collection (GCC) dan Borland C++.
![image](https://hackmd.io/_uploads/SytntdVCgl.png)

Tahap Kompilasi berdasarkan bagan diatas sebagai berikut
- Pertama source code (program yang ditulis) dibaca ke memori komputer.
- Source code tersebut diubah menjadi object code (bahasa assembly).
- Object code dihubungkan dengan library yang dibutuhkan untuk membentuk file yang
bisa di eksekusi.

B. Pengertian Interpreter
Interpreter adalah perangkat lunak yang berfungsi untuk mengeksekusi kode program yang ditulis oleh programmer secara langsung dengan menerjemahkannya ke dalam bahasa mesin. Proses eksekusi ini dilakukan secara baris demi baris, mengikuti logika yang terdapat dalam kode sumber. Dalam konteks ini, interpreter tidak menghasilkan output dalam bentuk file eksekusi yang lengkap sebelum menjalankan program, melainkan menerjemahkan dan menjalankan kode pada saat yang bersamaan.
![image](https://hackmd.io/_uploads/rJNOouN0gg.png)
Berbeda dengan compiler, yang mengubah keseluruhan kode sumber menjadi bahasa mesin dalam bentuk file eksekusi terpisah sebelum eksekusi, interpreter menjalankan kode secara langsung tanpa menghasilkan file eksekusi terpisah. Proses penerjemahan pada compilerdilakukan sebelumnya dan hasilnya berupa satu kesatuan perintah dalam bahasa mesin yang siap untuk dijalankan. 

Di sisi lain, dalam terminologi penerjemahan bahasa, interpreter (atau dalam bahasa Indonesia dikenal sebagai "juru bahasa") berbeda dari translator. Interpreter dalam konteks bahasa manusia menerjemahkan bahasa sumber ke bahasa sasaran secara lisan atau secara langsung (oral), sedangkan translator menerjemahkan teks dari bahasa sumber ke bahasa sasaran secara tertulis. Meskipun keduanya memiliki fungsi menerjemahkan, metode dan media yang digunakan dalam proses penerjemahan ini berbeda.

C. Pengertian Assembler
Bahasa assembly adalah bahasa pemrograman yang menggunakan instruksi-instruksi dengan "mnemonic" untuk menggantikan kode biner yang rumit dari bahasa mesin. Misalnya, sebuah instruksi penambahan dalam bahasa mesin dengan kode “10110011” yang dalam bahasa assembly dapat dibuat dalam instruksi mnemonik ADD, sehingga mudah diingat dibandingkan dengan angka 0 dan 1, dalam setiap instruksi membutuhkan suatu operand baik berupa data langsung maupun suatu lokasi memori yang menyimpan data yang bersangkutan
![image](https://hackmd.io/_uploads/rJmJ2uE0gg.png)

Bahasa assembly, sering disebut sebagai kode sumber atau kode simbolik, dapat dijalankan oleh prosesor setelah diterjemahkan oleh program assembler. Assembler adalah perangkat lunak yang mengkonversi program dalam bahasa assembly menjadi bahasa mesin. Bahasa mesin itu sendiri adalah sekumpulan kode biner yang dapat diproses langsung oleh komputer dan sering disebut sebagai kode objek.

### Tahap–Tahap Kompilasi
Proses kompilasi dikelompokan ke dalam dua kelompok besar:
1. Tahap Analisa (Front-end): Menganalisis source code dan memecahnya menjadi bagian-bagian dasarnya. Menghasilkan kode level menengah dari source code input yang ada. Tahap-tahap yang harus dilalui pada saat mengkompilasi program, yaitu:
a. Analisa Leksikal
b. Analisa Sintaks
c. Analisa Semantik
d. Pembangkit Kode Antara
2. Tahap Sintesa (Back-end): membangun program sasaran yang diinginkan dari bentuk
antara. Tahap-tahap yang harus dilalui pada tahap sintesa yaitu:
a. Code optimization
b. Object code generation

![image](https://hackmd.io/_uploads/HkiaCuNRel.png)

Keterangan :
- Analisa Leksikal (scanner)
Berfungsi memecah teks program sumber menjadi bagian-bagian kecil yang mempunyai satu arti yang disebut token, seperti : konstanta, nama variabel, keyword, operator. Lexer adalah suatu komponen yang berfungsi untuk mendeteksi bagian terkecil (token) dari suatu bahasa. Proses pendeteksian ini disebut lexing. 
- Analisa Sintaks (parser)
Parser berfungsi sebagai komponen yang mendeteksi struktur dari beberapa token berdasarkan aturan sintaksnya dan memeriksa kebenaran dan urutan kemunculan token. Proses pendeteksian ini disebut parsing. Proses parsing lebih rumit daripada lexing. Sederetan token yang tidak mengikuti aturan sintaks akan dilaporkan sebagai kesalahan sintaks (sintax error). Secara logika deretan token yang bersesuaian dengan sintaks tertentu akan dinyatakan sebagai pohon parsing (parse tree).

- Analisa Semantik
Berfungsi memeriksa token dan ekspresi dari batasan-batasan yang ditetapkan. 
Batasan-batasan tersebut misalnya :
1. Panjang maksimum token identifier adalah 8 karakter,
2. Panjang maksimum ekspresi tunggal adalah 80 karakter,
3. Nilai bilangan bulat adalah -32768 s/d 32767,
4. Operasi aritmatika harus melibatkan operan-operan yang bertipe sama.

- Pembangkit Kode Antara
Berfungsi membangkitkan kode antara (intermediate code) berdasarkan pohon parsing. Pohon parse selanjutnya diterjemahkan oleh suatu penerjemah yang dinamakan penerjemah berdasarkan sintak (syntax-directed translator). 

- Symbol Table management
Berfungsi mengelola tabel simbol selama proses kompilasi. Tabel simbol adalah
struktur data yang memuat record untuk tiap identifier dengan atribut-atribut identifier
itu.

- Penangan Kesalahan (Error handler)
Berfungsi menangani kesalahan yang berlangsung selama proses kompilasi.

Cara terbaik untuk memahami konsep kompilasi adalah dengan membangun compiler sendiri dari awal hingga akhir. Meskipun terdengar menantang, proses ini sebenarnya bisa dipecah menjadi beberapa tahap yang lebih sederhana dan mudah dipahami. Berikut ini kita akan membangun bahasa pemrograman sendiri dengan nama 'bahasaKu' (toy language).


## Analisis Leksikal (Lexer)
Analisis leksikal (bahasa Inggris: lexical analysis) adalah sebuah proses yang mendahului parsing sebuah rangkaian karakter. Ia menerima masukan serangkaian karakter (seperti dalam dokumen plain-text atau source code) dan menghasilkan deretan simbol yang masing-masing dinamakan token; Kelompok karakter yang membentuk sebuah token dinamakan lexeme untuk token tersebut. Analisis leksikal membuat pekerjaan parser jadi lebih mudah; terlepas dari efisiensi pemrograman yang dapat dicapai dengan penggunaannya, proses kerja analisis leksikal yang membaca lebih dari sekali setiap karakter dari input yang diberikan menjadikan penganalisis leksikal sebagai sub-sistem yang paling intensif melakukan komputasi, terutama bila digunakan dalam sebuah kompilator. 

Pada bab ini kita akan membangun kompilator sederhana untuk bahasa buatan sendiri yang kita beri nama BahasaKu. Pada tahap ini, kita fokus pada analisis leksikal (lexical analysis), yaitu proses membaca kode sumber dan memecahnya menjadi potongan-potongan kecil yang disebut token.

Contoh jika terdapat urutan karakter yang disebut dengan statement

```python
fahrenheit = 32 + celcius * 1.8
```
Akan menghasilkan token-token berupa

Identifier, fahrenheit
Operator, =
Numerik, 32
Operator, +
Identifier, celcius
Operator, *
Float, 1.8

Untuk menghasilkan token-token tersebut, kita akan menulis program analisis leksikal menggunakan kelas BahasaKu.
Kelas ini menerima kode sumber sebagai string dan memiliki method tokens() yang mengembalikan iterator yang menghasilkan token-token dari kode sumber tersebut.

Dengan kata lain, tahap ini adalah pondasi awal dari kompilator BahasaKu, yang bertugas mengenali elemen-elemen dasar dari bahasa yang akan kita kembangkan.

**Langkah 1: Definisikan Token dan Kategori Token**

Pertama, tentukan jenis token yang akan dikenali, pada BahasaKu token yang akan dikenali yaitu
- Keyword = ``'cetak', 'if', 'elif', 'else', 'while', 'for', 'break', 'continue', 'pass'``
- Operator = `'=', '+', '-', '*', '/', '==', '!=', '<', '>', '<=', '>='`
- Simbol = `'(', ')', ':'`
- Integer = angka 
```python
import sys

class BahasaKu:
    def __init__(self, code):
        self.code = code
        self.line_nr = 0
        self.returned_token = None
        self.token_feed = self.tokens()
        self.stack = []
        self.vars = {}
        self.loop_stack = []  # untuk break/continue
        self.skip_remaining_blocks = False

        # tambahkan 'in' dan 'end' sebagai keyword
        self.KEYWORDS = ['cetak', 'if', 'elif', 'else', 'while', 'for', 'break', 'continue', 'pass', 'end', 'in']
        self.OPERATORS = ['=', '+', '-', '*', '/', '==', '!=', '<', '>', '<=', '>=']
        self.SYMBOLS = ['(', ')', ':']

        # precedence untuk operator evaluasi infix
        self.precedence = {'+':1, '-':1, '*':2, '/':2, '==':0, '!=':0, '<':0, '>':0, '<=':0, '>=':0}

    def raise_error(self, msg):
        raise ValueError(f"Baris {self.line_nr}: {msg}")

    # ===== TOKENIZER =====
    def tokens(self):
        for line in self.code.splitlines():
            self.line_nr += 1
            line = line.strip()
            if line == '':
                yield ('NEWLINE',)
                continue

            i = 0
            tokens = []
            while i < len(line):
                ch = line[i]
                if ch.isspace():
                    i += 1
                    continue
                # === TAMBAHAN: Deteksi string literal ===
                if ch in '"\'':
                    quote = ch
                    i += 1
                    start = i
                    while i < len(line) and line[i] != quote:
                        i += 1
                    if i >= len(line):
                        self.raise_error(f"String tidak ditutup")
                    string_val = line[start:i]
                    tokens.append(('STR', string_val))
                    i += 1  # skip closing quote
                    continue
                # === AKHIR TAMBAHAN ===
                # dua-character operators
                if i+1 < len(line) and line[i:i+2] in ['==', '!=', '<=', '>=']:
                    tokens.append(line[i:i+2])
                    i += 2
                    continue
                # single-char operators or symbols
                if ch in "=+-*/()<>:":
                    tokens.append(ch)
                    i += 1
                    continue
                # number (int or float)
                if ch.isdigit() or (ch == '.' and i+1 < len(line) and line[i+1].isdigit()):
                    start = i
                    has_dot = False
                    while i < len(line) and (line[i].isdigit() or line[i] == '.'):
                        if line[i] == '.':
                            if has_dot:
                                self.raise_error("Angka tidak valid")
                            has_dot = True
                        i += 1
                    tokens.append(line[start:i])
                    continue
                # identifier / keyword
                if ch.isalpha() or ch == '_':
                    start = i
                    while i < len(line) and (line[i].isalnum() or line[i] == '_'):
                        i += 1
                    tokens.append(line[start:i])
                    continue
                self.raise_error(f"Token tidak sah: {ch}")

            # convert raw tokens to typed tokens
            for t in tokens:
                # === TAMBAHAN: Handle tuple token (sudah typed) ===
                if isinstance(t, tuple):
                    yield t
                    continue
                # === AKHIR TAMBAHAN ===
                if t in self.KEYWORDS:
                    yield ('KEY', t)
                elif t in self.OPERATORS:
                    yield ('OP', t)
                elif t in self.SYMBOLS:
                    yield ('SYM', t)
                elif t.replace('.', '', 1).isdigit():
                    if '.' in t:
                        yield ('NUM', float(t))
                    else:
                        yield ('NUM', int(t))
                else:
                    yield ('IDF', t)
            yield ('NEWLINE',)

    # ===== token helpers =====
    def next_token(self):
        if self.returned_token is not None:
            tok = self.returned_token
            self.returned_token = None
            return tok
        try:
            return next(self.token_feed)
        except StopIteration:
            return None

    def return_token(self, token):
        # allow putting back None as well (no-op)
        if token is None:
            return
        if self.returned_token is not None:
            # shouldn't happen; but handle gracefully by overwriting
            self.returned_token = token
        else:
            self.returned_token = token
```

dfgdg

```python
    def next_token(self):
        if self.returned_token is not None:
            tok = self.returned_token
            self.returned_token = None
            return tok
        try:
            return next(self.token_feed)
        except StopIteration:
            return None

    def return_token(self, token):
        if token is None:
            return
        self.returned_token = token
```




Kelas `BahasaKu` ini berfungsi sebagai tokenizer atau lexical analyzer, yaitu bagian yang memecah kode sumber menjadi potongan-potongan token yang dapat dipahami oleh interpreter. Dalam konstruktor `__init__`, terdapat tiga daftar utama: `KEYWORDS` untuk kata kunci seperti `if`, `else`, dan `while`; `OPERATORS` untuk operator aritmatika maupun logika; serta `SIMBOL` untuk tanda kurung.

Metode `tokens()` membaca kode baris demi baris, mengabaikan spasi, lalu mengenali token berdasarkan jenisnya — seperti operator dua karakter (`==, <=`), operator satu karakter (`+, -, =`), angka (termasuk bilangan desimal), dan identifier (nama variabel atau fungsi). Setelah dikenali, setiap token diberi label sesuai kategorinya (`KEY`, `OP`, `SYM`, `NUM`, `FLOAT`, atau `IDF`) dan dikembalikan satu per satu menggunakan yield.

selanjutnya jalankan program. 

Pada jendela Python Shell masukkan perintah berikut.
```python
coba = bahasaKu('''cetak 2
cetak 6
cetak a + 2
''')
list(coba.tokens())
```
Output

![image](https://hackmd.io/_uploads/BJUqJfCTlx.png)


- Untuk token `"cetak"` dan `"\n"`: tuple berisi **satu elemen** — token itu sendiri.  
  ```py
  ('cetak',)
  ('\n',)
    ```
- Untuk token angka: tuple berisi dua elemen — jenis token dan nilai aktual (tipe int).
    ```py
    ('num', 2), ('num', 6), ('idf', 'a')
    ```
> Catatan: “elemen kedua” pada token angka berarti nilai angka dalam bentuk int (bukan string). Pada identifier berbentuk string.


Apa yang terjadi jika ada token tidak valid?

hal yang terjadi adalah error dan itu adalah kesalahan, jadi untuk kasus tersebut, metode `raise_error()` akan dipanggil, yang akan memunculkan `ValueError`. Berikut contoh lain di mana kesalahan muncul karena token yang tidak valid:
```python
coba = bahasaKu('''cetak 2
cetak "hii"
''')
list(coba.tokens())
```
Output:
```
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    list(coba.tokens())
  File "C:\Users\muham\Downloads\New folder\bahasaKu.py", line 29, in tokens
    self.raise_error(f'Token tidak sah {token}')
  File "C:\Users\muham\Downloads\New folder\bahasaKu.py", line 15, in raise_error
    raise ValueError(f'{self.line_nr}: {message}')
ValueError: 2: Token tidak sah "hii"

```
![image](https://hackmd.io/_uploads/S1pUlf0ale.png)


Perhatikan `2` pada pesan kesalahan. ini adalah nomor baris kesalahan! `line_nr` adalah Atribut kelas `bahasaKu` yang menghitung baris saat dianalisis, ini memungkinkan pelaporan nomor baris ketika terjadi kesalahan.

Metode `tokens()` menghasilkan data yang dapat diiterasi yang artinya bisa langsung diubah jadi list untuk dilihat isinya.

Selanjutnya untuk mempermudah nantinya parser mengambil token satu per satu, maka dibuat metode `next_token()` dan juga metode `return_token()` yang berfungsi ketika token sudah diambil oleh `next_token()` akan mengembalikan token tersebut dan token tersebut tersimpan dalam `return_token()` sehingga jika kita memanggil `next_token()` berikutnya token yang disimpan tadi akan keluar kembali lebih dahulu. karena buffer hanya disediakan untuk satu token saja, jika kita mencoba menggembalikan lebih dari satu sekaligus maka program akan melempar `RuntimeError`

Dengan begitu, kedua metode ini saling melengkapi. `next_token()` menjadi pintu masuk utama parser untuk membaca token satu per satu, sedangkan `return_token()` memberi fleksibilitas bagi parser untuk mundur satu langkah jika perlu menunda pemrosesan token tertentu.

### 1. Method `next_token()`

Sebelum menambahkan method `next_token()`. 
Tambahkan iterator baru yang bernama `self.token_feed = self.tokens()` dan  `self.return_token = None`
```python
class BahasaKu:
    def __init__(self, code):
        self.code = code
        self.line_nr = 0
        self.returned_token = None
        self.token_feed = self.tokens()
        self.stack = []
        self.vars = {}
        self.loop_stack = []  # Untuk break/continue

        self.KEYWORDS = ['cetak', 'if', 'elif', 'else', 'while', 'for', 'break', 'continue', 'pass']
        self.OPERATORS = ['=', '+', '-', '*', '/', '==', '!=', '<', '>', '<=', '>=']
        self.SIMBOL = ['(', ')', ':']

        self.precedence = {'+':1, '-':1, '*':2, '/':2, '==':0, '!=':0, '<':0, '>':0, '<=':0, '>=':0}

    def raise_error(self, msg):
        raise ValueError(f"Baris {self.line_nr}: {msg}")
```
`self.token_feed = self.tokens()` digunakan untuk membuat iterator token, yaitu objek yang menghasilkan token satu per satu dari kode sumber agar interpreter bisa memprosesnya secara berurutan tanpa harus memuat semua token sekaligus. Sedangkan `self.returned_token = None` berfungsi sebagai penyimpanan sementara bagi token yang sudah diambil tapi belum sempat diproses—misalnya jika parser membaca token terlalu cepat, token itu bisa “dikembalikan” dan digunakan lagi di langkah berikutnya.

Hal ini akan menjadi dasar cara parser berkomunikasi dengan lexer untuk meminta token. Metode `next_token()` dari kelas `bahasaKu`, yang ditunjukkan di bawah ini, akan digunakan untuk tugas tersebut.
```python
def next_token(self):
        if self.returned_token:
            tok = self.returned_token
            self.returned_token = None
        else:
            try:
                tok = next(self.token_feed)
            except StopIteration:
                tok = None
        return tok
```

Potongan kode di atas adalah fungsi `next_token()` yang bertugas mengambil token berikutnya dari aliran token (`self.token_feed`). Jika sebelumnya ada token yang dikembalikan (disimpan sementara di `self.return_token`), maka token itu yang akan digunakan terlebih dahulu. Jika tidak ada, fungsi mencoba mengambil token baru dari generator `token_feed` menggunakan `next()`. Jika token sudah habis (menimbulkan StopIteration), maka fungsi mengembalikan `None`.
:::info
Output:
```
coba = bahasaKu('''cetak 2
cetak 6
''')
coba.next_token()
('cetak',)
coba.next_token()
('num', 2)
coba.next_token()
('\n',)
coba.next_token()
('cetak',)
coba.next_token()
('num', 6)
```
![image](https://hackmd.io/_uploads/HJd3fGCTgg.png)


:::
### 2. Method `return_token()`
```python
class bahasaKu:
    # ...
    def return_token(self, token):
        self.returned_token = token
```

Fungsi `return_token(self, token)` digunakan untuk menyimpan kembali token yang sudah diambil, agar bisa digunakan lagi nanti. Biasanya dipakai saat parser membaca token yang ternyata belum waktunya diproses. Dengan menyimpannya ke `self.returned_token`, interpreter bisa “mengembalikan” token tersebut seolah belum diambil, sehingga proses pembacaan token tetap rapi dan terkontrol.
:::info
Output:
```
coba = bahasaKu('''cetak 2
cetak 6
''')
coba.next_token()
('cetak',)
coba.next_token()
('num', 2)
coba.return_tokens(('num',2))
coba.next_token()
('num', 2)
coba.next_token()
```

![image](https://hackmd.io/_uploads/SkR2NG0Tgl.png)

:::
### 3. Melakukan `return_token()` lebih dari satu kali
:::info
```
coba = bahasaKu('''cetak 2
cetak 6
''')
coba.next_token()
('cetak',)
coba.next_token()
('num', 2)
coba.return_token(('angka',2))
coba.next_token()
('angka', 2)
coba.next_token()
('\n',)
coba.return_token(('\n',))
coba.return_token(('num',2))

Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    coba.return_token(('num',2))
  File "C:\Users\muham\Downloads\New folder\bahasaKu.py", line 45, in return_token
    raise RuntimeError('Cannot return more than one token at a time')
RuntimeError: Cannot return more than one token at a time
```
![image](https://hackmd.io/_uploads/rko8BMAplx.png)


Berikut adalah program lexer secara lengkap
```python

import sys

class BahasaKu:
    def __init__(self, code):
        self.code = code
        self.line_nr = 0
        self.returned_token = None
        self.token_feed = self.tokens()
        self.stack = []
        self.vars = {}
        self.loop_stack = []  # untuk break/continue
        self.skip_remaining_blocks = False

        # tambahkan 'in' dan 'end' sebagai keyword
        self.KEYWORDS = ['cetak', 'if', 'elif', 'else', 'while', 'for', 'break', 'continue', 'pass', 'end', 'in']
        self.OPERATORS = ['=', '+', '-', '*', '/', '==', '!=', '<', '>', '<=', '>=']
        self.SYMBOLS = ['(', ')', ':']

        # precedence untuk operator evaluasi infix
        self.precedence = {'+':1, '-':1, '*':2, '/':2, '==':0, '!=':0, '<':0, '>':0, '<=':0, '>=':0}

    def raise_error(self, msg):
        raise ValueError(f"Baris {self.line_nr}: {msg}")

    # ===== TOKENIZER =====
    def tokens(self):
        for line in self.code.splitlines():
            self.line_nr += 1
            line = line.strip()
            if line == '':
                yield ('NEWLINE',)
                continue

            i = 0
            tokens = []
            while i < len(line):
                ch = line[i]
                if ch.isspace():
                    i += 1
                    continue
                # === TAMBAHAN: Deteksi string literal ===
                if ch in '"\'':
                    quote = ch
                    i += 1
                    start = i
                    while i < len(line) and line[i] != quote:
                        i += 1
                    if i >= len(line):
                        self.raise_error(f"String tidak ditutup")
                    string_val = line[start:i]
                    tokens.append(('STR', string_val))
                    i += 1  # skip closing quote
                    continue
                # === AKHIR TAMBAHAN ===
                # dua-character operators
                if i+1 < len(line) and line[i:i+2] in ['==', '!=', '<=', '>=']:
                    tokens.append(line[i:i+2])
                    i += 2
                    continue
                # single-char operators or symbols
                if ch in "=+-*/()<>:":
                    tokens.append(ch)
                    i += 1
                    continue
                # number (int or float)
                if ch.isdigit() or (ch == '.' and i+1 < len(line) and line[i+1].isdigit()):
                    start = i
                    has_dot = False
                    while i < len(line) and (line[i].isdigit() or line[i] == '.'):
                        if line[i] == '.':
                            if has_dot:
                                self.raise_error("Angka tidak valid")
                            has_dot = True
                        i += 1
                    tokens.append(line[start:i])
                    continue
                # identifier / keyword
                if ch.isalpha() or ch == '_':
                    start = i
                    while i < len(line) and (line[i].isalnum() or line[i] == '_'):
                        i += 1
                    tokens.append(line[start:i])
                    continue
                self.raise_error(f"Token tidak sah: {ch}")

            # convert raw tokens to typed tokens
            for t in tokens:
                # === TAMBAHAN: Handle tuple token (sudah typed) ===
                if isinstance(t, tuple):
                    yield t
                    continue
                # === AKHIR TAMBAHAN ===
                if t in self.KEYWORDS:
                    yield ('KEY', t)
                elif t in self.OPERATORS:
                    yield ('OP', t)
                elif t in self.SYMBOLS:
                    yield ('SYM', t)
                elif t.replace('.', '', 1).isdigit():
                    if '.' in t:
                        yield ('FLOAT', float(t))
                    else:
                        yield ('INT', int(t))
                else:
                    yield ('IDF', t)
            yield ('NEWLINE',)

    # ===== token helpers =====
    def next_token(self):
        if self.returned_token is not None:
            tok = self.returned_token
            self.returned_token = None
            return tok
        try:
            return next(self.token_feed)
        except StopIteration:
            return None

    def return_token(self, token):
        # allow putting back None as well (no-op)
        if token is None:
            return
        if self.returned_token is not None:
            # shouldn't happen; but handle gracefully by overwriting
            self.returned_token = token
        else:
            self.returned_token = token

```
:::
### RANGKUMAN
1. Analisis Leksikal (lexer) adalah tahap awal dalam kompilasi/interpretasi bahasa pemrograman, yaitu  memecah teks kode sumber menjadi token.
2. Token adalah unit terkecil dari kode yang masih bermakna.

---

2. {%preview https://eddieantonio.ca/blog/2023/10/25/python-is-a-compiled-language/ %} 
3.  {%preview https://www.google.co.id/books/edition/PYTHON_CODE_HUNT_BEGINNERS_PLAY_BOOK/Yo9gEQAAQBAJ?hl=id&gbpv=1&dq=compiler+python+ebook&pg=PA5&printsec=frontcover%}
4. {%preview https://www.google.co.id/books/edition/Oxford_Resources_for_IB_DP_Computer_Scie/XxtXEQAAQBAJ?hl=id&gbpv=1&dq=python+compiler+or+interpreter+ebook&pg=PA92&printsec=frontcover %}
5. {%preview https://ntietz.com/blog/python-is-an-interpreted-language-with-a-compiler/ %}
6. {%preview https://www.google.co.id/books/edition/Python_Made_Simple/rLxIEAAAQBAJ?hl=id&gbpv=1&dq=compiler+in+python&pg=PT46&printsec=frontcover %}
7. {%preview https://dev.to/cwprogram/a-tour-of-cpython-compilation-cd5#what-is-cpython %}
8. {%preview https://www.asmeurer.com/brown-water-python/ %}
9. {%preview https://repository.unikom.ac.id/48769/1/Compilers%20-%20Principles,%20Techniques,%20and%20Tools%20(2006).pdf %}