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
186 changes: 114 additions & 72 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use simplicity::{jet::Elements, Cmr, FailEntropy};

use crate::array::{BTreeSlice, Partition};
use crate::num::NonZeroPow2Usize;
use crate::parse::{Pattern, SingleExpressionInner, UIntType};
use crate::parse::{Pattern, SingleExpressionInner, Span, UIntType};
use crate::{
error::{Error, RichError, WithSpan},
named::{ConstructExt, ProgExt},
parse::{Expression, ExpressionInner, FuncCall, FuncType, Program, Statement, Type},
scope::GlobalScope,
Expand All @@ -20,25 +21,25 @@ fn eval_blk(
scope: &mut GlobalScope,
index: usize,
last_expr: Option<&Expression>,
) -> ProgNode {
) -> Result<ProgNode, RichError> {
if index >= stmts.len() {
return match last_expr {
Some(expr) => expr.eval(scope, None),
None => ProgNode::unit(),
None => Ok(ProgNode::unit()),
};
}
match &stmts[index] {
Statement::Assignment(assignment) => {
let expr = assignment.expression.eval(scope, assignment.ty.as_ref());
let expr = assignment.expression.eval(scope, assignment.ty.as_ref())?;
scope.insert(assignment.pattern.clone());
let left = ProgNode::pair_iden(&expr);
let right = eval_blk(stmts, scope, index + 1, last_expr);
ProgNode::comp(&left, &right).unwrap()
let right = eval_blk(stmts, scope, index + 1, last_expr)?;
ProgNode::comp(&left, &right).with_span(assignment.span)
}
Statement::FuncCall(func_call) => {
let left = func_call.eval(scope, None);
let right = eval_blk(stmts, scope, index + 1, last_expr);
combine_seq(&left, &right).unwrap()
let left = func_call.eval(scope, None)?;
let right = eval_blk(stmts, scope, index + 1, last_expr)?;
combine_seq(&left, &right).with_span(func_call.span)
}
}
}
Expand All @@ -50,94 +51,114 @@ fn combine_seq(a: &ProgNode, b: &ProgNode) -> Result<ProgNode, simplicity::types
}

