diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..2a3083e --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1 @@ +rustflags = ["-Cinstrument-coverage"] diff --git a/Cargo.toml b/Cargo.toml index 2ce2b99..36cd096 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ hex = "0.4" hex-literal = "0.3" indexmap = "1.5" k256 = { version = "0.10.2", features = ["std", "ecdsa", "serde"] } +nom = "7.1.1" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" reqwest = { version = "0.11.10", features = ["json"] } diff --git a/src/an_elem.rs b/src/an_elem.rs index 6e568bf..9176d1b 100644 --- a/src/an_elem.rs +++ b/src/an_elem.rs @@ -210,7 +210,7 @@ impl AnElem for Value { /// AnElem::from_elem errors -#[derive(Clone, Debug, Error)] +#[derive(Clone, Debug, Error, PartialEq)] pub enum AnElemError { /// AnElem::from_elem: element popped from the Stack wasn't the expected type #[error("AnElem::from_elem: element popped from the stack\n\n{found}\n\nwasn't the expected type:\n{expected:?}")] diff --git a/src/an_elem_return.rs b/src/an_elem_return.rs index c1e7e74..e8094df 100644 --- a/src/an_elem_return.rs +++ b/src/an_elem_return.rs @@ -10,6 +10,12 @@ pub struct Return { return_value: Arc>>, } +impl Default for Return { + fn default() -> Self { + Self::new() + } +} + impl Return { /// New Return slot with nothing in it pub fn new() -> Self { diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 1d8fb01..b29e20b 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -26,7 +26,8 @@ impl Arbitrary for ArbitraryNumber { } else { let x: f64 = Arbitrary::arbitrary(g); ArbitraryNumber { number: - Number::from_f64(x).unwrap_or(From::from(0u8)) + Number::from_f64(x) + .unwrap_or_else(|| From::from(0u8)) } } } @@ -51,7 +52,8 @@ impl Arbitrary for ArbitraryNumber { Some(self_f64) => Box::new( self_f64.shrink() .map(|x| ArbitraryNumber { - number: Number::from_f64(x).unwrap_or(From::from(0u8)), + number: Number::from_f64(x) + .unwrap_or_else(|| From::from(0u8)), })), } } diff --git a/src/cli.rs b/src/cli.rs index ddc4886..5d2104f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -187,7 +187,7 @@ impl Cli { } /// Run Cli::parse_and_run_result and print its result - pub async fn parse_and_run(&self) -> () { + pub async fn parse_and_run(&self) { match self.parse_and_run_result().await { Ok(()) => println!("successful!"), Err(e) => println!("failed:\n{}\n", e), @@ -195,7 +195,7 @@ impl Cli { } /// Run a set of Cli arguments - pub async fn run(&self) -> () { + pub async fn run(&self) { match self.command { None => self.parse_and_run().await, Some(Commands::Parse) => { diff --git a/src/elem.rs b/src/elem.rs index 497d835..07f1773 100644 --- a/src/elem.rs +++ b/src/elem.rs @@ -112,11 +112,11 @@ pub enum ElemSymbol { impl Arbitrary for ElemSymbol { fn arbitrary(g: &mut Gen) -> Self { let choices: Vec = EnumSet::all().iter().collect(); - *g.choose(&choices).unwrap_or_else(|| &Self::Unit) + *g.choose(&choices).unwrap_or(&Self::Unit) } fn shrink(&self) -> Box> { - let self_copy = self.clone(); + let self_copy = *self; Box::new(EnumSet::all().iter().filter(move |&x| x < self_copy)) } } diff --git a/src/elem_type.rs b/src/elem_type.rs index d068f9e..0a66bc2 100644 --- a/src/elem_type.rs +++ b/src/elem_type.rs @@ -51,10 +51,10 @@ impl Display for ElemType { .fold(String::new(), |memo, x| { let x_str: &'static str = From::from(x); - if memo == "" { + if memo.is_empty() { x_str.to_string() } else { - memo + ", " + &x_str.to_string() + memo + ", " + x_str } } )) @@ -101,7 +101,7 @@ impl ElemSymbol { info: locations.iter() .map(|&location| ElemTypeInfo { - location: location, + location, }).collect(), } } @@ -119,11 +119,11 @@ impl ElemType { pub fn from_locations(type_set: EnumSet, locations: Vec) -> Self { ElemType { - type_set: type_set, + type_set, info: locations.iter() .map(|&location| ElemTypeInfo { - location: location, + location, }).collect(), } } @@ -136,10 +136,10 @@ impl ElemType { } /// Calculate the union of two ElemType's and append their metadata - pub fn union(&self, other: Self) -> Self { + pub fn union(&self, other: &mut Self) -> Self { let both = self.type_set.union(other.type_set); let mut both_info = self.info.clone(); - both_info.append(&mut other.info.clone()); + both_info.append(&mut other.info); ElemType { type_set: both, info: both_info, @@ -149,7 +149,7 @@ impl ElemType { /// Unify two ElemType's by returning their intersection and combining their metadata /// /// Fails if their intersection is empty (i.e. if it results in an empty type) - pub fn unify(&self, other: Self) -> Result { + pub fn unify(&self, other: &mut Self) -> Result { let both = self.type_set.intersection(other.type_set); if both.is_empty() { Err(ElemTypeError::UnifyEmpty { @@ -158,7 +158,7 @@ impl ElemType { }) } else { let mut both_info = self.info.clone(); - both_info.append(&mut other.info.clone()); + both_info.append(&mut other.info); Ok(ElemType { type_set: both, info: both_info, @@ -239,13 +239,18 @@ impl StackType { self.types.len() } + /// Is length of the StackType empty? + pub fn is_empty(&self) -> bool { + self.types.is_empty() + } + /// Push the given ElemType to the StackType - pub fn push(&mut self, elem_type: ElemType) -> () { + pub fn push(&mut self, elem_type: ElemType) { self.types.insert(0, elem_type) } /// Push (count) copies of the given ElemType to the StackType - pub fn push_n(&mut self, elem_type: ElemType, count: usize) -> () { + pub fn push_n(&mut self, elem_type: ElemType, count: usize) { for _index in 0..count { self.push(elem_type.clone()) } diff --git a/src/elems.rs b/src/elems.rs index b603fe6..380b359 100644 --- a/src/elems.rs +++ b/src/elems.rs @@ -18,7 +18,7 @@ use thiserror::Error; // - random typed program? /// Errors thrown by Elems::pop -#[derive(Clone, Debug, Error)] +#[derive(Clone, Debug, Error, PartialEq)] pub enum ElemsPopError { /// "Elems::pop singleton: tried to pop an Elem that was not found:\nelem_symbol:\n{elem_symbol:?}\n\n{error}" #[error("Elems::pop singleton: tried to pop an Elem that was not found:\nelem_symbol:\n{elem_symbol:?}\n\n{error}")] diff --git a/src/elems_input_output_or.rs b/src/elems_input_output_or.rs index 345d877..1392037 100644 --- a/src/elems_input_output_or.rs +++ b/src/elems_input_output_or.rs @@ -79,7 +79,7 @@ where .map(|x| { match x { Or::Left(array) => Self::Left { - array: array, + array, returning: Return::new(), }, Or::Right(y) => Self::Right(y), @@ -125,13 +125,13 @@ where let mut type_tl = IOElems::type_of(PhantomData::) .map_err(|e| ElemsPopError::ReturnOrTl(Arc::new(e)))?; let last_type_id = type_tl.context.max_type_id() - .map_err(|e| ElemsPopError::ReturnOrContextError(e))?; + .map_err(ElemsPopError::ReturnOrContextError)?; let next_type_id = type_tl.context.push(ElemType { type_set: AnElem::elem_symbol(PhantomData::), info: vec![], }); type_tl.context.unify(last_type_id, next_type_id) - .map_err(|e| ElemsPopError::ReturnOrContextError(e))?; + .map_err(ElemsPopError::ReturnOrContextError)?; Ok(type_tl) } } diff --git a/src/elems_input_output_singleton.rs b/src/elems_input_output_singleton.rs index 79eb36c..a893447 100644 --- a/src/elems_input_output_singleton.rs +++ b/src/elems_input_output_singleton.rs @@ -98,7 +98,7 @@ where info: vec![], }); Ok(Type { - context: context, + context, i_type: (1..num_inputs).into_iter().map(|_| type_id).collect(), o_type: vec![type_id], }) diff --git a/src/elems_or.rs b/src/elems_or.rs index 38c2d56..b3c7d58 100644 --- a/src/elems_or.rs +++ b/src/elems_or.rs @@ -80,7 +80,7 @@ where match self { Self::Left(array) => IterOr::Left( Singleton { - array: array, + array, }.into_iter() ), Self::Right(xs) => IterOr::Right(xs.into_iter()), @@ -98,8 +98,6 @@ where type N = N; type Tl = U; - // fn left(_s: PhantomData, x: GenericArray) -> Self { Self::Left(x) } - // fn right(_s: PhantomData, x: Self::Tl) -> Self { Self::Right(x) } fn or) -> V, G: Fn(&Self::Tl) -> V>(&self, f: F, g: G) -> V { match self { Self::Left(x) => f(x), @@ -132,8 +130,8 @@ where type_set: AnElem::elem_symbol(PhantomData::), info: vec![], }; - let elem_type_tl = Elems::elem_type(PhantomData::)?; - Ok(elem_type_hd.union(elem_type_tl)) + let mut elem_type_tl = Elems::elem_type(PhantomData::)?; + Ok(elem_type_hd.union(&mut elem_type_tl)) } } diff --git a/src/elems_singleton.rs b/src/elems_singleton.rs index 3f8216d..6054748 100644 --- a/src/elems_singleton.rs +++ b/src/elems_singleton.rs @@ -70,7 +70,7 @@ where } })?; Ok(Singleton { - array: array, + array, }) } diff --git a/src/json_template.rs b/src/json_template.rs index 4d944e2..29ed662 100644 --- a/src/json_template.rs +++ b/src/json_template.rs @@ -9,12 +9,19 @@ use indexmap::IndexMap; use serde_json::{Map, Number, Value}; use thiserror::Error; +// TODO: relocate /// Map defined to be convenient to Serialize and Deserialize #[derive(Clone, Debug, PartialEq, Eq)] pub struct TMap { map: IndexMap, } +impl Default for TMap { + fn default() -> Self { + Self::new() + } +} + impl TMap { /// IndexMap::new pub fn new() -> Self { @@ -78,7 +85,7 @@ where } Ok(TMap { - map: map, + map, }) } } @@ -138,7 +145,7 @@ impl TValue { Value::Bool(x) => Self::Bool(x), Value::Number(x) => Self::Number(x), Value::String(x) => Self::String(x), - Value::Array(x) => Self::Array(x.into_iter().map(|x| TValue::from_json(x)).collect()), + Value::Array(x) => Self::Array(x.into_iter().map(TValue::from_json).collect()), Value::Object(x) => Self::Object(TMap { map: x.into_iter().map(|(x, y)| (x, TValue::from_json(y))).collect() }), @@ -164,13 +171,13 @@ impl TValue { Self::String(x) => Ok(Value::String(x)), Self::Array(x) => Ok(Value::Array(x.into_iter().map(|y| y.run(variables.clone())).collect::, TValueRunError>>()?)), Self::Object(x) => Ok(Value::Object(x.map.into_iter().map(|(y, z)| Ok((y, z.run(variables.clone())?))).collect::, TValueRunError>>()?)), - Self::Var(x) => { - variables.get(&x) - .map(|y| y.clone()) + Self::Var(variable) => { + variables.get(&variable) + .cloned() .ok_or_else(|| TValueRunError { - variable: x, + variable, value: vec![self_copy], - variables: variables, + variables, }) }, } @@ -198,12 +205,12 @@ impl Template { pub fn new(template: TValue) -> Self { Self { variables: Map::new(), - template: template, + template, } } /// Set the given variable name to the given Value - pub fn set(&mut self, name: String, value: Value) -> () { + pub fn set(&mut self, name: String, value: Value) { self.variables.insert(name, value); } diff --git a/src/lib.rs b/src/lib.rs index fa1ba47..e5822b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,9 @@ //! Cryptoscript Rust Library //! See cli for the command line interface -#![warn(missing_docs, elided_lifetimes_in_paths, explicit_outlives_requirements, keyword_idents, missing_copy_implementations, missing_debug_implementations, non_ascii_idents, noop_method_call, single_use_lifetimes, trivial_casts, trivial_numeric_casts, unreachable_pub, unused_extern_crates, unused_import_braces, unused_lifetimes, unused_qualifications)] +#![warn(missing_docs, elided_lifetimes_in_paths, explicit_outlives_requirements, keyword_idents, missing_copy_implementations, missing_debug_implementations, non_ascii_idents, noop_method_call, trivial_casts, trivial_numeric_casts, unreachable_pub, unused_extern_crates, unused_import_braces, unused_lifetimes, unused_qualifications)] +// single_use_lifetimes, // #![warn(unused_crate_dependencies)] // #![warn(unused_results)] @@ -80,6 +81,11 @@ pub use typed_instrs::Instrs; mod parse; pub use parse::{parse, parse_json}; +mod parse_utils; +pub use parse_utils::{parse_string, whitespace_delimited}; +mod parse_nom; +pub use parse_nom::{parse_nom, SourceCode, SourceBlock, Comment, Var, Assignment, App, Expr, TypeAnnotation, InstructionsWriter}; + mod rest_api; pub use rest_api::Api; mod cli; @@ -142,7 +148,7 @@ fn sha256(input: &Vec) -> Vec { // read hash digest and consume hasher let result = hasher.finalize(); - return result.to_vec(); + result.to_vec() } #[cfg(test)] diff --git a/src/location.rs b/src/location.rs index e15fe30..5e3cfec 100644 --- a/src/location.rs +++ b/src/location.rs @@ -10,7 +10,7 @@ pub struct LineNo { impl From for LineNo { fn from(line_no: usize) -> Self { LineNo { - line_no: line_no, + line_no, } } } @@ -35,7 +35,7 @@ impl LineNo { pub fn in_at(&self, argument_index: usize) -> Location { Location { line_no: *self, - argument_index: argument_index, + argument_index, is_input: true, } } @@ -47,7 +47,7 @@ impl LineNo { pub fn out_at(&self, argument_index: usize) -> Location { Location { line_no: *self, - argument_index: argument_index, + argument_index, is_input: false, } } diff --git a/src/main.rs b/src/main.rs index ba6cec8..86a136f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -328,8 +328,8 @@ async fn main() { - println!(""); - println!(""); + println!(); + println!(); // println!("Template test:"); // ERC-20 token balance (currently) diff --git a/src/parse.rs b/src/parse.rs index c6f490e..8816a60 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -19,7 +19,7 @@ use thiserror::Error; /// Parse a list of Instruction's using serde_json::from_str pub fn parse_json(input: &str) -> Result { - match serde_json::from_str(&input) { + match serde_json::from_str(input) { Err(serde_error) => Err(ParseError::SerdeJsonError(serde_error)), Ok(instructions) => Ok(instructions), } @@ -34,7 +34,7 @@ pub fn parse(input: &str) -> Result { .split(';') .map(|term| term.trim()) .filter(|&term| !term.is_empty()) - .map(|term| parse_instruction(term)) + .map(parse_instruction) .collect::, ParseError>>()?, }) } @@ -57,7 +57,7 @@ impl FromStr for Elem { fn from_str(s: &str) -> Result { match s.as_bytes() { [b'b', b'"', inner @ .., b'"'] => { - return Ok(Elem::Bytes(inner.to_vec())); + Ok(Elem::Bytes(inner.to_vec())) } [b'0', b'x', hex_digits @ ..] => { if hex_digits.len() != 64 { @@ -88,7 +88,7 @@ impl FromStr for Elem { }, )?; - return Ok(Elem::Bytes(bytes)) + Ok(Elem::Bytes(bytes)) } // No need to support booleans, but it is trivial to do so. _ => Err(ParseError::UnsupportedElem(s.to_string())), diff --git a/src/parse_nom.rs b/src/parse_nom.rs new file mode 100644 index 0000000..bfd1b7a --- /dev/null +++ b/src/parse_nom.rs @@ -0,0 +1,769 @@ +use crate::elem::{Elem, ElemSymbol}; +use crate::elems::ElemsPopError; +use crate::parse_utils::{whitespace_delimited, parse_string}; +use crate::restack::{Restack, StackIx}; +use crate::untyped_instruction::{Instruction, InstructionError}; +use crate::untyped_instructions::Instructions; +use crate::typed_instr::Instr; + +use std::cmp; +// use std::sync::Arc; +// use std::collections::BTreeSet; + +use nom::IResult; +use nom::branch::alt; +use nom::bytes::complete::{tag, take_till}; +use nom::character::complete::{alpha1, alphanumeric1, char, multispace0}; +use nom::combinator::recognize; +use nom::multi::{many0, many0_count, separated_list1}; +use nom::sequence::{delimited, pair, preceded, terminated}; +use nom::Parser; + +// var = app // comment +// +// app := function(arg0, arg1, .., argN) +// arg := var | app | literal +// literal := Elem +// var := alpha[alpha | num | '_']+ +// function := var // parsed as function contextually + +/// Line comments: "//comment" +pub type Comment = String; + +/// Rust-style variables +pub type Var = String; + +/// unpack_json type annotation +pub type TypeAnnotation = String; + +/// A parsed source file +#[derive(Debug, Clone, PartialEq)] +pub struct SourceCode { + /// Vec of SourceBlock's, in order + blocks: Vec>, +} + +impl SourceCode { + /// Parse all of the String's to Elem's + pub fn parse_elems(&self) -> Result, SourceCodeError> { + Ok(SourceCode { + blocks: self.blocks + .iter() + .map(|block| block.parse_elems()) + .collect::>, SourceCodeError>>()?, + }) + } +} + +/// A single block of parsed source code +#[derive(Debug, Clone, PartialEq)] +pub enum SourceBlock { + /// A line comment + Comment(Comment), + + /// An assignment, which could span multiple lines + Assignment(Assignment), +} + +impl SourceBlock { + /// Parse all of the String's to Elem's + pub fn parse_elems(&self) -> Result, SourceCodeError> { + match self { + Self::Comment(comment) => Ok(SourceBlock::Comment(comment.to_string())), + Self::Assignment(assignment) => Ok(SourceBlock::Assignment(assignment.parse_elems()?)), + } + } +} + +/// A single assignment: assignments are "simple," i.e. no pattern matching, etc +#[derive(Debug, Clone, PartialEq)] +pub struct Assignment { + /// Assigned variable + var: Var, + + /// Expression assigned + app: App, +} + +impl Assignment { + /// Parse all of the String's to Elem's + pub fn parse_elems(&self) -> Result, SourceCodeError> { + Ok(Assignment { + var: self.var.clone(), + app: self.app.parse_elems()?, + }) + } +} + +/// An application of a function to a Vec of arguments +#[derive(Debug, Clone, PartialEq)] +pub struct App { + /// The function variable: "f" in "f(1, 2, 3)" + function: Var, + + /// Optional (unpack_json) type annotation + type_annotation: Option, + + /// The argument expressions: "[1, 2, 3]" in "f(1, 2, 3)" + args: Vec>, +} + +impl App { + /// Parse all of the String's to Elem's + pub fn parse_elems(&self) -> Result, SourceCodeError> { + Ok(App { + function: self.function.clone(), + type_annotation: self.type_annotation.clone(), + args: self.args + .iter() + .map(|block| block.parse_elems()) + .collect::>, SourceCodeError>>()?, + }) + } +} + +/// Parsed expression +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + /// Function application + App(App), + + /// Literal + // Lit(String), + Lit(T), + + /// Variable + Var(Var), +} + +impl Expr { + /// Parse all of the String's to Elem's + pub fn parse_elems(&self) -> Result, SourceCodeError> { + match self { + Self::App(app) => Ok(Expr::App(app.parse_elems()?)), + Self::Lit(lit) => { + Ok(Expr::Lit(serde_json::from_str(lit) + .map_err(|e| SourceCodeError::SerdeJsonError { + error: format!("{}", e), + lit: lit.to_string(), + })?)) + }, + Self::Var(var) => Ok(Expr::Var(var.to_string())), + } + } +} + + +// TODO: support '!' ending variables? +// +// variables may start with a letter (or underscore) and may contain underscores and alphanumeric characters +fn parse_var(input: &str) -> IResult<&str, Var> { + recognize(pair( + alt((alpha1, tag("_"))), + many0_count(alt((alphanumeric1, tag("_"))))) + )(input) + .map(|(i, o)| (i, o.to_string())) +} + +#[cfg(test)] +mod test_parse_var { + use super::*; + + #[test] + fn test_single_char_var() { + for s in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + assert_eq!(parse_var(&s), Ok(("", s.clone()))) + } + } +} + +fn parse_comment(input: &str) -> IResult<&str, SourceBlock> { + preceded(tag("//"), take_till(|c| c == '\r' || c == '\n'))(input) + .map(|(i, o)| (i, SourceBlock::Comment(o.to_string()))) +} + +#[cfg(test)] +mod test_parse_comment { + use super::*; + + #[test] + fn test_single_char_comment() { + for s in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + let mut comment_str = "//".to_string(); + comment_str.push_str(&s); + assert_eq!(parse_comment::(&comment_str), Ok(("", SourceBlock::Comment(s.to_string())))) + } + } +} + +// var(arg0, arg1, .., argN) with 0 < N +fn parse_app(input: &str) -> IResult<&str, App> { + pair(parse_var, + delimited( + char('('), + separated_list1(whitespace_delimited(tag(",")), parse_expression), + char(')'), + ))(input) + .map(|(i, o)| (i, App { + function: o.0, + type_annotation: None, + args: o.1, + })) +} + +#[cfg(test)] +mod test_parse_app { + use super::*; + + #[test] + fn test_zero_argument_app() { + for s in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + let mut app_str = s.to_string(); + app_str.push_str("()"); + assert_eq!(parse_app(&app_str).ok(), None) + } + } + + #[test] + fn test_single_argument_app() { + for s_function in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + for s_arg in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + let mut app_str = s_function.clone().to_string(); + app_str.push_str("("); + app_str.push_str(&s_arg); + app_str.push_str(")"); + assert_eq!(parse_app(&app_str), Ok(("", App { + function: s_function.clone(), + type_annotation: None, + args: vec![Expr::Var(s_arg)], + }))) + } + } + } + + #[test] + fn test_two_argument_app() { + for s_function in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + for s_arg_1 in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + for s_arg_2 in (b'a' ..= b'z').map(|x| char::to_string(&char::from(x))) { + for spaces_1 in vec!["", " "] { + for spaces_2 in vec!["", " "] { + let mut app_str = s_function.clone().to_string(); + app_str.push_str(&"("); + app_str.push_str(&s_arg_1); + app_str.push_str(&spaces_1); + app_str.push_str(&","); + app_str.push_str(&spaces_2); + app_str.push_str(&s_arg_2); + app_str.push_str(&")"); + + assert_eq!(parse_app(&app_str), Ok(("", App { + function: s_function.clone(), + type_annotation: None, + args: vec![Expr::Var(s_arg_1.clone()), Expr::Var(s_arg_2.clone())], + }))) + } + } + } + } + } + } +} + +// TODO +fn parse_elem_literal(input: &str) -> IResult<&str, String> { + parse_string(input) + // .map(|(i, o)| (i, o)) +} + +#[cfg(test)] +mod test_parse_elem_literal { + use super::*; + + #[test] + fn test_string_no_escapes() { + assert_eq!(parse_elem_literal("\"\""), Ok(("", "".to_string()))); + assert_eq!(parse_elem_literal("\"Unit\""), Ok(("", "Unit".to_string()))); + assert_eq!(parse_elem_literal("\"''\""), Ok(("", "''".to_string()))); + assert_eq!(parse_elem_literal("\"[1, 2, 3]\""), Ok(("", "[1, 2, 3]".to_string()))); + } + + #[test] + fn test_string_escapes() { + assert_eq!(parse_elem_literal("\"\r\n\t\""), Ok(("", "\r\n\t".to_string()))); + } + + // TODO: support \\ in strings + // #[test] + // fn test_string_escapes_failing() { + // assert_eq!(parse_elem_literal("\"\\\""), Ok(("", "\\".to_string()))); + // } +} + +fn parse_expression(input: &str) -> IResult<&str, Expr> { + alt((parse_app.map(Expr::App), + parse_elem_literal.map(Expr::Lit), + parse_var.map(Expr::Var) + ))(input) +} + +fn parse_assignment(input: &str) -> IResult<&str, SourceBlock> { + pair(parse_var, + preceded(whitespace_delimited(tag("=")), + parse_app))(input) + .map(|(i, o)| (i, SourceBlock::Assignment(Assignment { + var: o.0, + app: o.1 + }))) +} + +#[cfg(test)] +mod test_parse_assignment { + use super::*; + + #[test] + fn test_assignments() { + assert_eq!(parse_assignment("foo = convolve(bar, baz(two, \"hi\"))"), Ok(("", SourceBlock::Assignment(Assignment { + var: "foo".to_string(), + app: App { + function: "convolve".to_string(), + type_annotation: None, + args: vec![ + Expr::Var("bar".to_string()), + Expr::App(App { + function: "baz".to_string(), + type_annotation: None, + args: vec![ + Expr::Var("two".to_string()), + Expr::Lit("hi".to_string()) + ], + }), + ], + }, + })))) + } +} + +fn parse_source_block(input: &str) -> IResult<&str, SourceBlock> { + preceded(multispace0, alt((parse_comment, parse_assignment)))(input) +} + +/// Parse a cryptoscript program as a series of assignments of the form: +/// "var = function(arg0, arg1, .., argN)" +pub fn parse_nom(input: &str) -> IResult<&str, SourceCode> { + terminated(many0(parse_source_block), multispace0)(input) + .map(|(i, o)| (i, SourceCode { blocks: o })) +} + +#[cfg(test)] +mod test_parse_source_code { + use super::*; + + #[test] + fn test_source_code_demo() { + let test_code_str = r#" + input_json = unpack_json(INPUT) + + queries = unpack_json(lookup("queries", input_json)) + first_query = unpack_json(index("0", queries)) + + _ = assert(check_eq(unpack_json(lookup("action", first_query)), "tokenbalance")) + _ = assert(check_eq(unpack_json(lookup("contractaddress", first_query)), "0x57d90b64a1a57749b0f932f1a3395792e12e7055")) + _ = assert(check_eq(unpack_json(lookup("result"), unpack_json(lookup("response", first_query))), "135499")) + + prompts = unpack_json(lookup("prompts", input_json)) + first_prompt = unpack_json(lookup("0", prompts)) + + _ = assert(check_eq(unpack_json(lookup("action", first_prompt)), "siwe")) + _ = assert(check_eq(unpack_json(lookup("version", first_prompt)), "1.1.0")) + _ = assert(check_eq(unpack_json(lookup("address", unpack_json(lookup("fields", unpack_json(lookup("data", first_prompt)))))), "0xe04f27eb70e025b78871a2ad7eabe85e61212761")) + + message_hash = hash_sha256(string_to_bytes(unpack_json(lookup("message", unpack_json(lookup("data", first_prompt)))))) + address_hash = hash_sha256(string_to_bytes(unpack_json(lookup("address", unpack_json(lookup("fields", unpack_json(lookup("data", first_prompt)))))))) + + // # Hex vs list of bytes? Infix? + // # assert!(hash_sha256(concat(message_hash, address_hash)) == [53,163,178,139,122,187,171,47,42,135,175,176,240,11,10,152,228,238,106,205,132,68,80,79,188,54,124,242,97,132,31,139]) + // # assert!(hash_sha256(concat(message_hash, address_hash)) == 0x35a3b28b7abbab2f2a87afb0f00b0a98e4ee6acd8444504fbc367cf261841f8b) + _ = assert(check_eq(hash_sha256(concat(message_hash, address_hash)), "0x35a3b28b7abbab2f2a87afb0f00b0a98e4ee6acd8444504fbc367cf261841f8b")) + "#; + + assert_eq!(parse_nom(&test_code_str).map(|(i, _o)| (i, ())), Ok(("", ()))) + } +} + + +/////////////////////////// +// NOM AST TO STACK AST +// +// TODO: relocate +/////////////////////////// + +/// Create a series of Instructions using a context of named Var's +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InstructionsWriter { + // defined_vars: BTreeMap, + /// The current (untyped) context of Var's on the stack + context: Vec, + + /// The current output series of instructions + instructions: Instructions, + // stack_size: usize, +} + +impl Default for InstructionsWriter { + fn default() -> Self { + Self::new() + } +} + +impl InstructionsWriter { + // writer.push("INPUT"); + + /// Create a new InstructionsWriter with an empty context + /// (i.e. no defined Var's) and output Instructions + pub fn new() -> Self { + Self { + context: vec![], + instructions: Instructions::new(), + } + } + + /// Get the StackIx of the Var, or throw an error + pub fn get_var(&self, var: &Var) -> Result { + self.context.iter() + .enumerate() + .find(|(_i, var_i)| *var_i == var) + .map(|(i, _var_i)| i) + .ok_or_else(|| SourceCodeError::InstructionsWriterGetVar { + context: self.context.clone(), + var: var.clone(), + }) + } + + /// New "temp_N" var with "N = max temp var index + 1" + pub fn new_var(&self) -> Var { + let max_var = self.context + .iter() + .filter_map(|var| { + let mut var_temp = var.clone(); + let var_index = var_temp.split_off(5); + if var_temp == "temp_" { + var_index.parse::().ok() + } else { + None + } + }) + .max() + .unwrap_or(0); + format!("temp_{:?}", max_var + 1) + } + + /// Restack so that the rhs is now in the lhs's slot as well + /// + /// lhs = rhs + pub fn assign(&mut self, lhs: Var, rhs: &Var) -> Result<(), SourceCodeError> { + let rhs_ix = self.get_var(rhs)?; + let assigned_context = self.context.iter().enumerate().map(|(i, var_i)| if *var_i == lhs { + (rhs_ix, rhs) + } else { + (i, var_i) + }); + + let restack_vec = assigned_context.clone() + .map(|(i, _var_i)| i) + .collect::>(); + self.context = assigned_context.clone() + .map(|(_i, var_i)| var_i.clone()) + .collect::>(); + + self.instructions.instructions.push(Instruction::Restack(Restack { + restack_depth: restack_vec.len(), + restack_vec, + })); + + Ok(()) + } + + /// Restack so that the needed_vars are at the top of the stack (without dropping any or + /// modifying the context) + #[allow(clippy::reversed_empty_ranges)] // (1..=0) is intended to be empty + pub fn restack_for_instruction(&mut self, needed_vars: Vec) -> Result<(), SourceCodeError> { + let largest_restacked_var: Option = needed_vars.iter() + .try_fold(None, |current_largest_restacked_var, needed_var| { + let var_index = self.get_var(needed_var)?; + Ok::, SourceCodeError>(Some(cmp::max(var_index, current_largest_restacked_var.unwrap_or(0)))) + })?; + let restack_vec = needed_vars.iter() + .map(|needed_var| self.get_var(needed_var)) + .chain(largest_restacked_var + .map(|restacked_vars| (0..=restacked_vars)) + .unwrap_or_else(|| (1..=0)) + .into_iter() + .map(Ok)) + .collect::, SourceCodeError>>()?; + self.instructions.instructions.push(Instruction::Restack(Restack { + restack_depth: restack_vec.len(), + restack_vec, + })); + Ok(()) + } + + /// Convert a Var to an untyped Instruction, given an optional + /// TypeAnnotation (required for e.g. UnpackJson) + pub fn var_to_instruction(function: Var, opt_type_annotation: Option) -> Result { + match (&*function, opt_type_annotation.clone()) { + ("hash_sha256", None) => Ok(Instruction::HashSha256), + ("check_le", None) => Ok(Instruction::CheckLe), + ("check_lt", None) => Ok(Instruction::CheckLt), + ("check_eq", None) => Ok(Instruction::CheckEq), + ("string_eq", None) => Ok(Instruction::StringEq), + ("bytes_eq", None) => Ok(Instruction::BytesEq), + ("concat", None) => Ok(Instruction::Concat), + ("slice", None) => Ok(Instruction::Slice), + ("index", None) => Ok(Instruction::Index), + ("lookup", None) => Ok(Instruction::Lookup), + ("assert_true", None) => Ok(Instruction::AssertTrue), + ("to_json", None) => Ok(Instruction::ToJson), + ("unpack_json", Some(type_annotation)) => { + + let elem_symbol = match &*type_annotation { + "Unit" => Ok(ElemSymbol::Unit), + "Bool" => Ok(ElemSymbol::Bool), + "Number" => Ok(ElemSymbol::Number), + "Bytes" => Ok(ElemSymbol::Bytes), + "String" => Ok(ElemSymbol::String), + "Array" => Ok(ElemSymbol::Array), + "Object" => Ok(ElemSymbol::Object), + "Json" => Ok(ElemSymbol::Json), + _ => Err(SourceCodeError::VarToInstructionUnknownType(type_annotation)), + }?; + Ok(Instruction::UnpackJson(elem_symbol)) + }, + ("string_to_bytes", None) => Ok(Instruction::StringToBytes), + (_, Some(type_annotation)) => + Err(SourceCodeError::VarToInstructionExtraAnnotation { + function, + type_annotation + }), + _ => Err(SourceCodeError::VarToInstructionUnknownFunction { + function, + opt_type_annotation, + }), + } + } + + /// - Push the instruction with the Var's name and optional TypeAnnotation + /// - Consume the variables from the stack + /// - Return the output variable (after pushing onto the stack) + pub fn instruction(&mut self, function: Var, type_annotation: Option) -> Result { + let instruction = Self::var_to_instruction(function, type_annotation)?; + let instr = instruction.clone().to_instr()?; + let instr_type = match instr { + Instr::Instr(instr2) => Ok(instr2.type_of()?), + Instr::Restack(restack) => Err(SourceCodeError::InstructionRestackUnexpected(restack)), + }?; + + // instruction written to log + self.instructions.push(instruction); + // variables consumed by instruction + self.context.drain(0..instr_type.i_type.len()); + let output_var = self.new_var(); + // variable produced by instruction + self.context.insert(0, output_var.clone()); + Ok(output_var) + } + + /// The final list of Instructions + pub fn finalize(&self) -> Result { + Ok(self.instructions.clone()) + } +} + +impl Expr { + /// Output variable representing the arg + pub fn to_instructions(&self, writer: &mut InstructionsWriter) -> Result { + match self { + Self::App(app) => app.to_instructions(writer), + Self::Lit(lit) => { + let new_var = writer.new_var(); + writer.instructions.push(Instruction::Push(lit.clone())); + writer.context.insert(0, new_var.clone()); + Ok(new_var) + }, + Self::Var(var) => { + let var_string = var.to_string(); + writer.get_var(&var_string)?; + Ok(var_string) + }, + } + } +} + +impl App { + /// Output variable returned + pub fn to_instructions(&self, writer: &mut InstructionsWriter) -> Result { + let needed_vars = self.args + .iter() + .map(|arg| arg.to_instructions(writer)) + .collect::, SourceCodeError>>()?; + writer.restack_for_instruction(needed_vars)?; + writer.instruction(self.function.clone(), self.type_annotation.clone()) + + // 1. iterate through args + // lit => push => var + // app => new temp var => var + // var => var + // 2. restack so that list of vars == list on stack + // 3. output that instruction + } +} + + +impl Assignment { + /// Convert an Assignment to Instructions, given an InstructionsWriter. + pub fn to_instructions(&self, writer: &mut InstructionsWriter) -> Result<(), SourceCodeError> { + let output_var = self.app.to_instructions(writer)?; + writer.assign(self.var.clone(), &output_var) + } +} + +impl SourceBlock { + /// Convert a SourceBlock to Instructions, given an InstructionsWriter. + pub fn to_instructions(&self, writer: &mut InstructionsWriter) -> Result<(), SourceCodeError> { + match self { + Self::Comment(_) => Ok(()), + Self::Assignment(assignment) => assignment.to_instructions(writer), + } + } +} + +impl SourceCode { + /// Convert a SourceBlock to Instructions, given an InstructionsWriter. + pub fn to_instructions(&self) -> Result { + let mut writer = InstructionsWriter::new(); + for block in &self.blocks { + block.to_instructions(&mut writer)? + } + writer.finalize() + } +} + +/// Source code errors +#[derive(Debug, Clone, PartialEq)] +pub enum SourceCodeError { + // var not found in context + InstructionsWriterGetVar { + context: Vec, + var: Var, + }, + + VarToInstructionUnknownType(TypeAnnotation), + + VarToInstructionExtraAnnotation { + function: Var, + type_annotation: TypeAnnotation, + }, + + VarToInstructionUnknownFunction { + function: Var, + opt_type_annotation: Option, + }, + + // TODO: make this error impossible by using a different enum + // Restack not expected to be able to be generated at this stage + InstructionRestackUnexpected(Restack), + + /// "ElemsPop failed: \n{0:?}\n" + // #[error("ElemsPop failed: \n{0:?}\n")] + ElemsPopError(ElemsPopError), + + /// "Instruction failed: \n{0:?}\n" + // #[error("Instruction failed: \n{0:?}\n")] + InstructionError(InstructionError), + + + /// "Instruction failed: \n{0:?}\n" + // #[error("Instruction failed: \n{0:?}\n")] + SerdeJsonError { + error: String, + lit: String + }, + + /// "Instruction failed: \n{0:?}\n" + // #[error("Instruction failed: \n{0:?}\n")] + NestedNomError(String), +} + +impl From for SourceCodeError { + fn from(error: ElemsPopError) -> Self { + Self::ElemsPopError(error) + } +} + +impl From for SourceCodeError { + fn from(error: InstructionError) -> Self { + Self::InstructionError(error) + } +} + +// impl From for SourceCodeError { +// fn from(error: serde_json::Error) -> Self { +// Self::SerdeJsonError(format!("{}", error)) +// } +// } + +impl From>> for SourceCodeError { + fn from(error: nom::Err>) -> Self { + Self::NestedNomError(format!("{}", error)) + } +} + +#[cfg(test)] +mod test_parse_source_code_to_instructions { + use super::*; + + #[test] + fn test_source_code_demo_to_instructions() { + let test_code_str = r#" + input_json = unpack_json(INPUT) + + queries = unpack_json(lookup("queries", input_json)) + first_query = unpack_json(index("0", queries)) + + _ = assert(check_eq(unpack_json(lookup("action", first_query)), "tokenbalance")) + _ = assert(check_eq(unpack_json(lookup("contractaddress", first_query)), "0x57d90b64a1a57749b0f932f1a3395792e12e7055")) + _ = assert(check_eq(unpack_json(lookup("result"), unpack_json(lookup("response", first_query))), "135499")) + + prompts = unpack_json(lookup("prompts", input_json)) + first_prompt = unpack_json(lookup("0", prompts)) + + _ = assert(check_eq(unpack_json(lookup("action", first_prompt)), "siwe")) + _ = assert(check_eq(unpack_json(lookup("version", first_prompt)), "1.1.0")) + _ = assert(check_eq(unpack_json(lookup("address", unpack_json(lookup("fields", unpack_json(lookup("data", first_prompt)))))), "0xe04f27eb70e025b78871a2ad7eabe85e61212761")) + + message_hash = hash_sha256(string_to_bytes(unpack_json(lookup("message", unpack_json(lookup("data", first_prompt)))))) + address_hash = hash_sha256(string_to_bytes(unpack_json(lookup("address", unpack_json(lookup("fields", unpack_json(lookup("data", first_prompt)))))))) + + // # Hex vs list of bytes? Infix? + // # assert!(hash_sha256(concat(message_hash, address_hash)) == [53,163,178,139,122,187,171,47,42,135,175,176,240,11,10,152,228,238,106,205,132,68,80,79,188,54,124,242,97,132,31,139]) + // # assert!(hash_sha256(concat(message_hash, address_hash)) == 0x35a3b28b7abbab2f2a87afb0f00b0a98e4ee6acd8444504fbc367cf261841f8b) + _ = assert(check_eq(hash_sha256(concat(message_hash, address_hash)), "0x35a3b28b7abbab2f2a87afb0f00b0a98e4ee6acd8444504fbc367cf261841f8b")) + "#; + + let result : Result<(&str, Instructions), SourceCodeError> = + parse_nom(&test_code_str) + .map_err(|e| From::from(e)) + .and_then(|(i, o)| { + Ok((i, o.parse_elems()?.to_instructions()?)) + }); + + assert_eq!(result, + // Ok(("", SourceCode { blocks: vec![] })) + Ok(("", Instructions::new())) + + ) + + } +} + + diff --git a/src/parse_utils.rs b/src/parse_utils.rs new file mode 100644 index 0000000..b7082bc --- /dev/null +++ b/src/parse_utils.rs @@ -0,0 +1,182 @@ +// use crate::elem::Elem; + +// use std::sync::Arc; + +use nom::IResult; +use nom::branch::alt; +use nom::bytes::complete::{is_not, take_while_m_n}; +use nom::character::complete::{char, multispace0, multispace1}; +use nom::combinator::{map, map_opt, map_res, value, verify}; +use nom::multi::fold_many0; +use nom::sequence::{delimited, preceded}; +use nom::error::{FromExternalError, ParseError}; + +/// A combinator that takes a parser `inner` and produces a parser that also consumes both leading and +/// trailing whitespace, returning the output of `inner`. +pub fn whitespace_delimited<'a, F: 'a, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> + where + F: Fn(&'a str) -> IResult<&'a str, O, E>, +{ + delimited( + multispace0, + inner, + multispace0 + ) +} + +// fn parens_delimited<'a, F: 'a, O, E: ParseError<&'a str>>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O, E> +// where +// F: Fn(&'a str) -> IResult<&'a str, O, E>, +// { +// delimited( +// char('('), +// inner, +// char(')'), +// ) +// } + + +// parse escapable string, from nom examples: + +// parser combinators are constructed from the bottom up: +// first we write parsers for the smallest elements (escaped characters), +// then combine them into larger parsers. + +/// Parse a unicode sequence, of the form u{XXXX}, where XXXX is 1 to 6 +/// hexadecimal numerals. We will combine this later with parse_escaped_char +/// to parse sequences like \u{00AC}. +fn parse_unicode<'a, E>(input: &'a str) -> IResult<&'a str, char, E> +where + E: ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>, +{ + // `take_while_m_n` parses between `m` and `n` bytes (inclusive) that match + // a predicate. `parse_hex` here parses between 1 and 6 hexadecimal numerals. + let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit()); + + // `preceded` takes a prefix parser, and if it succeeds, returns the result + // of the body parser. In this case, it parses u{XXXX}. + let parse_delimited_hex = preceded( + char('u'), + // `delimited` is like `preceded`, but it parses both a prefix and a suffix. + // It returns the result of the middle parser. In this case, it parses + // {XXXX}, where XXXX is 1 to 6 hex numerals, and returns XXXX + delimited(char('{'), parse_hex, char('}')), + ); + + // `map_res` takes the result of a parser and applies a function that returns + // a Result. In this case we take the hex bytes from parse_hex and attempt to + // convert them to a u32. + let parse_u32 = map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16)); + + // map_opt is like map_res, but it takes an Option instead of a Result. If + // the function returns None, map_opt returns an error. In this case, because + // not all u32 values are valid unicode code points, we have to fallibly + // convert to char with from_u32. + map_opt(parse_u32, std::char::from_u32)(input) +} + +/// Parse an escaped character: \n, \t, \r, \u{00AC}, etc. +fn parse_escaped_char<'a, E>(input: &'a str) -> IResult<&'a str, char, E> +where + E: ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>, +{ + preceded( + char('\\'), + // `alt` tries each parser in sequence, returning the result of + // the first successful match + alt(( + parse_unicode, + // The `value` parser returns a fixed value (the first argument) if its + // parser (the second argument) succeeds. In these cases, it looks for + // the marker characters (n, r, t, etc) and returns the matching + // character (\n, \r, \t, etc). + value('\n', char('n')), + value('\r', char('r')), + value('\t', char('t')), + value('\u{08}', char('b')), + value('\u{0C}', char('f')), + value('\\', char('\\')), + value('/', char('/')), + value('"', char('"')), + )), + )(input) +} + +/// Parse a backslash, followed by any amount of whitespace. This is used later +/// to discard any escaped whitespace. +fn parse_escaped_whitespace<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, &'a str, E> { + preceded(char('\\'), multispace1)(input) +} + +/// Parse a non-empty block of text that doesn't include \ or " +fn parse_literal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { + // `is_not` parses a string of 0 or more characters that aren't one of the + // given characters. + let not_quote_slash = is_not("\"\\"); + + // `verify` runs a parser, then runs a verification function on the output of + // the parser. The verification function accepts out output only if it + // returns true. In this case, we want to ensure that the output of is_not + // is non-empty. + verify(not_quote_slash, |s: &str| !s.is_empty())(input) +} + +/// A string fragment contains a fragment of a string being parsed: either +/// a non-empty Literal (a series of non-escaped characters), a single +/// parsed escaped character, or a block of escaped whitespace. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum StringFragment<'a> { + Literal(&'a str), + EscapedChar(char), + EscapedWS, +} + +/// Combine parse_literal, parse_escaped_whitespace, and parse_escaped_char +/// into a StringFragment. +fn parse_fragment<'a, E>(input: &'a str) -> IResult<&'a str, StringFragment<'a>, E> +where + E: ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>, +{ + alt(( + // The `map` combinator runs a parser, then applies a function to the output + // of that parser. + map(parse_literal, StringFragment::Literal), + map(parse_escaped_char, StringFragment::EscapedChar), + value(StringFragment::EscapedWS, parse_escaped_whitespace), + ))(input) +} + +/// Parse a string. Use a loop of parse_fragment and push all of the fragments +/// into an output string. +pub fn parse_string<'a, E>(input: &'a str) -> IResult<&'a str, String, E> +where + E: ParseError<&'a str> + FromExternalError<&'a str, std::num::ParseIntError>, +{ + // fold_many0 is the equivalent of iterator::fold. It runs a parser in a loop, + // and for each output value, calls a folding function on each output value. + let build_string = fold_many0( + // Our parser function– parses a single string fragment + parse_fragment, + // Our init value, an empty string + String::new, + // Our folding function. For each fragment, append the fragment to the + // string. + |mut string, fragment| { + match fragment { + StringFragment::Literal(s) => string.push_str(s), + StringFragment::EscapedChar(c) => string.push(c), + StringFragment::EscapedWS => {} + } + string + }, + ); + + // Finally, parse the string. Note that, if `build_string` could accept a raw + // " character, the closing delimiter " would never match. When using + // `delimited` with a looping parser (like fold_many0), be sure that the + // loop won't accidentally match your closing delimiter! + delimited(char('"'), build_string, char('"'))(input) +} + diff --git a/src/query.rs b/src/query.rs index 5970f0a..c0fa33a 100644 --- a/src/query.rs +++ b/src/query.rs @@ -109,8 +109,8 @@ impl QueryTemplate { pub fn to_query(self, variables: Arc>, cache_location: Arc) -> Query { Query { query_template: self, - variables: variables, - cache_location: cache_location, + variables, + cache_location, } } } @@ -233,6 +233,11 @@ impl QueryTemplates { self.queries.len() } + /// Is number of queries empty? + pub fn is_empty(&self) -> bool { + self.queries.is_empty() + } + /// Run a list of QueryTemplate's, in series, and collect their results pub async fn run(self, variables: Arc>, cache_location: Arc) -> Result>, QueryError> { let mut result = Vec::with_capacity(self.queries.len()); diff --git a/src/rest_api.rs b/src/rest_api.rs index 8c2d9e2..833399f 100644 --- a/src/rest_api.rs +++ b/src/rest_api.rs @@ -23,9 +23,9 @@ impl Api { /// Create a new Api that's never been called pub fn new(request: Value, response: Value, rate_limit_seconds: u64) -> Self { Api { - request: request, - response: response, - rate_limit_seconds: rate_limit_seconds, + request, + response, + rate_limit_seconds, last_api_call: None, } } diff --git a/src/restack.rs b/src/restack.rs index 45770b7..8b270c0 100644 --- a/src/restack.rs +++ b/src/restack.rs @@ -109,7 +109,10 @@ impl Restack { if self.restack_depth <= stack.len() { let result = self.restack_vec.iter().map(|&restack_index| match stack.get(restack_index) { - None => Err(RestackError::StackIndexInvalid{ restack_index: restack_index, restack_depth: self.restack_depth, }), + None => Err(RestackError::StackIndexInvalid{ + restack_index, + restack_depth: self.restack_depth, + }), Some(stack_element) => Ok( stack_element.clone() ), } ).collect::, RestackError>>(); @@ -148,7 +151,7 @@ impl Restack { restack_vec: self.restack_vec.iter().map(|&restack_index| match other.restack_vec.get(restack_index) { None => restack_index, - Some(stack_index) => stack_index.clone(), + Some(stack_index) => *stack_index, } ).collect() } diff --git a/src/stack.rs b/src/stack.rs index 7eb15eb..6082f60 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -32,6 +32,11 @@ impl Display for Stack { } } +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} impl Stack { /// New empty Stack @@ -45,9 +50,9 @@ impl Stack { // (so we know what we were expecting) /// Pop an Elem from the stack (remove 0th element) pub fn pop(&mut self) -> Result { - let result = self.stack.get(0).ok_or_else(|| StackError::EmptyStack).map(|x|x.clone())?; + let result = self.stack.get(0).ok_or(StackError::EmptyStack).map(|x|x.clone())?; self.stack = self.stack.drain(1..).collect(); - Ok(result.clone()) + Ok(result) } /// Pop AnElem from the stack (remove 0th element) @@ -78,7 +83,7 @@ impl Stack { let hd_elem = self.pop()?; xs.push(AnElem::from_elem(PhantomData::, hd_elem)?) } - GenericArray::from_exact_iter(xs).ok_or_else(|| StackError::PopGenericArray) + GenericArray::from_exact_iter(xs).ok_or(StackError::PopGenericArray) } /// Type of the Stack's elements @@ -89,7 +94,7 @@ impl Stack { } /// Debug a Stack's type - pub fn debug_type(&self) -> () { + pub fn debug_type(&self) { println!("stack type:\n{}", self.type_of()) } @@ -107,7 +112,7 @@ impl Stack { /// Stack errors -#[derive(Clone, Debug, Error)] +#[derive(Clone, Debug, Error, PartialEq)] pub enum StackError { /// Stack::pop: tried to pop from an empty stack #[error("Stack::pop: tried to pop from an empty stack")] diff --git a/src/typed_instr.rs b/src/typed_instr.rs index bbed700..9e069c4 100644 --- a/src/typed_instr.rs +++ b/src/typed_instr.rs @@ -38,7 +38,7 @@ impl Instruction { pub fn to_instr(self) -> Result { match self { Self::Push(elem) => Ok(Instr::Instr(Arc::new(Push { push: elem }))), - Self::Restack(restack) => Ok(Instr::Restack(restack.clone())), + Self::Restack(restack) => Ok(Instr::Restack(restack)), Self::HashSha256 => Ok(Instr::Instr(Arc::new(HashSha256 {}))), Self::CheckLe => Ok(Instr::Instr(Arc::new(CheckLe {}))), Self::CheckLt => Ok(Instr::Instr(Arc::new(CheckLt {}))), @@ -60,7 +60,7 @@ impl Instruction { ElemSymbol::Array => Ok(Instr::Instr(Arc::new(UnpackJson { t: PhantomData::> }))), ElemSymbol::Object => Ok(Instr::Instr(Arc::new(UnpackJson { t: PhantomData::> }))), _ => Err(InstructionError::UnpackJson { - elem_symbol: elem_symbol, + elem_symbol, }) } }, diff --git a/src/typed_instrs.rs b/src/typed_instrs.rs index 5e4bef1..c9308d0 100644 --- a/src/typed_instrs.rs +++ b/src/typed_instrs.rs @@ -18,6 +18,12 @@ pub struct Instrs { pub instrs: Vec, } +impl Default for Instrs { + fn default() -> Self { + Self::new() + } +} + impl Instrs { /// A new empty list of Instr's pub fn new() -> Self { @@ -41,12 +47,12 @@ impl Instrs { println!("{}\n", restack .type_of(From::from(line_no)) - .map_err(|e| ElemsPopError::RestackError(e))?); + .map_err(ElemsPopError::RestackError)?); }, } } println!("--------------------------------------------------------------------------------"); - println!(""); + println!(); Ok(()) } @@ -54,21 +60,21 @@ impl Instrs { /// what's the monomorphic type of Self? pub fn type_of_mono(&self, num_input_json: usize) -> Result { let mut stack_type = (0..num_input_json).map(|_| ElemType::from_locations(EnumSet::only(ElemSymbol::Json), vec![])).collect(); - for (line_no, instr_or_restack) in (&self.instrs).into_iter().enumerate() { + for (line_no, instr_or_restack) in (&self.instrs).iter().enumerate() { println!("------------------------------------------------------------------------------------------"); println!("line_no: {}", line_no); println!("{:?}\n", instr_or_restack); match instr_or_restack { Instr::Instr(instr) => { let mut instr_type = instr.type_of() - .map_err(|e| StackInstructionError::ElemsPopError(e))?; + .map_err(StackInstructionError::ElemsPopError)?; println!("instr: {}\n", instr_type); stack_type = instr_type.specialize_to_input_stack(stack_type) - .map_err(|e| StackInstructionError::TypeError(e))?; + .map_err(StackInstructionError::TypeError)?; }, Instr::Restack(restack) => { restack.run(&mut stack_type.types) - .map_err(|e| StackInstructionError::RestackError(e))? + .map_err(StackInstructionError::RestackError)? }, } } @@ -82,14 +88,14 @@ impl Instrs { /// instructions have non-matching types, e.g. if "Push(true)" is /// immediately followed by "UnpackJson". pub fn run(&self, stack: &mut Stack) -> Result<(), StackInstructionError> { - for (line_no, instr_or_restack) in (&self.instrs).into_iter().enumerate() { + for (line_no, instr_or_restack) in (&self.instrs).iter().enumerate() { stack.debug().map_err(|e| StackInstructionError::DebugJsonError(Arc::new(e)))?; println!("------------------------------------------------------------------------------------------"); println!("line_no: {}", line_no); println!("{:?}\n", instr_or_restack); match instr_or_restack { Instr::Instr(instr) => { - println!(""); + println!(); stack.debug_type(); match instr.type_of() { Ok(instr_type) => { @@ -103,12 +109,12 @@ impl Instrs { }, Err(e) => println!("instr type_of errror: {}\n", e), } - println!(""); + println!(); instr.stack_run(stack)? }, Instr::Restack(restack) => { restack.run(&mut stack.stack) - .map_err(|e| StackInstructionError::RestackError(e))? + .map_err(StackInstructionError::RestackError)? }, } } @@ -120,14 +126,13 @@ impl Instrs { } /// Push an instruction that IsStackInstruction onto the list of instructions - pub fn instr(&mut self, instr: impl IsStackInstruction + 'static) -> () { + pub fn instr(&mut self, instr: impl IsStackInstruction + 'static) { self.instrs.push(Instr::Instr(Arc::new(instr))) } /// Push a Restack onto the list of instructions - pub fn restack(&mut self, restack: Restack) -> () { + pub fn restack(&mut self, restack: Restack) { self.instrs.push(Instr::Restack(restack)) } } - diff --git a/src/typed_instruction.rs b/src/typed_instruction.rs index bbe76b5..8cad874 100644 --- a/src/typed_instruction.rs +++ b/src/typed_instruction.rs @@ -38,7 +38,7 @@ pub enum StackInstructionError { ElemsPopError(ElemsPopError), #[error("RawStackInstructionError:\n{0}")] - RawStackInstructionError(String), + RawStackInstructionErrorString(String), #[error("MissingOutput:\n{instruction}\n\n{stack_input}")] // TODO: more granular error typing @@ -89,9 +89,9 @@ where fn stack_run(&self, stack: &mut Stack) -> Result<(), StackInstructionError> { let stack_input = &IsList::pop(PhantomData::<::IO>, stack) - .map_err(|e| StackInstructionError::ElemsPopError(e))?; + .map_err(StackInstructionError::ElemsPopError)?; self.run(stack_input) - .map_err(|e| StackInstructionError::RawStackInstructionError(format!("{:?}", e)))?; + .map_err(|e| StackInstructionError::RawStackInstructionErrorString(format!("{:?}", e)))?; let output_value = stack_input .returning() .ok_or_else(|| StackInstructionError::MissingOutput { diff --git a/src/typed_instructions.rs b/src/typed_instructions.rs index 13e0f8a..dcd1002 100644 --- a/src/typed_instructions.rs +++ b/src/typed_instructions.rs @@ -59,12 +59,12 @@ impl IsInstructionT for Concat { ReturnOr::Left { array, returning } => { let lhs = &array[0]; let rhs = &array[1]; - returning.returning(lhs.into_iter().chain(rhs.into_iter()).cloned().collect()); + returning.returning(lhs.iter().chain(rhs.iter()).cloned().collect()); }, ReturnOr::Right(ReturnOr::Left { array, returning }) => { let lhs = &array[0]; let rhs = &array[1]; - returning.returning(lhs.into_iter().chain(rhs.into_iter()).cloned().collect()); + returning.returning(lhs.iter().chain(rhs.iter()).cloned().collect()); }, ReturnOr::Right(ReturnOr::Right(ReturnSingleton { singleton, returning })) => { let lhs = &singleton.array[0]; @@ -237,10 +237,10 @@ impl IsInstructionT for Slice { match y.clone() { ReturnOr::Left { array, returning } => { let iterable = &array[0]; - if iterable.clone().into_iter().count() < u_offset_plus_length { + if iterable.clone().len() < u_offset_plus_length { Err(()) } else { - returning.returning(iterable.into_iter().skip(u_offset).take(u_length).copied().collect()); + returning.returning(iterable.iter().skip(u_offset).take(u_length).copied().collect()); Ok(()) } }, @@ -255,10 +255,10 @@ impl IsInstructionT for Slice { }, ReturnOr::Right(ReturnOr::Right(ReturnOr::Left { array, returning })) => { let iterable = &array[0]; - if iterable.clone().into_iter().count() < u_offset_plus_length { + if iterable.clone().len() < u_offset_plus_length { Err(()) } else { - returning.returning(iterable.into_iter().skip(u_offset).take(u_length).cloned().collect()); + returning.returning(iterable.iter().skip(u_offset).take(u_length).cloned().collect()); Ok(()) } }, @@ -334,15 +334,13 @@ impl IsInstructionT for Index { array[0] .clone() .into_iter() - .skip(u_index) - .next() + .nth(u_index) }, Or::Right(Singleton { array }) => { array[0] .clone() .into_iter() - .skip(u_index) - .next() + .nth(u_index) .map(|(_x, y)| y) }, }.ok_or_else(|| { @@ -629,9 +627,9 @@ impl IsInstructionT for CheckLe { let lhs = array[0].clone(); let rhs = array[1].clone(); let cmp_result = lhs.partial_cmp(&rhs) - .ok_or_else(|| CheckLeError { - lhs: lhs, - rhs: rhs + .ok_or(CheckLeError { + lhs, + rhs })?; let result = match cmp_result { cmp::Ordering::Less => true, @@ -676,14 +674,11 @@ impl IsInstructionT for CheckLt { let lhs = array[0].clone(); let rhs = array[1].clone(); let cmp_result = lhs.partial_cmp(&rhs) - .ok_or_else(|| CheckLtError { - lhs: lhs, - rhs: rhs + .ok_or(CheckLtError { + lhs, + rhs })?; - let result = match cmp_result { - cmp::Ordering::Less => true, - _ => false, - }; + let result = matches!(cmp_result, cmp::Ordering::Less); returning.returning(result); Ok(()) } @@ -723,14 +718,11 @@ impl IsInstructionT for CheckEq { let lhs = array[0].clone(); let rhs = array[1].clone(); let cmp_result = lhs.partial_cmp(&rhs) - .ok_or_else(|| CheckEqError { - lhs: lhs, - rhs: rhs + .ok_or(CheckEqError { + lhs, + rhs })?; - let result = match cmp_result { - cmp::Ordering::Equal => true, - _ => false, - }; + let result = matches!(cmp_result, cmp::Ordering::Equal); returning.returning(result); Ok(()) } diff --git a/src/types.rs b/src/types.rs index dcea3c7..7868a18 100644 --- a/src/types.rs +++ b/src/types.rs @@ -73,7 +73,7 @@ impl Type { /// Update a TypeId, failing if "from" isn't present or "to" already is pub fn update_type_id(&mut self, from: TypeId, to: TypeId) -> Result<(), TypeError> { - self.context.update_type_id(from, to).map_err(|e| TypeError::UpdateTypeId(e))?; + self.context.update_type_id(from, to).map_err(TypeError::UpdateTypeId)?; self.i_type = self.i_type.iter().map(|x| x.update_type_id(from, to)).collect(); self.o_type = self.o_type.iter().map(|x| x.update_type_id(from, to)).collect(); Ok(()) @@ -84,11 +84,13 @@ impl Type { let mut basis = self.i_type.clone(); basis.append(&mut self.o_type.clone()); basis.dedup(); - let (new_context, type_map) = self.context.normalize_on(basis).map_err(|e| TypeError::NormalizeContextError(e))?; + let (new_context, type_map) = self.context + .normalize_on(basis) + .map_err(TypeError::NormalizeContextError)?; Ok(Type { context: new_context, - i_type: type_map.run(self.i_type.clone()).map_err(|e| TypeError::TypeIdMapError(e))?, - o_type: type_map.run(self.o_type.clone()).map_err(|e| TypeError::TypeIdMapError(e))?, + i_type: type_map.run(self.i_type.clone()).map_err(TypeError::TypeIdMapError)?, + o_type: type_map.run(self.o_type.clone()).map_err(TypeError::TypeIdMapError)?, }) } @@ -103,7 +105,7 @@ impl Type { // TODO: elimate copy? let elem_type_copy = elem_type.clone(); self.context.unify_elem_type(type_id, elem_type).map_err(|e| TypeError::Specialization { - type_id: type_id, + type_id, elem_type: elem_type_copy, context: self.context.clone(), error: e, @@ -122,13 +124,13 @@ impl Type { stack_type: stack_type.clone(), }) }).collect::, ContextError>>() - .map_err(|e| TypeError::SpecializeToInputStackContextError(e))?, + .map_err(TypeError::SpecializeToInputStackContextError)?, }) } else { Err(TypeError::SpecializeToInputStack { type_of: self.clone(), - stack_type: stack_type.clone(), + stack_type, }) } } @@ -150,7 +152,7 @@ impl Type { /// 2. collect the remainder and add them to the context /// 3. add the remainder to (self.i_type, other.o_type), with replaced variables pub fn compose(&self, other: Self) -> Result { - println!(""); + println!(); println!("composing:\n{0}\n\nAND\n{1}\n", self, other); let mut context = self.context.clone(); @@ -161,7 +163,7 @@ impl Type { // println!("offset_other: {}", offset_other); context.disjoint_union(offset_other.context.clone()) - .map_err(|e| TypeError::ComposeContextError(e))?; + .map_err(TypeError::ComposeContextError)?; // println!("context union: {}", context); let mut mut_offset_other = offset_other.clone(); @@ -172,21 +174,21 @@ impl Type { zip_len += 1; context .unify(o_type, i_type) - .map_err(|e| TypeError::ComposeContextError(e))?; + .map_err(TypeError::ComposeContextError)?; mut_offset_other .update_type_id(o_type, i_type)?; Ok(()) })?; Ok(Type { - context: context, + context, i_type: mut_offset_other.i_type.iter().chain(self.i_type.iter().skip(zip_len)).copied().collect(), o_type: self.o_type.iter().chain(mut_offset_other.o_type.iter().skip(zip_len)).copied().collect(), }) } /// Prepend inputs to self.i_type - pub fn prepend_inputs(&mut self, num_copies: usize, elem_type: ElemType) -> () { + pub fn prepend_inputs(&mut self, num_copies: usize, elem_type: ElemType) { if 0 < num_copies { let type_id = self.context.push(elem_type); self.i_type = (1..num_copies).into_iter() @@ -197,7 +199,7 @@ impl Type { } /// Append inputs to self.i_type - pub fn append_inputs(&mut self, elem_types: T) -> () + pub fn append_inputs(&mut self, elem_types: T) where T: IntoIterator, { @@ -226,14 +228,14 @@ impl Type { // ``` // // Results in: -// +// ``` // ∀ (t0 ∊ {A, B, C}), // ∀ (t1 ∊ {B, C}), // .. // ∀ (tN ∊ {D, E, F}), // [t0, t1, .., tN] -> // [ti, tj, .., tk] -// +// ``` impl Display for Type { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { // TODO: fix normalize @@ -244,14 +246,14 @@ impl Display for Type { context = self_normalized.context, i_type = self_normalized.i_type.iter().fold(String::new(), |memo, x| { let x_str = x.debug(); - if memo == "" { + if memo.is_empty() { x_str } else { memo + ", " + &x_str }}), o_type = self_normalized.o_type.iter().fold(String::new(), |memo, x| { let x_str = x.debug(); - if memo == "" { + if memo.is_empty() { x_str } else { memo + ", " + &x_str @@ -365,8 +367,8 @@ impl Restack { let i_type = restack_type.clone(); self.run(&mut restack_type)?; Ok(Type { - context: context, - i_type: i_type, + context, + i_type, o_type: restack_type, }) } diff --git a/src/types/context.rs b/src/types/context.rs index e7848fb..6944dc8 100644 --- a/src/types/context.rs +++ b/src/types/context.rs @@ -47,7 +47,7 @@ impl Display for Context { .fold(String::new(), |memo, (i, xs)| { memo + "\n" + - &format!("∀ ({t_i} ∊ {xs}),", t_i = i.debug(), xs = xs).to_string() + &format!("∀ ({t_i} ∊ {xs}),", t_i = i.debug(), xs = xs) })) } } @@ -78,6 +78,12 @@ mod context_display_tests { } } +impl Default for Context { + fn default() -> Self { + Self::new() + } +} + impl Context { /// New empty context with next_type_id = 0. pub fn new() -> Self { @@ -117,7 +123,7 @@ impl Context { for &type_id in &basis { match source.context.remove(&type_id) { None => Err(ContextError::NormalizeOnInvalidBasis { - type_id: type_id, + type_id, context: self.clone(), basis: basis.clone().into_iter().collect(), }), @@ -148,15 +154,15 @@ impl Context { Ok(()) } else { Err(ContextError::UpdateTypeIdFromMissing { - from: from, - to: to, + from, + to, context: self.clone(), }) }?; if self.context.contains_key(&to) { Err(ContextError::UpdateTypeIdToPresent { - from: from, - to: to, + from, + to, context: self.clone(), }) } else { @@ -175,9 +181,9 @@ impl Context { Ok(()) }, Some(conflicting_elem_type) => Err(ContextError::DisjointUnion { - type_id: type_id, + type_id, elem_type: elem_type.clone(), - conflicting_elem_type: conflicting_elem_type, + conflicting_elem_type, lhs: self.clone(), rhs: other.clone(), }), @@ -201,22 +207,22 @@ impl Context { pub fn unify(&mut self, xi: TypeId, yi: TypeId) -> Result<(), ContextError> { let x_type = self.context.remove(&xi).ok_or_else(|| ContextError::Unify { xs: self.clone(), - xi: xi.clone(), - yi: yi.clone(), + xi, + yi, is_lhs: true, })?; - let y_type = self.context.remove(&yi).ok_or_else(|| ContextError::Unify { + let mut y_type = self.context.remove(&yi).ok_or_else(|| ContextError::Unify { xs: self.clone(), - xi: xi.clone(), - yi: yi.clone(), + xi, + yi, is_lhs: false, })?; - let xy_type = x_type.unify(y_type).or_else(|e| Err(ContextError::UnifyElemType { + let xy_type = x_type.unify(&mut y_type).map_err(|error| ContextError::UnifyElemType { xs: self.clone(), - xi: xi.clone(), - yi: yi.clone(), - error: e, - }))?; + xi, + yi, + error, + })?; self.context.insert(yi, xy_type); Ok(()) } diff --git a/src/types/type_id.rs b/src/types/type_id.rs index a7a725f..0db4f39 100644 --- a/src/types/type_id.rs +++ b/src/types/type_id.rs @@ -20,7 +20,7 @@ impl TypeId { /// New TypeId with the given ID pub fn new(type_id: usize) -> Self { Self { - type_id: type_id, + type_id, } } @@ -32,7 +32,7 @@ impl TypeId { /// Subtract one or return None if 0 pub fn previous(&self) -> Option { self.type_id.checked_sub(1).map(|type_id| Self { - type_id: type_id, + type_id, }) } diff --git a/src/types/type_id/map.rs b/src/types/type_id/map.rs index ad01139..7cceb5a 100644 --- a/src/types/type_id/map.rs +++ b/src/types/type_id/map.rs @@ -13,6 +13,12 @@ pub struct TypeIdMap { map: BTreeMap, } +impl Default for TypeIdMap { + fn default() -> Self { + Self::new() + } +} + impl TypeIdMap { /// New empty TypeIdMap pub fn new() -> Self { @@ -24,15 +30,15 @@ impl TypeIdMap { /// Add a mapping to the TypeIdMap, failing if the "from" TypeId" already /// exists in the map pub fn push(&mut self, from: TypeId, to: TypeId) -> Result<(), TypeIdMapError> { - if self.map.contains_key(&from) { + if let std::collections::btree_map::Entry::Vacant(e) = self.map.entry(from) { + e.insert(to); + Ok(()) + } else { Err(TypeIdMapError::PushExists { - from: from, - to: to, + from, + to, map: self.clone(), }) - } else { - self.map.insert(from, to); - Ok(()) } } @@ -40,15 +46,15 @@ impl TypeIdMap { pub fn get(&self, index: &TypeId, location: usize) -> Result<&TypeId, TypeIdMapError> { self.map.get(index) .ok_or_else(|| TypeIdMapError::GetUnknownTypeId { - index: index.clone(), - location: location, + index: *index, + location, type_map: self.clone(), }) } /// Resolve the map on a Vec of TypeId's pub fn run(&self, type_vars: Vec) -> Result, TypeIdMapError> { - type_vars.iter().enumerate().map(|(i, x)| Ok(self.get(x, i)?.clone())).collect() + type_vars.iter().enumerate().map(|(i, x)| Ok(*self.get(x, i)?)).collect() } } diff --git a/src/untyped_instruction.rs b/src/untyped_instruction.rs index 0c37bbd..353292a 100644 --- a/src/untyped_instruction.rs +++ b/src/untyped_instruction.rs @@ -28,7 +28,7 @@ pub enum Instruction { StringToBytes, } -#[derive(Clone, Copy, Debug, Error)] +#[derive(Clone, Copy, Debug, Error, PartialEq)] pub enum InstructionError { #[error("Instruction::to_instr UnpackJson does not support: {elem_symbol:?}")] UnpackJson { diff --git a/src/untyped_instructions.rs b/src/untyped_instructions.rs index dd5ed34..8e4a6c8 100644 --- a/src/untyped_instructions.rs +++ b/src/untyped_instructions.rs @@ -21,7 +21,25 @@ impl IntoIterator for Instructions { } } +impl Default for Instructions { + fn default() -> Self { + Self::new() + } +} + impl Instructions { + /// New empty list of untyped instructions + pub fn new() -> Self { + Instructions { + instructions: vec![], + } + } + + /// Push an Instruction onto the end of the list of instructions + pub fn push(&mut self, instruction: Instruction) { + self.instructions.push(instruction) + } + /// Convert to a list of typed instructions pub fn to_instrs(self) -> Result { Ok(Instrs {