Skip to content

Commit

Permalink
Merge #93: Add witness module
Browse files Browse the repository at this point in the history
e3ed903 test: Parse witness module (Christian Lewe)
9628306 feat: Parse WitnessValues from code (Christian Lewe)

Pull request description:

  Make witness JSON parsing via serde optional. Introduce witness modules as an alternative way to provide witness data. Witness modules are written directly inside the Simfony source code.

ACKs for top commit:
  apoelstra:
    ACK e3ed903 successfully ran local tests

Tree-SHA512: 3fab641207cee5555c350fc48a3d2d4fd18365b5d7e9dfe931e8aed008b6039dfb67365ad958f7717514eeb29239bccfe1ba4f3c9240bf9534d4a286395835d4
  • Loading branch information
uncomputable committed Oct 10, 2024
2 parents dbfad07 + e3ed903 commit f877c7b
Show file tree
Hide file tree
Showing 13 changed files with 391 additions and 15 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ jobs:
- compile_text
- display_parse_tree
- parse_value_rtt
- parse_witness_rtt
- parse_witness_json_rtt
- parse_witness_module_rtt
- reconstruct_value
steps:
- name: Checkout
Expand Down
11 changes: 9 additions & 2 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,15 @@ doc = false
bench = false

[[bin]]
name = "parse_witness_rtt"
path = "fuzz_targets/parse_witness_rtt.rs"
name = "parse_witness_json_rtt"
path = "fuzz_targets/parse_witness_json_rtt.rs"
test = false
doc = false
bench = false

[[bin]]
name = "parse_witness_module_rtt"
path = "fuzz_targets/parse_witness_module_rtt.rs"
test = false
doc = false
bench = false
Expand Down
4 changes: 2 additions & 2 deletions fuzz/fuzz_targets/parse_value_rtt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use simfony::value::Value;

