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,
}
}