Skip to content
Closed
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
180 changes: 180 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "foamlib"
version = "1.5.4"
edition = "2021"

[lib]
name = "foamlib_rust"
crate-type = ["cdylib"]
path = "rust_src/lib.rs"

[dependencies]
pyo3 = { version = "0.23", features = ["extension-module"] }
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
requires = ["uv_build>=0.9,<0.10"]
build-backend = "uv_build"
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"

[project]
name = "foamlib"
Expand Down Expand Up @@ -119,3 +119,7 @@ extend-ignore = [

[tool.ruff.lint.pydocstyle]
convention = "pep257"

[tool.maturin]
python-source = "src"
module-name = "foamlib.foamlib_rust"
140 changes: 140 additions & 0 deletions rust_src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use pyo3::prelude::*;

// Whitespace lookup table
static IS_WHITESPACE: [bool; 256] = {
let mut table = [false; 256];
table[b' ' as usize] = true;
table[b'\n' as usize] = true;
table[b'\t' as usize] = true;
table[b'\r' as usize] = true;
table[b'\x0C' as usize] = true; // \f
table[b'\x0B' as usize] = true; // \v
table
};

// Whitespace without newline lookup table
static IS_WHITESPACE_NO_NEWLINE: [bool; 256] = {
let mut table = [false; 256];
table[b' ' as usize] = true;
table[b'\t' as usize] = true;
table[b'\r' as usize] = true;
table[b'\x0C' as usize] = true; // \f
table[b'\x0B' as usize] = true; // \v
table
};

/// Skip whitespace and comments in OpenFOAM files
///
/// This function skips over whitespace and comments (both // and /* */ style).
/// It handles line continuations with backslash in line comments.
///
/// # Arguments
/// * `contents` - The file contents as bytes or bytearray
/// * `pos` - Current position in the file
/// * `newline_ok` - Whether newlines should be skipped (default: True)
///
/// # Returns
/// The new position after skipping whitespace and comments
#[pyfunction]
#[pyo3(signature = (contents, pos, *, newline_ok=true))]
fn skip(contents: &Bound<'_, pyo3::types::PyAny>, mut pos: usize, newline_ok: bool) -> PyResult<usize> {
// Extract bytes from either bytes or bytearray
let contents = if let Ok(bytes) = contents.downcast::<pyo3::types::PyBytes>() {
bytes.as_bytes()
} else if let Ok(bytearray) = contents.downcast::<pyo3::types::PyByteArray>() {
// SAFETY: This is safe because:
// 1. We only read from the bytearray (immutable access)
// 2. The reference doesn't escape this function
// 3. The GIL prevents concurrent modification by Python code
unsafe { bytearray.as_bytes() }
} else {
return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
"contents must be bytes or bytearray"
));
};
let is_whitespace = if newline_ok {
&IS_WHITESPACE
} else {
&IS_WHITESPACE_NO_NEWLINE
};

loop {
// Skip whitespace
while pos < contents.len() && is_whitespace[contents[pos] as usize] {
pos += 1;
}

// Check if we're at the end of content
if pos >= contents.len() {
break;
}

// Check for comments
if pos + 1 < contents.len() {
let next1 = contents[pos];
let next2 = contents[pos + 1];

// Single-line comment //
if next1 == b'/' && next2 == b'/' {
pos += 2;
loop {
if pos >= contents.len() {
break;
}

if contents[pos] == b'\n' {
if newline_ok {
pos += 1;
}
break;
}

// Handle line continuation
if contents[pos] == b'\\' && pos + 1 < contents.len() && contents[pos + 1] == b'\n' {
pos += 2;
continue;
}

pos += 1;
}
continue;
}

// Multi-line comment /* */
if next1 == b'/' && next2 == b'*' {
pos += 2;

// Find the closing */
let mut found = false;
while pos + 1 < contents.len() {
if contents[pos] == b'*' && contents[pos + 1] == b'/' {
pos += 2;
found = true;
break;
}
pos += 1;
}

if !found {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
format!("Unterminated comment at position {}", pos)
));
}

continue;
}
}

// No more whitespace or comments
break;
}

Ok(pos)
}

/// A Python module implemented in Rust for performance-critical parsing operations.
#[pymodule]
fn foamlib_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(skip, m)?)?;
Ok(())
}
Loading
Loading