Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov flake8
pip install pytest pytest-asyncio pytest-cov flake8
pip install -e .

- name: Lint with flake8
Expand Down
7 changes: 7 additions & 0 deletions reproduce_async.cy
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
async fungsi tes_async():
tulis("Memulai...")
menunggu async_tidur(1)
tulis("Selesai setelah 1 detik")

async fungsi main():
menunggu tes_async()
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def read_requirements():
extras_require={
"dev": [
"pytest>=6.0",
"pytest-asyncio>=0.14.0",
"pytest-cov>=2.0",
"black>=21.0",
"mypy>=0.900",
Expand Down
14 changes: 7 additions & 7 deletions src/codingyok/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(
self.superclass = superclass
self.methods = methods

def call(
async def call(
self,
interpreter: "CodingYokInterpreter",
arguments: List[Any],
Expand All @@ -37,7 +37,7 @@ def call(
# Call __init__ if it exists
initializer = self.find_method("__init__")
if initializer:
initializer.bind(instance).call(interpreter, arguments, keyword_args)
await initializer.bind(instance).call(interpreter, arguments, keyword_args)

return instance

Expand Down Expand Up @@ -88,7 +88,7 @@ def __init__(self, instance: CodingYokInstance, method: Any):
self.instance = instance
self.method = method

def call(
async def call(
self,
interpreter: "CodingYokInterpreter",
arguments: List[Any],
Expand All @@ -98,7 +98,7 @@ def call(
if keyword_args is None:
keyword_args = {}
# Add 'diri' (self) as first argument
return self.method.call(interpreter, [self.instance] + arguments, keyword_args)
return await self.method.call(interpreter, [self.instance] + arguments, keyword_args)

def __str__(self) -> str:
return f"<bound method {self.method.declaration.name}>"
Expand All @@ -116,7 +116,7 @@ def bind(self, instance: CodingYokInstance) -> CodingYokBoundMethod:
"""Bind this method to an instance"""
return CodingYokBoundMethod(instance, self)

def call(
async def call(
self,
interpreter: "CodingYokInterpreter",
arguments: List[Any],
Expand All @@ -142,7 +142,7 @@ def call(
elif i < len(arguments):
environment.define(param, arguments[i])
elif i < len(defaults) and defaults[i] is not None:
default_value = interpreter.evaluate(defaults[i]) # type: ignore
default_value = await interpreter.evaluate(defaults[i]) # type: ignore
environment.define(param, default_value)
else:
raise CodingYokRuntimeError(f"Parameter '{param}' tidak memiliki nilai")
Expand All @@ -153,7 +153,7 @@ def call(
interpreter.environment = environment

for statement in self.declaration.body:
interpreter.execute(statement)
await interpreter.execute(statement)

return None # No explicit return

Expand Down
23 changes: 12 additions & 11 deletions src/codingyok/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import sys
import argparse
import asyncio
from pathlib import Path
from typing import Optional

Expand All @@ -14,15 +15,15 @@
from . import __version__


def run_file(file_path: str) -> None:
"""Run a CodingYok file"""
async def run_file_async(file_path: str) -> None:
"""Run a CodingYok file asynchronously"""
try:
with open(file_path, "r", encoding="utf-8") as file:
source_code = file.read()

# Get the directory of the script for module imports
script_dir = str(Path(file_path).parent.absolute())
run_code(source_code, file_path, script_dir)
await run_code_async(source_code, file_path, script_dir)

except FileNotFoundError:
print(f"Error: File '{file_path}' tidak ditemukan.", file=sys.stderr)
Expand All @@ -35,10 +36,10 @@ def run_file(file_path: str) -> None:
sys.exit(1)


def run_code(
async def run_code_async(
source_code: str, filename: str = "<stdin>", script_dir: Optional[str] = None
) -> None:
"""Run CodingYok source code"""
"""Run CodingYok source code asynchronously"""
try:
# Tokenize
lexer = CodingYokLexer(source_code)
Expand All @@ -50,7 +51,7 @@ def run_code(

# Interpret
interpreter = CodingYokInterpreter(script_dir=script_dir)
interpreter.interpret(ast)
await interpreter.interpret(ast)

except CodingYokError as error:
source_lines = source_code.splitlines()
Expand All @@ -62,8 +63,8 @@ def run_code(
sys.exit(1)


def run_repl() -> None:
"""Run interactive REPL"""
async def run_repl_async() -> None:
"""Run interactive REPL asynchronously"""
print(f"CodingYok v{__version__} - Bahasa Pemrograman Indonesia")
print("Ketik 'keluar()' atau tekan Ctrl+C untuk keluar.")
print("=" * 50)
Expand Down Expand Up @@ -91,7 +92,7 @@ def run_repl() -> None:
parser = CodingYokParser(tokens)
ast = parser.parse()

interpreter.interpret(ast)
await interpreter.interpret(ast)

except CodingYokError as error:
source_lines = [line]
Expand Down Expand Up @@ -187,9 +188,9 @@ def main() -> None:
if not args.file.endswith(".cy"):
print("Warning: File tidak memiliki ekstensi .cy", file=sys.stderr)

run_file(args.file)
asyncio.run(run_file_async(args.file))
else:
run_repl()
asyncio.run(run_repl_async())


if __name__ == "__main__":
Expand Down
15 changes: 15 additions & 0 deletions src/codingyok/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def __init__(

def format_traceback(error: CodingYokError, source_lines: Optional[list] = None) -> str:
"""Format a nice traceback for CodingYok errors"""
from .tokens import INDONESIAN_KEYWORDS

lines = []

lines.append("=" * 50)
Expand Down Expand Up @@ -173,6 +175,15 @@ def format_traceback(error: CodingYokError, source_lines: Optional[list] = None)
lines.append(" - Periksa tanda kurung, kurung siku, dan kurung kurawal")
lines.append(" - Pastikan indentasi konsisten")
lines.append(" - Periksa ejaan kata kunci bahasa Indonesia")

# Check for common keyword typos if it's a syntax error
msg_parts = error.message.split("'")
if len(msg_parts) >= 2:
typo = msg_parts[1]
matches = get_close_matches(typo, list(INDONESIAN_KEYWORDS.keys()))
if matches:
lines.append(f" - Apakah maksud Anda keyword: {', '.join(matches)}?")

elif isinstance(error, CodingYokNameError):
lines.append("💡 Tips:")
lines.append(" - Pastikan variabel sudah didefinisikan sebelum digunakan")
Expand All @@ -181,6 +192,10 @@ def format_traceback(error: CodingYokError, source_lines: Optional[list] = None)
lines.append("💡 Tips:")
lines.append(" - Periksa tipe data yang digunakan")
lines.append(" - Pastikan operasi sesuai dengan tipe data")
elif isinstance(error, CodingYokZeroDivisionError):
lines.append("💡 Tips:")
lines.append(" - Pastikan pembagi bukan nol")
lines.append(" - Gunakan blok 'coba...kecuali' untuk menangani kasus ini")

lines.append("=" * 50)

Expand Down
29 changes: 29 additions & 0 deletions src/codingyok/indonesia.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,32 @@ def jarak_kota(kota1: str, kota2: str) -> Optional[str]:
return distances.get(key1) or distances.get(key2) or "Jarak tidak tersedia"


def hitung_umur(tanggal_lahir: str) -> int:
"""Calculate age from birth date (YYYY-MM-DD)"""
try:
lahir = datetime.datetime.strptime(tanggal_lahir, "%Y-%m-%d")
sekarang = datetime.datetime.now()
umur = sekarang.year - lahir.year - ((sekarang.month, sekarang.day) < (lahir.month, lahir.day))
return umur
except ValueError:
raise CodingYokValueError("Format tanggal lahir harus YYYY-MM-DD")


def bersihkan_teks(teks: str) -> str:
"""Clean text from special characters, keeping only alphanumeric and basic punctuation"""
import re
# Keep alphanumeric, spaces, and basic punctuation
cleaned = re.sub(r'[^a-zA-Z0-9\s.,!?()-]', '', teks)
return cleaned.strip()


def validasi_email(email: str) -> bool:
"""Validate email format"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool(re.match(pattern, email))


def get_indonesian_functions() -> Dict[str, Any]:
"""Get all Indonesian-specific functions"""
return {
Expand All @@ -383,6 +409,9 @@ def get_indonesian_functions() -> Dict[str, Any]:
"validasi_nik": validasi_nik,
"konversi_suhu": konversi_suhu,
"jarak_kota": jarak_kota,
"hitung_umur": hitung_umur,
"bersihkan_teks": bersihkan_teks,
"validasi_email": validasi_email,
# Constants
"PROVINSI": PROVINSI_INDONESIA,
"KOTA_BESAR": KOTA_BESAR_INDONESIA,
Expand Down
Loading
Loading