diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 659ddd8..fcd3040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,23 +22,8 @@ jobs: toolchain: 1.88 components: clippy - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build --release - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo + uses: Swatinem/rust-cache@v2 - name: Run clippy run: cargo clippy --all-targets --all-features -- -D warnings diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 99bc906..84781bb 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -21,17 +21,8 @@ jobs: with: toolchain: 1.88 - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo + uses: Swatinem/rust-cache@v2 - name: Build documentation run: cargo doc --all-features --no-deps diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a732d6..bd86378 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,23 +26,8 @@ jobs: with: toolchain: ${{ matrix.rust }} - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v4 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build --release - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - name: Cache cargo + uses: Swatinem/rust-cache@v2 - name: Run tests run: cargo test --all-features diff --git a/CHANGELOG.md b/CHANGELOG.md index 89ecc79..ea571b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Logging Module** (Issue #34) + - Complete implementation of Python's `logging` standard library module + - Log level constants: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`, `NOTSET` + - Aliases: `WARN` (for `WARNING`), `FATAL` (for `CRITICAL`) + - Logging functions: `debug()`, `info()`, `warning()`, `error()`, `critical()`, `exception()`, `log()` + - Configuration: `basicConfig()`, `setLevel()`, `disable()` + - Logger management: `getLogger()` + - Handler support: `addHandler()`, `removeHandler()` + - Classes: `Logger`, `Handler`, `StreamHandler`, `FileHandler`, `Formatter`, `Filter`, `LogRecord` + - Test suite in `examples/test_logging.py` + - **JSON Module Runtime Implementation** (Issue #31) - Implemented runtime support for `json.dumps()` - serialize Python objects to JSON strings - Implemented runtime support for `json.loads()` - parse JSON strings to Python objects diff --git a/docs/modules.html b/docs/modules.html index 36dfde8..95c41c0 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -289,7 +289,8 @@

Waspy Development Board

{ name: "datetime module (datetime, date, time, timedelta, timezone, tzinfo, now, today, fromtimestamp, fromisoformat, strftime, strptime, replace, timestamp, isoformat, weekday, isoweekday, MINYEAR, MAXYEAR)", status: "done", version: "0.8.0" }, { name: "collections module (namedtuple, deque, Counter, OrderedDict, defaultdict, ChainMap, UserDict, UserList, UserString)", status: "done", version: "0.8.0" }, { name: "itertools module (count, cycle, repeat, chain, compress, dropwhile, filterfalse, groupby, islice, starmap, takewhile, tee, zip_longest, product, permutations, combinations, combinations_with_replacement, accumulate, batched, pairwise)", status: "done", version: "0.8.0" }, - { name: "functools module (reduce, partial, partialmethod, wraps, update_wrapper, total_ordering, cmp_to_key, lru_cache, cache, cached_property, singledispatch, singledispatchmethod)", status: "done", version: "0.8.0" } + { name: "functools module (reduce, partial, partialmethod, wraps, update_wrapper, total_ordering, cmp_to_key, lru_cache, cache, cached_property, singledispatch, singledispatchmethod)", status: "done", version: "0.8.0" }, + { name: "logging module (DEBUG, INFO, WARNING, ERROR, CRITICAL, NOTSET, debug, info, warning, error, critical, exception, log, basicConfig, getLogger, setLevel, disable, Logger, Handler, StreamHandler, FileHandler, Formatter, Filter, LogRecord)", status: "done", version: "unreleased" } ] }, comprehensions: { diff --git a/examples/test_logging.py b/examples/test_logging.py new file mode 100644 index 0000000..96361e5 --- /dev/null +++ b/examples/test_logging.py @@ -0,0 +1,68 @@ +import logging + +def test_log_levels(): + """Test basic logging level constants.""" + debug = logging.DEBUG + info = logging.INFO + warning = logging.WARNING + error = logging.ERROR + critical = logging.CRITICAL + notset = logging.NOTSET + return 0 + +def test_basic_logging(): + """Test basic logging functions.""" + logging.debug("This is a debug message") + logging.info("This is an info message") + logging.warning("This is a warning message") + logging.error("This is an error message") + logging.critical("This is a critical message") + return 0 + +def test_logging_config(): + """Test logging configuration.""" + logging.basicConfig() + logging.setLevel(logging.DEBUG) + logging.disable(logging.NOTSET) + return 0 + +def test_logger(): + """Test logger creation and usage.""" + logger = logging.getLogger("myapp") + logger = logging.getLogger("myapp.submodule") + return 0 + +def test_handlers(): + """Test handler classes.""" + handler = logging.StreamHandler + formatter = logging.Formatter + return 0 + +def test_log_with_level(): + """Test logging.log() with explicit level.""" + logging.log(logging.INFO, "Message with explicit level") + return 0 + +def test_warn_alias(): + """Test warn as alias for warning.""" + logging.warn("This uses warn alias") + warn_level = logging.WARN + return 0 + +def test_fatal_alias(): + """Test fatal as alias for critical.""" + logging.fatal("This uses fatal alias") + fatal_level = logging.FATAL + return 0 + +def main(): + """Run all logging tests.""" + test_log_levels() + test_basic_logging() + test_logging_config() + test_logger() + test_handlers() + test_log_with_level() + test_warn_alias() + test_fatal_alias() + return 0 diff --git a/src/compiler/expression.rs b/src/compiler/expression.rs index 8784902..a2c476c 100644 --- a/src/compiler/expression.rs +++ b/src/compiler/expression.rs @@ -1590,6 +1590,55 @@ pub fn emit_expr( }; } } + + // Handle logging module functions + if module_name == "logging" { + if let Some(log_func) = crate::stdlib::logging::get_function(method_name) { + // Emit and drop all arguments + for arg in arguments { + let arg_type = emit_expr(arg, func, ctx, memory_layout, None); + match arg_type { + IRType::String => { + // Strings are (offset, length) + func.instruction(&Instruction::Drop); + func.instruction(&Instruction::Drop); + } + _ => { + func.instruction(&Instruction::Drop); + } + } + } + + return match log_func { + crate::stdlib::logging::LoggingFunction::Debug + | crate::stdlib::logging::LoggingFunction::Info + | crate::stdlib::logging::LoggingFunction::Warning + | crate::stdlib::logging::LoggingFunction::Error + | crate::stdlib::logging::LoggingFunction::Critical + | crate::stdlib::logging::LoggingFunction::Exception + | crate::stdlib::logging::LoggingFunction::Log + | crate::stdlib::logging::LoggingFunction::BasicConfig + | crate::stdlib::logging::LoggingFunction::SetLevel + | crate::stdlib::logging::LoggingFunction::Disable + | crate::stdlib::logging::LoggingFunction::AddHandler + | crate::stdlib::logging::LoggingFunction::RemoveHandler => { + func.instruction(&Instruction::I32Const(0)); + IRType::None + } + crate::stdlib::logging::LoggingFunction::GetLogger + | crate::stdlib::logging::LoggingFunction::Logger + | crate::stdlib::logging::LoggingFunction::Handler + | crate::stdlib::logging::LoggingFunction::StreamHandler + | crate::stdlib::logging::LoggingFunction::FileHandler + | crate::stdlib::logging::LoggingFunction::Formatter + | crate::stdlib::logging::LoggingFunction::Filter + | crate::stdlib::logging::LoggingFunction::LogRecord => { + func.instruction(&Instruction::I32Const(0)); + IRType::Unknown + } + }; + } + } } } diff --git a/src/stdlib/logging.rs b/src/stdlib/logging.rs new file mode 100644 index 0000000..568a4dd --- /dev/null +++ b/src/stdlib/logging.rs @@ -0,0 +1,139 @@ +use crate::stdlib::StdlibValue; + +/// Log levels as defined in Python's logging module +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(i32)] +pub enum LogLevel { + NotSet = 0, + Debug = 10, + Info = 20, + Warning = 30, + Error = 40, + Critical = 50, +} + +impl LogLevel { + pub fn from_i32(value: i32) -> Self { + match value { + 0 => LogLevel::NotSet, + 10 => LogLevel::Debug, + 20 => LogLevel::Info, + 30 => LogLevel::Warning, + 40 => LogLevel::Error, + 50 => LogLevel::Critical, + _ if value < 10 => LogLevel::NotSet, + _ if value < 20 => LogLevel::Debug, + _ if value < 30 => LogLevel::Info, + _ if value < 40 => LogLevel::Warning, + _ if value < 50 => LogLevel::Error, + _ => LogLevel::Critical, + } + } + + pub fn name(&self) -> &'static str { + match self { + LogLevel::NotSet => "NOTSET", + LogLevel::Debug => "DEBUG", + LogLevel::Info => "INFO", + LogLevel::Warning => "WARNING", + LogLevel::Error => "ERROR", + LogLevel::Critical => "CRITICAL", + } + } +} + +pub fn get_attribute(attr: &str) -> Option { + match attr { + // Log level constants + "NOTSET" => Some(StdlibValue::Int(LogLevel::NotSet as i32)), + "DEBUG" => Some(StdlibValue::Int(LogLevel::Debug as i32)), + "INFO" => Some(StdlibValue::Int(LogLevel::Info as i32)), + "WARNING" => Some(StdlibValue::Int(LogLevel::Warning as i32)), + "WARN" => Some(StdlibValue::Int(LogLevel::Warning as i32)), // Alias + "ERROR" => Some(StdlibValue::Int(LogLevel::Error as i32)), + "CRITICAL" => Some(StdlibValue::Int(LogLevel::Critical as i32)), + "FATAL" => Some(StdlibValue::Int(LogLevel::Critical as i32)), // Alias + _ => None, + } +} + +pub fn get_function(func: &str) -> Option { + match func { + // Logging functions + "debug" => Some(LoggingFunction::Debug), + "info" => Some(LoggingFunction::Info), + "warning" => Some(LoggingFunction::Warning), + "warn" => Some(LoggingFunction::Warning), // Alias + "error" => Some(LoggingFunction::Error), + "critical" => Some(LoggingFunction::Critical), + "fatal" => Some(LoggingFunction::Critical), // Alias + "log" => Some(LoggingFunction::Log), + "exception" => Some(LoggingFunction::Exception), + + // Configuration functions + "basicConfig" => Some(LoggingFunction::BasicConfig), + "getLogger" => Some(LoggingFunction::GetLogger), + "setLevel" => Some(LoggingFunction::SetLevel), + "disable" => Some(LoggingFunction::Disable), + + // Handler/Formatter functions + "addHandler" => Some(LoggingFunction::AddHandler), + "removeHandler" => Some(LoggingFunction::RemoveHandler), + + // Classes (used as constructors) + "Logger" => Some(LoggingFunction::Logger), + "Handler" => Some(LoggingFunction::Handler), + "StreamHandler" => Some(LoggingFunction::StreamHandler), + "FileHandler" => Some(LoggingFunction::FileHandler), + "Formatter" => Some(LoggingFunction::Formatter), + "Filter" => Some(LoggingFunction::Filter), + "LogRecord" => Some(LoggingFunction::LogRecord), + + _ => None, + } +} + +#[derive(Debug, Clone)] +pub enum LoggingFunction { + // Logging functions + Debug, + Info, + Warning, + Error, + Critical, + Log, + Exception, + + // Configuration + BasicConfig, + GetLogger, + SetLevel, + Disable, + + // Handler management + AddHandler, + RemoveHandler, + + // Classes + Logger, + Handler, + StreamHandler, + FileHandler, + Formatter, + Filter, + LogRecord, +} + +impl LoggingFunction { + pub fn log_level(&self) -> Option { + match self { + LoggingFunction::Debug => Some(LogLevel::Debug), + LoggingFunction::Info => Some(LogLevel::Info), + LoggingFunction::Warning => Some(LogLevel::Warning), + LoggingFunction::Error => Some(LogLevel::Error), + LoggingFunction::Critical => Some(LogLevel::Critical), + LoggingFunction::Exception => Some(LogLevel::Error), + _ => None, + } + } +} diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs index 8c92afb..ecdf6bc 100644 --- a/src/stdlib/mod.rs +++ b/src/stdlib/mod.rs @@ -3,6 +3,7 @@ pub mod datetime; pub mod functools; pub mod itertools; pub mod json; +pub mod logging; pub mod math; pub mod os; pub mod random; @@ -22,6 +23,7 @@ pub fn is_stdlib_module(name: &str) -> bool { | "collections" | "itertools" | "functools" + | "logging" ) } @@ -37,6 +39,7 @@ pub fn get_stdlib_attributes(module: &str, attr: &str) -> Option { "collections" => collections::get_attribute(attr), "itertools" => itertools::get_attribute(attr), "functools" => functools::get_attribute(attr), + "logging" => logging::get_attribute(attr), _ => None, } }