impl Program {
pub fn eval(&self, scope: &mut GlobalScope) -> ProgNode {
pub fn eval(&self, scope: &mut GlobalScope) -> Result<ProgNode, RichError> {
eval_blk(&self.statements, scope, 0, None)
}
}

impl FuncCall {
pub fn eval(&self, scope: &mut GlobalScope, _reqd_ty: Option<&Type>) -> ProgNode {
pub fn eval(
&self,
scope: &mut GlobalScope,
_reqd_ty: Option<&Type>,
) -> Result<ProgNode, RichError> {
match &self.func_type {
FuncType::Jet(jet_name) => {
let args = self
.args
.iter()
.map(|e| e.eval(scope, None)) // TODO: Pass the jet source type here.
.reduce(|a, b| ProgNode::pair(&a, &b).unwrap());
let jet = Elements::from_str(jet_name).expect("Invalid jet name");
let jet = ProgNode::jet(jet);
match args {
Some(param) => {
// println!("param: {}", param.arrow());
// println!("jet: {}", jet.arrow());
ProgNode::comp(&param, &jet).unwrap()
}
None => ProgNode::unit_comp(&jet),
}
FuncType::Jet(name) => {
let args = match self.args.is_empty() {
true => SingleExpressionInner::Unit,
false => SingleExpressionInner::Array(self.args.clone()),
};
// TODO: Pass the jet source type here.
// FIXME: Constructing pairs should never fail because when Simfony is translated to
// Simplicity the input type is variable. However, the fact that pairs always unify
// is hard to prove at the moment, while Simfony lacks a type system.
let args_expr = args.eval(scope, None, self.span)?;
let jet = Elements::from_str(name.as_inner())
.map_err(|_| Error::JetDoesNotExist(name.as_inner().clone()))
.with_span(self.span)?;
let jet_expr = ProgNode::jet(jet);
ProgNode::comp(&args_expr, &jet_expr).with_span(self.span)
}
FuncType::BuiltIn(..) => unimplemented!("Builtins are not supported yet"),
FuncType::UnwrapLeft => {
debug_assert!(self.args.len() == 1);
let b = self.args[0].eval(scope, None);
let b = self.args[0].eval(scope, None)?;
let left_and_unit = ProgNode::pair_unit(&b);
let fail_cmr = Cmr::fail(FailEntropy::ZERO);
let take_iden = ProgNode::take(&ProgNode::iden());
// FIXME: Assertions never fail to unify
// Fix upstream
let get_inner = ProgNode::assertl(&take_iden, fail_cmr).unwrap();
ProgNode::comp(&left_and_unit, &get_inner).unwrap()
ProgNode::comp(&left_and_unit, &get_inner).with_span(self.span)
}
FuncType::UnwrapRight | FuncType::Unwrap => {
debug_assert!(self.args.len() == 1);
let c = self.args[0].eval(scope, None);
let c = self.args[0].eval(scope, None)?;
let right_and_unit = ProgNode::pair_unit(&c);
let fail_cmr = Cmr::fail(FailEntropy::ZERO);
let take_iden = ProgNode::take(&ProgNode::iden());
// FIXME: Assertions never fail to unify
// Fix upstream
let get_inner = ProgNode::assertr(fail_cmr, &take_iden).unwrap();
ProgNode::comp(&right_and_unit, &get_inner).unwrap()
ProgNode::comp(&right_and_unit, &get_inner).with_span(self.span)
}
}
}
}

impl Expression {
pub fn eval(&self, scope: &mut GlobalScope, reqd_ty: Option<&Type>) -> ProgNode {
pub fn eval(
&self,
scope: &mut GlobalScope,
reqd_ty: Option<&Type>,
) -> Result<ProgNode, RichError> {
match &self.inner {
ExpressionInner::BlockExpression(stmts, expr) => {
scope.push_scope();
let res = eval_blk(stmts, scope, 0, Some(expr.as_ref()));
scope.pop_scope();
res
}
ExpressionInner::SingleExpression(e) => e.inner.eval(scope, reqd_ty),
ExpressionInner::SingleExpression(e) => e.inner.eval(scope, reqd_ty, self.span),
}
}
}

impl SingleExpressionInner {
pub fn eval(&self, scope: &mut GlobalScope, reqd_ty: Option<&Type>) -> ProgNode {
let res = match self {
pub fn eval(
&self,
scope: &mut GlobalScope,
reqd_ty: Option<&Type>,
span: Span,
) -> Result<ProgNode, RichError> {
let expr = match self {
SingleExpressionInner::Unit => ProgNode::unit(),
SingleExpressionInner::Left(l) => {
let l = l.eval(scope, None);
let l = l.eval(scope, None)?;
ProgNode::injl(&l)
}
SingleExpressionInner::None => ProgNode::_false(),
SingleExpressionInner::Right(r) | SingleExpressionInner::Some(r) => {
let r = r.eval(scope, None);
let r = r.eval(scope, None)?;
ProgNode::injr(&r)
}
SingleExpressionInner::False => ProgNode::_false(),
SingleExpressionInner::True => ProgNode::_true(),
SingleExpressionInner::Product(l, r) => {
let l = l.eval(scope, None);
let r = r.eval(scope, None);
ProgNode::pair(&l, &r).unwrap()
let l = l.eval(scope, None)?;
let r = r.eval(scope, None)?;
// FIXME: Constructing pairs should never fail because when Simfony is translated to
// Simplicity the input type is variable. However, the fact that pairs always unify
// is hard to prove at the moment, while Simfony lacks a type system.
ProgNode::pair(&l, &r).with_span(span)?
}
SingleExpressionInner::UnsignedInteger(decimal) => {
let reqd_ty = reqd_ty.cloned().unwrap_or(Type::UInt(UIntType::U32));
let ty = reqd_ty
.unwrap_or(&Type::UInt(UIntType::U32))
.to_uint()
.expect("Not an integer type");
let value = ty.parse_decimal(decimal);
.ok_or(Error::TypeValueMismatch(reqd_ty))
.with_span(span)?;
let value = ty.parse_decimal(decimal).with_span(span)?;
ProgNode::unit_comp(&ProgNode::const_word(value))
}
SingleExpressionInner::BitString(bits) => {
Expand All @@ -152,9 +173,12 @@ impl SingleExpressionInner {
scope.insert_witness(name.clone());
ProgNode::witness(name.as_inner().clone())
}
SingleExpressionInner::Variable(identifier) => scope.get(identifier),
SingleExpressionInner::FuncCall(call) => call.eval(scope, reqd_ty),
SingleExpressionInner::Expression(expression) => expression.eval(scope, reqd_ty),
SingleExpressionInner::Variable(identifier) => scope
.get(identifier)
.ok_or(Error::UndefinedVariable(identifier.clone()))
.with_span(span)?,
SingleExpressionInner::FuncCall(call) => call.eval(scope, reqd_ty)?,
SingleExpressionInner::Expression(expression) => expression.eval(scope, reqd_ty)?,
SingleExpressionInner::Match {
scrutinee,
left,
Expand All @@ -168,7 +192,7 @@ impl SingleExpressionInner {
.map(Pattern::Identifier)
.unwrap_or(Pattern::Ignore),
);
let l_compiled = left.expression.eval(&mut l_scope, reqd_ty);
let l_compiled = left.expression.eval(&mut l_scope, reqd_ty)?;

let mut r_scope = scope.clone();
r_scope.insert(
Expand All @@ -179,60 +203,78 @@ impl SingleExpressionInner {
.map(Pattern::Identifier)
.unwrap_or(Pattern::Ignore),
);
let r_compiled = right.expression.eval(&mut r_scope, reqd_ty);
let r_compiled = right.expression.eval(&mut r_scope, reqd_ty)?;

// TODO: Enforce target type A + B for m_expr
let scrutinized_input = scrutinee.eval(scope, None);
let scrutinized_input = scrutinee.eval(scope, None)?;
let input = ProgNode::pair_iden(&scrutinized_input);
let output = ProgNode::case(&l_compiled, &r_compiled).unwrap();
ProgNode::comp(&input, &output).unwrap()
let output = ProgNode::case(&l_compiled, &r_compiled).with_span(span)?;
ProgNode::comp(&input, &output).with_span(span)?
}
SingleExpressionInner::Array(elements) => {
let el_type = if let Some(Type::Array(ty, _)) = reqd_ty {
Some(ty.as_ref())
} else {
None
};
let nodes: Vec<_> = elements.iter().map(|e| e.eval(scope, el_type)).collect();
// FIXME: Constructing pairs should never fail because when Simfony is translated to
// Simplicity the input type is variable. However, the fact that pairs always unify
// is hard to prove at the moment, while Simfony lacks a type system.
let nodes: Vec<Result<ProgNode, RichError>> =
elements.iter().map(|e| e.eval(scope, el_type)).collect();
let tree = BTreeSlice::from_slice(&nodes);
tree.fold(|a, b| ProgNode::pair(&a, &b).unwrap())
tree.fold(|res_a, res_b| {
res_a.and_then(|a| res_b.and_then(|b| ProgNode::pair(&a, &b).with_span(span)))
})?
}
SingleExpressionInner::List(elements) => {
let el_type = if let Some(Type::List(ty, _)) = reqd_ty {
Some(ty.as_ref())
} else {
None
};
let nodes: Vec<_> = elements.iter().map(|e| e.eval(scope, el_type)).collect();
let bound = if let Some(Type::List(_, bound)) = reqd_ty {
let nodes: Vec<Result<ProgNode, RichError>> =
elements.iter().map(|e| e.eval(scope, el_type)).collect();
let bound = if let Some(list_type @ Type::List(_, bound)) = reqd_ty {
if bound.get() <= nodes.len() {
return Err(Error::TypeValueMismatch(list_type.clone())).with_span(span);
}
*bound
} else {
NonZeroPow2Usize::next(elements.len().saturating_add(1))
};

// FIXME: Constructing pairs should never fail because when Simfony is translated to
// Simplicity the input type is variable. However, the fact that pairs always unify
// is hard to prove at the moment, while Simfony lacks a type system.
let partition = Partition::from_slice(&nodes, bound.get() / 2);
let process = |block: &[ProgNode]| -> ProgNode {
if block.is_empty() {
ProgNode::_false()
} else {
let tree = BTreeSlice::from_slice(block);
let array = tree.fold(|a, b| ProgNode::pair(&a, &b).unwrap());
ProgNode::injr(&array)
}
};
let process =
|block: &[Result<ProgNode, RichError>]| -> Result<ProgNode, RichError> {
if block.is_empty() {
Ok(ProgNode::_false())
} else {
let tree = BTreeSlice::from_slice(block);
let array = tree.fold(|res_a, res_b| {
res_a.and_then(|a| {
res_b.and_then(|b| ProgNode::pair(&a, &b).with_span(span))
})
})?;
Ok(ProgNode::injr(&array))
}
};

partition.fold(process, |a, b| ProgNode::pair(&a, &b).unwrap())
partition.fold(process, |res_a, res_b| {
res_a.and_then(|a| res_b.and_then(|b| ProgNode::pair(&a, &b).with_span(span)))
})?
}
};
if let Some(reqd_ty) = reqd_ty {
res.arrow()
expr.arrow()
.target
.unify(
&reqd_ty.to_simplicity(),
"Type mismatch for user provided type",
)
.unwrap();
.unify(&reqd_ty.to_simplicity(), "")
.map_err(|_| Error::TypeValueMismatch(reqd_ty.clone()))
.with_span(span)?;
}
res
Ok(expr)
}
}
36 changes: 35 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::Arc;

use simplicity::elements;

use crate::parse::{Position, Span};
use crate::parse::{Identifier, Position, Span, Type, UIntType};
use crate::Rule;

/// Helper trait to convert `Result<T, E>` into `Result<T, RichError>`.
Expand Down Expand Up @@ -141,6 +141,14 @@ pub enum Error {
CannotParse(String),
Grammar(String),
UnmatchedPattern(String),
// TODO: Remove CompileError once Simfony has a type system
// The Simfony compiler should never produce ill-typed Simplicity code
// The compiler can only be this precise if it knows a type system at least as expressive as Simplicity's
CannotCompile(String),
JetDoesNotExist(Arc<str>),
TypeValueMismatch(Type),
InvalidDecimal(UIntType),
UndefinedVariable(Identifier),
}

#[rustfmt::skip]
Expand Down Expand Up @@ -175,6 +183,26 @@ impl fmt::Display for Error {
f,
"Pattern `{pattern}` not covered in match"
),
Error::CannotCompile(description) => write!(
f,
"Failed to compile to Simplicity: {description}"
),
Error::JetDoesNotExist(name) => write!(
f,
"Jet `{name}` does not exist"
),
Error::TypeValueMismatch(ty) => write!(
f,
"Value does not match the assigned type `{ty}`"
),
Error::InvalidDecimal(ty) => write!(
f,
"Use bit strings or hex strings for values of type `{ty}`"
),
Error::UndefinedVariable(identifier) => write!(
f,
"Variable `{identifier}` is not defined"
),
}
}
}
Expand All @@ -200,6 +228,12 @@ impl From<std::num::ParseIntError> for Error {
}
}

impl From<simplicity::types::Error> for Error {
fn from(error: simplicity::types::Error) -> Self {
Self::CannotCompile(error.to_string())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn _compile(file: &Path) -> Result<Arc<Node<Named<Commit<Elements>>>>, Strin
.with_file(file.clone())?;

let mut scope = GlobalScope::new();
let simplicity_named_commit = simfony_program.eval(&mut scope);
let simplicity_named_commit = simfony_program.eval(&mut scope).with_file(file)?;
let simplicity_redeem = simplicity_named_commit
.finalize_types_main()
.expect("Type check error");
Expand Down
Loading