fuzz_target!(|value: Value| {
let value_string = value.to_string();
let parsed_value = Value::parse_from_str(&value_string, value.ty())
.expect("Value string should be parseable");
let parsed_value =
Value::parse_from_str(&value_string, value.ty()).expect("Value string should be parseable");
assert_eq!(
value, parsed_value,
"Value string should parse to original value"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use libfuzzer_sys::fuzz_target;
use simfony::witness::WitnessValues;

fuzz_target!(|witness_values: WitnessValues| {
let witness_text =
serde_json::to_string(&witness_values).expect("Witness map should be convertible into JSON");
let witness_text = serde_json::to_string(&witness_values)
.expect("Witness map should be convertible into JSON");
let parsed_witness_text =
serde_json::from_str(&witness_text).expect("Witness JSON should be parseable");
assert_eq!(
Expand Down
16 changes: 16 additions & 0 deletions fuzz/fuzz_targets/parse_witness_module_rtt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![no_main]

use libfuzzer_sys::fuzz_target;

use simfony::parse::ParseFromStr;
use simfony::witness::WitnessValues;

fuzz_target!(|witness_values: WitnessValues| {
let witness_text = witness_values.to_string();
let parsed_witness_text =
WitnessValues::parse_from_str(&witness_text).expect("Witness module should be parseable");
assert_eq!(
witness_values, parsed_witness_text,
"Witness module should parse to original witness values"
);
});
2 changes: 1 addition & 1 deletion fuzz/fuzz_targets/reconstruct_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use libfuzzer_sys::fuzz_target;

use simfony::value::{Value, StructuralValue};
use simfony::value::{StructuralValue, Value};

fuzz_target!(|value: Value| {
let structural_value = StructuralValue::from(&value);
Expand Down
3 changes: 2 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ check_fuzz:
just fuzz compile_text
just fuzz display_parse_tree
just fuzz parse_value_rtt
just fuzz parse_witness_rtt
just fuzz parse_witness_json_rtt
just fuzz parse_witness_module_rtt
just fuzz reconstruct_value

# Build fuzz tests
Expand Down
141 changes: 140 additions & 1 deletion src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ use crate::error::{Error, RichError, Span, WithSpan};
use crate::num::{NonZeroPow2Usize, Pow2Usize};
use crate::parse::MatchPattern;
use crate::pattern::Pattern;
use crate::str::{FunctionName, Identifier, WitnessName};
use crate::str::{FunctionName, Identifier, ModuleName, WitnessName};
use crate::types::{
AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, UIntType,
};
use crate::value::{UIntValue, Value};
use crate::witness::WitnessValues;
use crate::{impl_eq_hash, parse};

/// Map of witness names to their expected type, as declared in the program.
Expand Down Expand Up @@ -75,6 +76,8 @@ pub enum Item {
TypeAlias,
/// A function.
Function(Function),
/// A module, which is ignored.
Module,
}

/// Definition of a function.
Expand Down Expand Up @@ -374,6 +377,44 @@ impl MatchArm {
}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum WitnessItem {
Ignored,
WitnessModule(WitnessModule),
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct WitnessModule {
assignments: Arc<[WitnessAssignment]>,
span: Span,
}

impl WitnessModule {
/// Access the assignments of the module.
pub fn assignments(&self) -> &[WitnessAssignment] {
&self.assignments
}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct WitnessAssignment {
name: WitnessName,
value: Value,
span: Span,
}

impl WitnessAssignment {
/// Access the assigned witness name.
pub fn name(&self) -> &WitnessName {
&self.name
}

/// Access the assigned witness value.
pub fn value(&self) -> &Value {
&self.value
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum ExprTree<'a> {
Expression(&'a Expression),
Expand Down Expand Up @@ -653,6 +694,7 @@ impl AbstractSyntaxTree for Item {
parse::Item::Function(function) => {
Function::analyze(function, ty, scope).map(Self::Function)
}
parse::Item::Module => Ok(Self::Module),
}
}
}
Expand Down Expand Up @@ -1278,6 +1320,91 @@ impl AbstractSyntaxTree for Match {
}
}

impl WitnessValues {
pub fn analyze(from: &parse::WitnessProgram) -> Result<Self, RichError> {
let unit = ResolvedType::unit();
let mut scope = Scope::default();
let items = from
.items()
.iter()
.map(|s| WitnessItem::analyze(s, &unit, &mut scope))
.collect::<Result<Vec<WitnessItem>, RichError>>()?;
debug_assert!(scope.is_topmost());
let mut iter = items.into_iter().filter_map(|item| match item {
WitnessItem::WitnessModule(witness_module) => Some(witness_module),
_ => None,
});
let witness_module = iter
.next()
.ok_or(Error::ModuleRequired(ModuleName::witness()))
.with_span(from)?;
if iter.next().is_some() {
return Err(Error::ModuleRedefined(ModuleName::witness())).with_span(from);
}
let mut witness_values = Self::empty();
for assignment in witness_module.assignments() {
witness_values
.insert(assignment.name().clone(), assignment.value().clone())
.with_span(assignment)?;
}
Ok(witness_values)
}
}

impl AbstractSyntaxTree for WitnessItem {
type From = parse::WitnessItem;

fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
assert!(ty.is_unit(), "Items cannot return anything");
assert!(scope.is_topmost(), "Items live in the topmost scope only");
match from {
parse::WitnessItem::Ignored => Ok(Self::Ignored),
parse::WitnessItem::WitnessModule(witness_module) => {
WitnessModule::analyze(witness_module, ty, scope).map(Self::WitnessModule)
}
}
}
}

impl AbstractSyntaxTree for WitnessModule {
type From = parse::WitnessModule;

fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
assert!(ty.is_unit(), "Modules cannot return anything");
assert!(scope.is_topmost(), "Modules live in the topmost scope only");
let assignments = from
.assignments()
.iter()
.map(|s| WitnessAssignment::analyze(s, ty, scope))
.collect::<Result<Arc<[WitnessAssignment]>, RichError>>()?;
debug_assert!(scope.is_topmost());

Ok(Self {
span: *from.as_ref(),
assignments,
})
}
}

impl AbstractSyntaxTree for WitnessAssignment {
type From = parse::WitnessAssignment;

fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
assert!(ty.is_unit(), "Assignments cannot return anything");
let ty_expr = scope.resolve(from.ty()).with_span(from)?;
let expression = Expression::analyze(from.expression(), &ty_expr, scope)?;
let value = Value::from_const_expr(&expression)
.ok_or(Error::ExpressionUnexpectedType(ty_expr.clone()))
.with_span(from.expression())?;

Ok(Self {
name: from.name().clone(),
value,
span: *from.as_ref(),
})
}
}

impl AsRef<Span> for Assignment {
fn as_ref(&self) -> &Span {
&self.span
Expand Down Expand Up @@ -1307,3 +1434,15 @@ impl AsRef<Span> for Match {
&self.span
}
}

impl AsRef<Span> for WitnessModule {
fn as_ref(&self) -> &Span {
&self.span
}
}

impl AsRef<Span> for WitnessAssignment {
fn as_ref(&self) -> &Span {
&self.span
}
}
12 changes: 11 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use simplicity::hashes::{sha256, Hash, HashEngine};
use simplicity::{elements, Cmr};

use crate::parse::{MatchPattern, Rule};
use crate::str::{FunctionName, Identifier, JetName, WitnessName};
use crate::str::{FunctionName, Identifier, JetName, ModuleName, WitnessName};
use crate::types::{ResolvedType, UIntType};

/// Position of an object inside a file.
Expand Down Expand Up @@ -329,6 +329,8 @@ pub enum Error {
WitnessTypeMismatch(WitnessName, ResolvedType, ResolvedType),
WitnessReassigned(WitnessName),
WitnessOutsideMain,
ModuleRequired(ModuleName),
ModuleRedefined(ModuleName),
}

#[rustfmt::skip]
Expand Down Expand Up @@ -451,6 +453,14 @@ impl fmt::Display for Error {
f,
"Witness expressions are not allowed outside the `main` function"
),
Error::ModuleRequired(name) => write!(
f,
"Required module `{name}` is missing"
),
Error::ModuleRedefined(name) => write!(
f,
"Module `{name}` is defined twice"
),
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/minimal.pest
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | ("//" ~ (!"\n" ~ ANY)*) }

program = { SOI ~ item* ~ EOI }
item = { type_alias | function }
item = { type_alias | function | witness_module }
statement = { assignment | expression }
expression = { block_expression | single_expression }
block_expression = { "{" ~ (statement ~ ";")* ~ expression? ~ "}" }
Expand Down Expand Up @@ -82,3 +82,8 @@ tuple_expr = { "(" ~ ((expression ~ ",")+ ~ expression?)? ~ ")" }
array_expr = { "[" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ "]" }
list_expr = { "list![" ~ (expression ~ ("," ~ expression)* ~ ","?)? ~ "]" }
single_expression = { left_expr | right_expr | none_expr | some_expr | false_expr | true_expr | call_expr | match_expr | tuple_expr | array_expr | list_expr | bin_literal | hex_literal | dec_literal | witness_expr | variable_expr | "(" ~ expression ~ ")" }

mod_keyword = @{ "mod" ~ !ASCII_ALPHANUMERIC }
witness_module = { mod_keyword ~ "witness" ~ "{" ~ (witness_assign ~ ";")* ~ "}" }
const_keyword = @{ "const" ~ !ASCII_ALPHANUMERIC }
witness_assign = { const_keyword ~ witness_name ~ ":" ~ ty ~ "=" ~ expression }
Loading

0 comments on commit f877c7b

Please sign in to comment.