Podlog language v1 (#225)

* Initial commit for Podlog language

* Spell-checker thinks that 'lits' is a bad abbreviation for 'literals'

* Enable SetContains/SetNotContains

* Update language based on review feedback

* Typo/comment fix

* Make native predicates case-sensitive

* Enforce max batch size in CustomPredicateBatchBuilder

* Remove some unnecessary checks for things handled by the grammar

* Clean up more unnecessary error-checking

* Typo

* Simplify hex processing

* Replace various errors with unreachable!()

* Translate from big-endian hex string to little-endian RawValue

* Update hex en/decoding functions
This commit is contained in:
Rob Knight 2025-06-07 07:17:23 +02:00 committed by GitHub
parent e8edbbc1c5
commit 541c264586
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 2259 additions and 29 deletions

View file

@ -190,15 +190,22 @@ impl fmt::Display for Hash {
impl FromHex for Hash {
type Error = FromHexError;
// TODO make it dependant on backend::Value len
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
// In little endian
// The input `hex` is a big-endian hex string.
let bytes = <[u8; 32]>::from_hex(hex)?;
let mut buf: [u8; 8] = [0; 8];
let mut inner = [F::ZERO; HASH_SIZE];
for i in 0..HASH_SIZE {
buf.copy_from_slice(&bytes[8 * i..8 * (i + 1)]);
inner[i] = F::from_canonical_u64(u64::from_le_bytes(buf));
let start = i * 8;
let end = start + 8;
let chunk: [u8; 8] = bytes[start..end]
.try_into()
.expect("slice with incorrect length");
// We read big-endian chunks from a big-endian string,
// and place them into a little-endian limb array.
let u64_val = u64::from_be_bytes(chunk);
inner[HASH_SIZE - 1 - i] = F::from_canonical_u64(u64_val);
}
Ok(Self(inner))
}

View file

@ -373,20 +373,12 @@ impl fmt::Display for CustomPredicate {
pub struct CustomPredicateBatch {
id: Hash,
pub name: String,
predicates: Vec<CustomPredicate>,
pub(crate) predicates: Vec<CustomPredicate>,
}
impl ToFields for CustomPredicateBatch {
fn to_fields(&self, params: &Params) -> Vec<F> {
// all the custom predicates in order
// TODO think if this check should go into the StatementTmpl creation,
// instead of at the `to_fields` method, where we should assume that the
// values are already valid
if self.predicates.len() > params.max_custom_batch_size {
panic!("Predicate batch exceeds maximum size");
}
let pad_pred = CustomPredicate::empty();
let fields: Vec<F> = self
.predicates

View file

@ -1,4 +1,7 @@
use std::collections::{HashMap, HashSet};
use std::{
collections::{HashMap, HashSet},
fmt::Write,
};
use plonky2::field::types::Field;
use serde::{ser::SerializeSeq, Deserialize, Serialize, Serializer};
@ -13,10 +16,16 @@ fn serialize_field_tuple<S, const N: usize>(
where
S: serde::Serializer,
{
serializer.serialize_str(&format!(
"{:016x}{:016x}{:016x}{:016x}",
value[0].0, value[1].0, value[2].0, value[3].0
))
// `value` is little-endian in memory. We serialize it as a big-endian hex string
// for human readability.
let s = value
.iter()
.rev()
.fold(String::with_capacity(N * 16), |mut s, limb| {
write!(s, "{:016x}", limb.0).unwrap();
s
});
serializer.serialize_str(&s)
}
fn deserialize_field_tuple<'de, D, const N: usize>(deserializer: D) -> Result<[F; N], D::Error>
@ -25,20 +34,29 @@ where
{
let hex_str = String::deserialize(deserializer)?;
if !hex_str.chars().count() == 64 || !hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
let expected_len = N * 16;
if hex_str.len() != expected_len {
return Err(serde::de::Error::custom(format!(
"Invalid hex string length: expected {} characters, found {}",
expected_len,
hex_str.len()
)));
}
if !hex_str.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(serde::de::Error::custom(
"Invalid hex string format - expected 64 hexadecimal characters",
"Invalid hex string format: contains non-hexadecimal characters",
));
}
let mut v = [F::ZERO; N];
for (i, v_i) in v.iter_mut().enumerate() {
for i in 0..N {
let start = i * 16;
let end = start + 16;
let hex_part = &hex_str[start..end];
*v_i = F::from_canonical_u64(
u64::from_str_radix(hex_part, 16).map_err(serde::de::Error::custom)?,
);
let u64_val = u64::from_str_radix(hex_part, 16).map_err(serde::de::Error::custom)?;
// The hex string is big-endian, so the first chunk (i=0) is the most significant.
// We store it in the last position of our little-endian array `v`.
v[N - 1 - i] = F::from_canonical_u64(u64_val);
}
Ok(v)
}