From 4939c4edd58f849e0dc04681e59ea8a83e9e0ae1 Mon Sep 17 00:00:00 2001 From: a2nr Date: Wed, 14 Jan 2026 10:35:20 +0700 Subject: [PATCH] update multi language programing (currently c and python) --- README.md | 70 +++++++++++--- app.py | 78 +++++----------- compiler/__init__.py | 62 +++++++++++++ compiler/base_compiler.py | 69 ++++++++++++++ compiler/c_compiler.py | 114 +++++++++++++++++++++++ compiler/python_compiler.py | 85 +++++++++++++++++ example.env.production | 4 + example_content/introduction_to_c.md | 33 ------- example_content/python_intro.md | 52 +++++++++++ example_content/variables_and_types.md | 121 +++++++++++++++++++++++++ podman-compose.yml | 2 + templates/lesson.html | 20 ++-- 12 files changed, 601 insertions(+), 109 deletions(-) create mode 100644 compiler/__init__.py create mode 100644 compiler/base_compiler.py create mode 100644 compiler/c_compiler.py create mode 100644 compiler/python_compiler.py delete mode 100644 example_content/introduction_to_c.md create mode 100644 example_content/python_intro.md create mode 100644 example_content/variables_and_types.md diff --git a/README.md b/README.md index dc468de..2ad910c 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,14 @@ A web-based learning management system for C programming with interactive exerci ## Features - View lessons written in Markdown format -- Interactive C code editor with compilation and execution +- Interactive code editor with compilation and execution for multiple programming languages - Real-time feedback on code compilation and execution - No database required - content stored as Markdown files - Student token-based progress tracking system - No authentication required - ready to use out of the box - Containerized with Podman for easy deployment +- Support for multiple programming languages (C, Python, and extensible for others) +- Configurable default programming language via environment variables ## Prerequisites @@ -747,18 +749,58 @@ Monitor the LMS server's resource usage (CPU, memory, disk I/O) during load test - Database performance (if one is added in the future) - Container resource limits -The included `locustfile.py` simulates the following user behaviors: -- Browsing the home page -- Viewing lessons -- Compiling C code -- Validating student tokens -- Logging in with tokens -- Tracking student progress +## Multi-Language Programming Support -### Monitoring Performance +The system now supports multiple programming languages with a modular compiler architecture. Currently supports C and Python with easy extensibility for additional languages. -Monitor the LMS server's resource usage (CPU, memory, disk I/O) during load testing to identify potential bottlenecks. Pay attention to: -- Response times for API requests -- Compilation performance under load -- Database performance (if one is added in the future) -- Container resource limits +### Supported Languages + +- **C**: Compiled using GCC with standard compilation and execution workflow +- **Python**: Interpreted using Python 3 with syntax checking and execution + +### Configuration + +The default programming language can be configured via environment variables in your `.env` file: + +``` +# To use C as default language (default setting) +DEFAULT_PROGRAMMING_LANGUAGE=c + +# To use Python as default language +DEFAULT_PROGRAMMING_LANGUAGE=python +``` + +### Extending to Additional Languages + +The system is designed to be easily extended with additional programming languages: + +1. Create a new compiler class that inherits from `BaseCompiler` in the `compiler/` directory +2. Implement the `compile()` and `run()` methods for your language +3. Register the new compiler in the `CompilerFactory` in `compiler/__init__.py` +4. Update the UI if needed to support the new language + +Example structure for a new language compiler: +```python +# compiler/new_language_compiler.py +from .base_compiler import BaseCompiler +import subprocess + +class NewLanguageCompiler(BaseCompiler): + def __init__(self): + super().__init__("NewLanguage", ".nl") # Replace with appropriate extension + + def compile(self, file_path, timeout=10): + # Implement compilation logic for your language + pass + + def run(self, file_path, timeout=5): + # Implement execution logic for your language + pass +``` + +### Language-Specific Features + +- The code editor header now dynamically displays the active programming language +- File extensions are automatically adjusted based on the selected language +- The system maintains backward compatibility with existing C lessons +- All language compilation follows the same security and timeout constraints diff --git a/app.py b/app.py index 5c05b6d..e2b51d4 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,9 @@ app = Flask(__name__) # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +# Import compiler module after app initialization to avoid circular imports +from compiler import compiler_factory + # Load configuration from environment variables with defaults CONTENT_DIR = os.environ.get('CONTENT_DIR', 'content') STATIC_DIR = os.environ.get('STATIC_DIR', 'static') @@ -571,6 +574,10 @@ int main() { # Get ordered lessons for the sidebar ordered_lessons = get_ordered_lessons_with_learning_objectives(progress) + # Get the programming language from environment variable + programming_language = os.environ.get('DEFAULT_PROGRAMMING_LANGUAGE', 'c').lower() + language_display_name = compiler_factory.get_language_display_name(programming_language) + return render_template('lesson.html', lesson_content=lesson_html, exercise_content=exercise_html, @@ -587,20 +594,25 @@ int main() { ordered_lessons=ordered_lessons, app_bar_title=APP_BAR_TITLE, copyright_text=COPYRIGHT_TEXT, - page_title_suffix=PAGE_TITLE_SUFFIX) + page_title_suffix=PAGE_TITLE_SUFFIX, + language=programming_language, + language_display_name=language_display_name) + @app.route('/compile', methods=['POST']) def compile_code(): - """Compile and run C code submitted by the user""" + """Compile and run code submitted by the user in the selected programming language""" try: code = None + language = None - # Try to get code from JSON data + # Try to get code and language from JSON data if request.content_type and 'application/json' in request.content_type: try: json_data = request.get_json(force=True) - if json_data and 'code' in json_data: - code = json_data['code'] + if json_data: + code = json_data.get('code', '') + language = json_data.get('language', '') # Get language from request except Exception as e: # Log the error for debugging print(f"JSON parsing error: {e}") @@ -609,6 +621,7 @@ def compile_code(): # If not found in JSON, try form data if not code: code = request.form.get('code', '') + language = request.form.get('language', '') # Get language from form if not code: return jsonify({ @@ -617,58 +630,11 @@ def compile_code(): 'error': 'No code provided' }) - # Create a temporary file for the C code - with tempfile.NamedTemporaryFile(mode='w', suffix='.c', delete=False) as temp_c: - temp_c.write(code) - temp_c_path = temp_c.name + # Get the appropriate compiler based on the language + compiler = compiler_factory.get_compiler(language) - # Create a temporary file for the executable - temp_exe_path = temp_c_path.replace('.c', '') - - # Compile the C code - compile_result = subprocess.run( - ['gcc', temp_c_path, '-o', temp_exe_path], - capture_output=True, - text=True, - timeout=10 - ) - - if compile_result.returncode != 0: - # Compilation failed - result = { - 'success': False, - 'output': compile_result.stdout, # Include any stdout if available - 'error': compile_result.stderr # Show GCC error messages - } - else: - # Compilation succeeded, run the program - try: - run_result = subprocess.run( - [temp_exe_path], - capture_output=True, - text=True, - timeout=5 - ) - - result = { - 'success': True, - 'output': run_result.stdout, - 'error': run_result.stderr if run_result.stderr else None - } - except subprocess.TimeoutExpired: - result = { - 'success': False, - 'output': '', - 'error': 'Program execution timed out' - } - - # Clean up temporary files - try: - os.remove(temp_c_path) - if os.path.exists(temp_exe_path): - os.remove(temp_exe_path) - except: - pass # Ignore cleanup errors + # Compile and run the code using the selected compiler + result = compiler.compile_and_run(code) return jsonify(result) diff --git a/compiler/__init__.py b/compiler/__init__.py new file mode 100644 index 0000000..19bdb91 --- /dev/null +++ b/compiler/__init__.py @@ -0,0 +1,62 @@ +""" +Compiler Factory for managing different language compilers +""" + +import os +from .c_compiler import CCompiler +from .python_compiler import PythonCompiler + + +class CompilerFactory: + """ + Factory class to create and manage different language compilers + """ + + def __init__(self): + self.compilers = { + 'c': CCompiler(), + 'python': PythonCompiler() + } + + # Get default language from environment variable + default_language = os.environ.get('DEFAULT_PROGRAMMING_LANGUAGE', 'c').lower() + self.default_compiler = self.compilers.get(default_language, self.compilers['c']) + + def get_compiler(self, language=None): + """ + Get a compiler instance for the specified language + :param language: Language identifier ('c', 'python', etc.) + :return: Compiler instance + """ + if language is None: + return self.default_compiler + + language = language.lower() + if language in self.compilers: + return self.compilers[language] + else: + # Return default compiler if requested language is not available + return self.default_compiler + + def get_available_languages(self): + """ + Get list of available programming languages + :return: List of available language identifiers + """ + return list(self.compilers.keys()) + + def get_language_display_name(self, language): + """ + Get display name for a language + :param language: Language identifier + :return: Display name for the language + """ + display_names = { + 'c': 'C', + 'python': 'Python' + } + return display_names.get(language, language.capitalize()) + + +# Global compiler factory instance +compiler_factory = CompilerFactory() \ No newline at end of file diff --git a/compiler/base_compiler.py b/compiler/base_compiler.py new file mode 100644 index 0000000..7ad3bf0 --- /dev/null +++ b/compiler/base_compiler.py @@ -0,0 +1,69 @@ +""" +Base compiler class for the modular compilation system +""" + +import os +import subprocess +import tempfile +from abc import ABC, abstractmethod + + +class BaseCompiler(ABC): + """ + Abstract base class for all compilers + """ + + def __init__(self, language, file_extension): + self.language = language + self.file_extension = file_extension + + @abstractmethod + def compile(self, code, timeout=10): + """ + Compile the code and return the result + :param code: Source code to compile + :param timeout: Compilation timeout in seconds + :return: Dictionary with success, output, and error fields + """ + pass + + @abstractmethod + def run(self, file_path, timeout=5): + """ + Run the compiled code and return the result + :param file_path: Path to the compiled file + :param timeout: Execution timeout in seconds + :return: Dictionary with success, output, and error fields + """ + pass + + def compile_and_run(self, code, compile_timeout=10, run_timeout=5): + """ + Compile and run the code in one step + :param code: Source code to compile and run + :param compile_timeout: Compilation timeout in seconds + :param run_timeout: Execution timeout in seconds + :return: Dictionary with success, output, and error fields + """ + # Create a temporary file for the source code + with tempfile.NamedTemporaryFile(mode='w', suffix=self.file_extension, delete=False) as temp_source: + temp_source.write(code) + temp_source_path = temp_source.name + + try: + # Compile the code + compile_result = self.compile(temp_source_path, compile_timeout) + + if not compile_result['success']: + return compile_result + + # Run the code + run_result = self.run(temp_source_path, run_timeout) + return run_result + + finally: + # Clean up temporary files + try: + os.remove(temp_source_path) + except: + pass # Ignore cleanup errors \ No newline at end of file diff --git a/compiler/c_compiler.py b/compiler/c_compiler.py new file mode 100644 index 0000000..0a52c94 --- /dev/null +++ b/compiler/c_compiler.py @@ -0,0 +1,114 @@ +""" +C Compiler implementation for the modular compilation system +""" + +import subprocess +import os +from .base_compiler import BaseCompiler + + +class CCompiler(BaseCompiler): + """ + Compiler class for C programming language + """ + + def __init__(self): + super().__init__("C", ".c") + + def compile(self, file_path, timeout=10): + """ + Compile C code + :param file_path: Path to the C source file + :param timeout: Compilation timeout in seconds + :return: Dictionary with success, output, and error fields + """ + # Create a temporary file for the executable + temp_exe_path = file_path.replace('.c', '') + + try: + # Compile the C code + compile_result = subprocess.run( + ['gcc', file_path, '-o', temp_exe_path], + capture_output=True, + text=True, + timeout=timeout + ) + + if compile_result.returncode != 0: + # Compilation failed + return { + 'success': False, + 'output': compile_result.stdout, + 'error': compile_result.stderr + } + else: + # Compilation succeeded + return { + 'success': True, + 'output': compile_result.stdout, + 'error': compile_result.stderr if compile_result.stderr else None, + 'executable_path': temp_exe_path + } + except subprocess.TimeoutExpired: + return { + 'success': False, + 'output': '', + 'error': 'Compilation timed out' + } + except Exception as e: + return { + 'success': False, + 'output': '', + 'error': f'Compilation error: {str(e)}' + } + + def run(self, file_path, timeout=5): + """ + Run compiled C program + :param file_path: Path to the C source file (executable will be derived) + :param timeout: Execution timeout in seconds + :return: Dictionary with success, output, and error fields + """ + temp_exe_path = file_path.replace('.c', '') + + if not os.path.exists(temp_exe_path): + return { + 'success': False, + 'output': '', + 'error': 'Executable not found' + } + + try: + # Run the compiled program + run_result = subprocess.run( + [temp_exe_path], + capture_output=True, + text=True, + timeout=timeout + ) + + result = { + 'success': True, + 'output': run_result.stdout, + 'error': run_result.stderr if run_result.stderr else None + } + + # Clean up the executable + try: + os.remove(temp_exe_path) + except: + pass # Ignore cleanup errors + + return result + except subprocess.TimeoutExpired: + return { + 'success': False, + 'output': '', + 'error': 'Program execution timed out' + } + except Exception as e: + return { + 'success': False, + 'output': '', + 'error': f'Runtime error: {str(e)}' + } \ No newline at end of file diff --git a/compiler/python_compiler.py b/compiler/python_compiler.py new file mode 100644 index 0000000..c2dd385 --- /dev/null +++ b/compiler/python_compiler.py @@ -0,0 +1,85 @@ +""" +Python Compiler/Interpreter implementation for the modular compilation system +""" + +import subprocess +import tempfile +import os +from .base_compiler import BaseCompiler + + +class PythonCompiler(BaseCompiler): + """ + Compiler/Interpreter class for Python programming language + """ + + def __init__(self): + super().__init__("Python", ".py") + + def compile(self, file_path, timeout=10): + """ + Check Python syntax (simulate compilation) + :param file_path: Path to the Python source file + :param timeout: Compilation timeout in seconds + :return: Dictionary with success, output, and error fields + """ + try: + # Use Python's compile function to check syntax + with open(file_path, 'r', encoding='utf-8') as f: + code = f.read() + + # Attempt to compile the code to check for syntax errors + compile(code, file_path, 'exec') + + # If we reach this point, syntax is valid + return { + 'success': True, + 'output': '', + 'error': None + } + except SyntaxError as e: + return { + 'success': False, + 'output': '', + 'error': f"SyntaxError: {e.msg} at line {e.lineno}" + } + except Exception as e: + return { + 'success': False, + 'output': '', + 'error': f'Syntax check error: {str(e)}' + } + + def run(self, file_path, timeout=5): + """ + Run Python code + :param file_path: Path to the Python source file + :param timeout: Execution timeout in seconds + :return: Dictionary with success, output, and error fields + """ + try: + # Run the Python script + run_result = subprocess.run( + ['python3', file_path], + capture_output=True, + text=True, + timeout=timeout + ) + + return { + 'success': True, + 'output': run_result.stdout, + 'error': run_result.stderr if run_result.stderr else None + } + except subprocess.TimeoutExpired: + return { + 'success': False, + 'output': '', + 'error': 'Program execution timed out' + } + except Exception as e: + return { + 'success': False, + 'output': '', + 'error': f'Runtime error: {str(e)}' + } \ No newline at end of file diff --git a/example.env.production b/example.env.production index 6d69ef1..8e8ec73 100644 --- a/example.env.production +++ b/example.env.production @@ -19,6 +19,10 @@ APP_BAR_TITLE=Belajar Pemrograman C COPYRIGHT_TEXT=Sistem Pembelajaran Pemrograman C © 2025 PAGE_TITLE_SUFFIX=Belajar Pemrograman C +# Programming Language Configuration +DEFAULT_PROGRAMMING_LANGUAGE=c +# To use Python as default language instead, uncomment the line below: +# DEFAULT_PROGRAMMING_LANGUAGE=python # Server Configuration HOST=0.0.0.0 diff --git a/example_content/introduction_to_c.md b/example_content/introduction_to_c.md deleted file mode 100644 index fa17a7d..0000000 --- a/example_content/introduction_to_c.md +++ /dev/null @@ -1,33 +0,0 @@ -# Welcome to C Programming Learning System - -This is a comprehensive learning platform designed to help you master the C programming language through interactive lessons and exercises. - -## Learning Objectives - -| Module | Objective | Skills Acquired | -|--------|-----------|-----------------| -| Introduction to C | Understand basic syntax and structure | Writing first C program | -| Variables & Data Types | Learn about different data types | Proper variable declaration | -| Control Structures | Master conditional statements and loops | Logic implementation | -| Functions | Create and use functions | Code organization | -| Arrays & Pointers | Work with complex data structures | Memory management | - -## How to Use This System - -1. **Browse Lessons**: Select from the available lessons on the left -2. **Read Content**: Study the lesson materials and examples -3. **Practice Coding**: Use the integrated code editor to write and test C code -4. **Complete Exercises**: Apply your knowledge to solve programming challenges -5. **Get Feedback**: See immediate results of your code execution - -## Getting Started - -Start with the "Introduction to C" lesson to begin your journey in C programming. Each lesson builds upon the previous one, so it's recommended to follow them in order. - -Happy coding! - ----Available_Lessons--- - -1. [Introduction to C Programming](lesson/introduction_to_c.md) -2. [Variables and Data Types in C](lesson/variables_and_data_types.md) - diff --git a/example_content/python_intro.md b/example_content/python_intro.md new file mode 100644 index 0000000..4d47f4f --- /dev/null +++ b/example_content/python_intro.md @@ -0,0 +1,52 @@ +# Python Introduction Lesson + +This lesson introduces the basics of Python programming. + +## Variables and Data Types + +Python is a high-level programming language known for its simplicity and readability. + +```python +# This is a comment in Python +name = "John" # String variable +age = 25 # Integer variable +height = 5.9 # Float variable +is_student = True # Boolean variable + +print(f"Name: {name}, Age: {age}, Height: {height}, Student: {is_student}") +``` + +## Basic Operations + +You can perform basic arithmetic operations in Python: + +```python +a = 10 +b = 5 + +addition = a + b +subtraction = a - b +multiplication = a * b +division = a / b + +print(f"Addition: {addition}") +print(f"Subtraction: {subtraction}") +print(f"Multiplication: {multiplication}") +print(f"Division: {division}") +``` + +---EXERCISE--- + +Write a Python program that prints "Hello, Python!" to the console. + +---INITIAL_CODE--- +print("Write your Python code here") +---END_INITIAL_CODE--- + +---EXPECTED_OUTPUT--- +Hello, Python! +---END_EXPECTED_OUTPUT--- + +---SOLUTION_CODE--- +print("Hello, Python!") +---END_SOLUTION_CODE--- diff --git a/example_content/variables_and_types.md b/example_content/variables_and_types.md new file mode 100644 index 0000000..a27c33f --- /dev/null +++ b/example_content/variables_and_types.md @@ -0,0 +1,121 @@ +---LESSON_INFO--- +**Learning Objectives:** +- Memahami berbagai tipe data dalam bahasa C +- Belajar mendeklarasikan dan menginisialisasi variabel +- Mengenal batas-batas masing-masing tipe data +- Memahami perbedaan antara tipe data signed dan unsigned + +**Prerequisites:** +- Dasar-dasar pemrograman +- Pemahaman tentang program Halo Dunia + +---END_LESSON_INFO--- +# Tipe Data dan Variabel dalam C + +C memiliki beberapa jenis variabel, tetapi ada beberapa tipe dasar: + +* Bilangan Bulat - bilangan bulat yang bisa positif atau negatif. Didefinisikan menggunakan `char`, `int`, `short`, `long` atau `long long`. +* Bilangan Bulat Tak Bertanda - bilangan bulat yang hanya bisa positif. Didefinisikan menggunakan `unsigned char`, `unsigned int`, `unsigned short`, `unsigned long` atau `unsigned long long`. +* Bilangan Pecahan - bilangan real (bilangan dengan pecahan). Didefinisikan menggunakan `float` dan `double`. +* Struktur - akan dijelaskan nanti, di bagian Struktur. + +## Tipe Data dalam C + +Jenis-jenis variabel yang berbeda menentukan batas-batasnya. Sebuah `char` bisa dari -128 hingga 127, sedangkan sebuah `long` bisa dari -2,147,483,648 hingga 2,147,483,647 (`long` dan tipe data numerik lainnya mungkin memiliki rentang lain di komputer yang berbeda, misalnya - dari –9,223,372,036,854,775,808 hingga 9,223,372,036,854,775,807 di komputer 64-bit). + +Perhatikan bahwa C _tidak_ memiliki tipe boolean. Biasanya, itu didefinisikan menggunakan notasi berikut: + +```c +#define BOOL char +#define FALSE 0 +#define TRUE 1 +``` + +C menggunakan array karakter untuk mendefinisikan string, dan akan dijelaskan di bagian String. + +## Mendefinisikan variabel +Untuk angka, kita biasanya akan menggunakan tipe `int`. Di kebanyakan komputer saat ini, itu adalah bilangan 32-bit, yang berarti angkanya bisa dari -2,147,483,648 hingga 2,147,483,647. + +Untuk mendefinisikan variabel `foo` dan `bar`, kita perlu menggunakan sintaks berikut: + +```c +int foo; +int bar = 1; +``` + +Variabel `foo` bisa digunakan, tetapi karena kita tidak menginisialisasinya, kita tidak tahu apa yang ada di dalamnya. Variabel `bar` berisi angka 1. + +Sekarang, kita bisa melakukan beberapa operasi matematika. Dengan mengasumsikan `a`, `b`, `c`, `d`, dan `e` adalah variabel, kita bisa menggunakan operator penjumlahan, pengurangan dan perkalian dalam notasi berikut, dan memberikan nilai baru ke `a`: + +```c +int a = 0, b = 1, c = 2, d = 3, e = 4; +a = b - c + d * e; +printf("%d", a); /* akan mencetak 1-2+3*4 = 11 */ +``` + +--- + +## Tabel Tipe Data dalam C + +| Tipe | Ukuran (bit) | Rentang Nilai | Contoh | +|------|--------------|---------------|--------| +| char | 8 | -128 hingga 127 | `char grade = 'A';` | +| int | 32 | -2,147,483,648 hingga 2,147,483,647 | `int age = 25;` | +| short | 16 | -32,768 hingga 32,767 | `short year = 2023;` | +| long | 64 | -9,223,372,036,854,775,808 hingga 9,223,372,036,854,775,807 | `long population = 1000000L;` | +| float | 32 | ~7 digit desimal | `float price = 19.99f;` | +| double | 64 | ~15 digit desimal | `double pi = 3.14159;` | +| unsigned char | 8 | 0 hingga 255 | `unsigned char count = 100;` | + +---EXERCISE--- + +# Latihan: Menjumlahkan Variabel + +Di latihan berikutnya, Anda perlu membuat program yang mencetak jumlah dari angka `a`, `b`, dan `c`. + +**Requirements:** +- Hitung jumlah dari variabel a, b, dan c +- Simpan hasilnya dalam variabel sum +- Pastikan tipe data yang digunakan sesuai + +**Expected Output:** +``` +The sum of a, b, and c is 12.750000. +``` + +Try writing your solution in the code editor below! + +---EXPECTED_OUTPUT--- +The sum of a, b, and c is 12.750000. +---END_EXPECTED_OUTPUT--- + +---INITIAL_CODE--- +#include + +int main() { + int a = 3; + float b = 4.5; + double c = 5.25; + float sum; + + /* Kode Anda di sini */ + + printf("The sum of a, b, and c is %f.", sum); + return 0; +} +---END_INITIAL_CODE--- + +---SOLUTION_CODE--- +#include + +int main() { + int a = 3; + float b = 4.5; + double c = 5.25; + float sum; + + sum = a + b + c; + printf("The sum of a, b, and c is %f.", sum); + return 0; +} +---END_SOLUTION_CODE--- \ No newline at end of file diff --git a/podman-compose.yml b/podman-compose.yml index 7d602f4..54bb857 100644 --- a/podman-compose.yml +++ b/podman-compose.yml @@ -4,6 +4,8 @@ services: elemes: build: . container_name: elemes + ports: + - "5000:5000" # Expose port 5000 to the host volumes: - ../content:/app/content - ./static:/app/static diff --git a/templates/lesson.html b/templates/lesson.html index a3d45c0..48ed1b3 100644 --- a/templates/lesson.html +++ b/templates/lesson.html @@ -72,7 +72,7 @@
- C code.c + {{ language_display_name }} code.{{ 'py' if language == 'python' else 'c' }}