Skip to content
Merged
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ serde_json = "1.0.140"
base64 = "0.22.1"
schemars = "0.8.22"
hashbrown = { version = "0.14.3", default-features = false, features = ["serde"] }
pest = "2.8.0"
pest_derive = "2.8.0"

# Uncomment for debugging with https://github.com/ed255/plonky2/ at branch `feat/debug`. The repo directory needs to be checked out next to the pod2 repo directory.
# [patch."https://github.com/0xPolygonZero/plonky2"]
Expand Down
16 changes: 12 additions & 4 deletions src/frontend/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn key(s: &str) -> KeyOrWildcardStr {
}

/// Builder Argument for the StatementTmplBuilder
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum BuilderArg {
Literal(Value),
/// Key: (origin, key), where origin is SELF or Wildcard and key is Key or Wildcard
Expand Down Expand Up @@ -79,8 +79,8 @@ pub fn literal(v: impl Into<Value>) -> BuilderArg {

#[derive(Clone)]
pub struct StatementTmplBuilder {
predicate: Predicate,
args: Vec<BuilderArg>,
pub(crate) predicate: Predicate,
pub(crate) args: Vec<BuilderArg>,
}

impl StatementTmplBuilder {
Expand All @@ -98,7 +98,7 @@ impl StatementTmplBuilder {

/// Desugar the predicate to a simpler form
/// Should mirror the logic in `MainPodBuilder::lower_op`
fn desugar(self) -> StatementTmplBuilder {
pub(crate) fn desugar(self) -> StatementTmplBuilder {
match self.predicate {
Predicate::Native(NativePredicate::Gt) => {
let mut stb = StatementTmplBuilder {
Expand Down Expand Up @@ -184,6 +184,14 @@ impl CustomPredicateBatchBuilder {
priv_args: &[&str],
sts: &[StatementTmplBuilder],
) -> Result<Predicate> {
if self.predicates.len() >= self.params.max_custom_batch_size {
return Err(Error::max_length(
"self.predicates.len".to_string(),
self.predicates.len(),
self.params.max_custom_batch_size,
));
}

if args.len() > self.params.max_statement_args {
return Err(Error::max_length(
"args.len".to_string(),
Expand Down
94 changes: 94 additions & 0 deletions src/lang/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use thiserror::Error;

use crate::{frontend, lang::parser::ParseError, middleware};

#[derive(Error, Debug)]
pub enum LangError {
#[error("Parsing failed: {0}")]
Parse(Box<ParseError>),

#[error("AST processing error: {0}")]
Processor(Box<ProcessorError>),

#[error("Middleware error during processing: {0}")]
Middleware(Box<middleware::Error>),

#[error("Frontend error: {0}")]
Frontend(Box<frontend::Error>),
}

/// Errors that can occur during the processing of Podlog Pest tree into middleware structures.
#[derive(thiserror::Error, Debug)]
pub enum ProcessorError {
#[error("Undefined identifier: '{name}' at {span:?}")]
UndefinedIdentifier {
name: String,
span: Option<(usize, usize)>,
},
#[error("Duplicate definition: '{name}' at {span:?}")]
DuplicateDefinition {
name: String,
span: Option<(usize, usize)>,
},
#[error("Duplicate wildcard: ?{name} in scope at {span:?}")]
DuplicateWildcard {
name: String,
span: Option<(usize, usize)>,
},
#[error("Type error: expected {expected}, found {found} for '{item}' at {span:?}")]
TypeError {
expected: String,
found: String,
item: String,
span: Option<(usize, usize)>,
},
#[error(
"Invalid argument count for '{predicate}': expected {expected}, found {found} at {span:?}"
)]
ArgumentCountMismatch {
predicate: String,
expected: usize,
found: usize,
span: Option<(usize, usize)>,
},
#[error("Multiple REQUEST definitions found. Only one is allowed. First at {first_span:?}, second at {second_span:?}")]
MultipleRequestDefinitions {
first_span: Option<(usize, usize)>,
second_span: Option<(usize, usize)>,
},
#[error("Internal processing error: {0}")]
Internal(String),
#[error("Middleware error: {0}")]
Middleware(middleware::Error),
#[error("Undefined wildcard: '?{name}' at {span:?}")]
UndefinedWildcard {
name: String,
span: Option<(usize, usize)>,
},
#[error("Invalid literal format for {kind}: '{value}' at {span:?}")]
InvalidLiteralFormat {
kind: String,
value: String,
span: Option<(usize, usize)>,
},
#[error("Frontend error: {0}")]
Frontend(#[from] frontend::Error),
}

impl From<ParseError> for LangError {
fn from(err: ParseError) -> Self {
LangError::Parse(Box::new(err))
}
}

impl From<ProcessorError> for LangError {
fn from(err: ProcessorError) -> Self {
LangError::Processor(Box::new(err))
}
}

impl From<middleware::Error> for LangError {
fn from(err: middleware::Error) -> Self {
LangError::Middleware(Box::new(err))
}
}
96 changes: 96 additions & 0 deletions src/lang/grammar.pest
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Grammar for the "Podlog" language. Used for describing POD2 Custom
// Predicates and Proof Requests.

// Silent rules (`_`) are automatically handled by Pest between other rules.
// WHITESPACE matches one or more spaces, tabs, or newlines.
WHITESPACE = _{ (" " | "\t" | NEWLINE)+ }

// COMMENT matches '//' followed by any characters until the end of the line.
// Also silent.
COMMENT = _{ "//" ~ (!NEWLINE ~ ANY)* }

// Define rules for identifiers (predicate names, variable names without '?')
// Must start with alpha or _, followed by alpha, numeric, or _
identifier = @{ !("private") ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

private_kw = { "private:" }

self_keyword = @{ "SELF" }

// Define wildcard names (start with '?')
wildcard = @{ "?" ~ identifier }

arg_section = {
public_arg_list ~ ("," ~ private_kw ~ private_arg_list)?
}

public_arg_list = { identifier ~ ("," ~ identifier)* }
private_arg_list = { identifier ~ ("," ~ identifier)* }

document = { SOI ~ (custom_predicate_def | request_def)* ~ EOI }

request_def = { "REQUEST" ~ "(" ~ statement_list? ~ ")" }

// Define conjunction type explicitly
conjunction_type = { "AND" | "OR" }

custom_predicate_def = {
identifier
~ "(" ~ arg_section ~ ")"
~ "="
~ conjunction_type
~ "(" ~ statement_list ~ ")"
}

statement_list = { statement+ }

statement_arg = { anchored_key | wildcard | literal_value }
statement_arg_list = { statement_arg ~ ("," ~ statement_arg)* }

statement = { identifier ~ "(" ~ statement_arg_list? ~ ")" }

// Anchored Key: (SELF | ?Var)["key_literal" | ?KeyVar]
anchored_key = { ( self_keyword | wildcard ) ~ "[" ~ (wildcard | literal_string) ~ "]" }

// Literal Values (ordered to avoid ambiguity, e.g., string before int)
literal_value = {
literal_dict |
literal_set |
literal_array |
literal_bool |
literal_raw |
literal_string |
literal_int
}

// Primitive literal types
literal_int = @{ "-"? ~ ASCII_DIGIT+ }
literal_bool = @{ "true" | "false" }

// literal_raw: 0x followed by exactly 32 PAIRS of hex digits (64 hex characters)
// representing a 32-byte value in big-endian order
literal_raw = @{ "0x" ~ (ASCII_HEX_DIGIT ~ ASCII_HEX_DIGIT){32} }

// String literal parsing based on https://pest.rs/book/examples/json.html
literal_string = ${ "\"" ~ inner ~ "\"" } // Compound atomic string rule
inner = @{ char* } // Atomic rule for the raw inner content
char = { // Rule for a single logical character (unescaped or escaped)
!("\"" | "\\") ~ ANY // Any char except quote or backslash
| "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") // Simple escape sequences
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) // Unicode escape sequence
}

// Container Literals (recursive definition using literal_value)
literal_array = { "[" ~ (literal_value ~ ("," ~ literal_value)*)? ~ "]" }
literal_set = { "#[" ~ (literal_value ~ ("," ~ literal_value)*)? ~ "]" }
literal_dict = { "{" ~ (dict_pair ~ ("," ~ dict_pair)*)? ~ "}" }
dict_pair = { literal_string ~ ":" ~ literal_value }

// --- Rules for testing full input matching ---
test_identifier = { SOI ~ identifier ~ EOI }
test_wildcard = { SOI ~ wildcard ~ EOI }
test_literal_int = { SOI ~ literal_int ~ EOI }
test_literal_raw = { SOI ~ literal_raw ~ EOI }
test_literal_value = { SOI ~ literal_value ~ EOI }
test_statement = { SOI ~ statement ~ EOI }
test_custom_predicate_def = { SOI ~ custom_predicate_def ~ EOI }
Loading
Loading