diff --git a/bin/darkwallet/src/error.rs b/bin/darkwallet/src/error.rs
index a56549bf6..336df20b0 100644
--- a/bin/darkwallet/src/error.rs
+++ b/bin/darkwallet/src/error.rs
@@ -113,4 +113,7 @@ pub enum Error {
#[error("Channel closed")]
ChannelClosed = 36,
+
+ #[error("Unexpected token found")]
+ UnexpectedToken = 38,
}
diff --git a/bin/darkwallet/src/expr/compile.rs b/bin/darkwallet/src/expr/compile.rs
new file mode 100644
index 000000000..8df082bdd
--- /dev/null
+++ b/bin/darkwallet/src/expr/compile.rs
@@ -0,0 +1,321 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use crate::{
+ error::{Error, Result},
+ //prop::{Property, PropertySubType, PropertyType, PropertySExprValue},
+};
+
+use super::{Op, SExprCode};
+
+fn remove_whitespace(s: &str) -> String {
+ s.chars().filter(|c| !c.is_whitespace()).collect()
+}
+
+pub fn compile(stmts: &str) -> Result {
+ let mut code = vec![];
+ for stmt in stmts.split(';') {
+ code.push(compile_line(stmt)?);
+ }
+ Ok(code)
+}
+
+#[derive(Debug, Clone)]
+enum Token {
+ LoadVar(String),
+ Add,
+ Sub,
+ Mul,
+ Div,
+ LeftParen,
+ RightParen,
+ ConstFloat32(f32),
+ NestedExpr(Box>),
+}
+
+impl Token {
+ fn flatten(self) -> Vec {
+ match self {
+ Self::NestedExpr(tokens) => {
+ let tokens = Box::into_inner(tokens);
+ tokens.into_iter().map(|t| t.flatten()).flatten().collect()
+ }
+ _ => vec![self],
+ }
+ }
+}
+
+fn tokenize(stmt: &str) -> Vec {
+ let mut tokens = Vec::new();
+ let mut current_token = String::new();
+ for chr in stmt.chars() {
+ match chr {
+ '+' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::Add);
+ }
+ '-' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::Sub);
+ }
+ '*' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::Mul);
+ }
+ '/' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::Div);
+ }
+ '(' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::LeftParen);
+ }
+ ')' => {
+ clear_accum(&mut current_token, &mut tokens);
+ tokens.push(Token::RightParen);
+ }
+ _ => current_token.push(chr),
+ }
+ }
+ clear_accum(&mut current_token, &mut tokens);
+ tokens
+}
+
+fn clear_accum(current_token: &mut String, tokens: &mut Vec) {
+ let prev_token = std::mem::replace(current_token, String::new());
+ if prev_token.is_empty() {
+ return
+ }
+
+ // Number or var?
+ match prev_token.parse::() {
+ Ok(v) => tokens.push(Token::ConstFloat32(v)),
+ Err(_) => tokens.push(Token::LoadVar(prev_token)),
+ }
+}
+
+/// Convert from infix to reverse polish notation
+fn to_rpn(tokens: Vec) -> Result> {
+ //println!("to_rpn = {tokens:#?}");
+ let mut out = Vec::new();
+ let mut stack = Vec::new();
+
+ // Parens
+ let mut paren = 0;
+ for token in tokens {
+ match token {
+ Token::LeftParen => {
+ // Is this the first opening paren for this subexpr?
+ if paren > 0 {
+ stack.push(token);
+ }
+ paren += 1;
+ continue
+ }
+ Token::RightParen => {
+ paren -= 1;
+
+ // Whoops non-matching number of parens!
+ if paren < 0 {
+ return Err(Error::UnexpectedToken)
+ }
+
+ // Did we finally reach the closing paren for this subexpr?
+ if paren == 0 {
+ let stack = std::mem::take(&mut stack);
+ let mut rpn = to_rpn(stack)?;
+ out.push(Token::NestedExpr(Box::new(rpn)));
+ } else {
+ stack.push(token);
+ }
+
+ continue
+ }
+ _ => {}
+ }
+ if paren > 0 {
+ stack.push(token);
+ } else {
+ out.push(token);
+ }
+ }
+ out.append(&mut stack);
+
+ // */
+ let tokens = out.clone();
+ assert!(stack.is_empty());
+ out.clear();
+ let mut is_op = false;
+ for token in tokens {
+ match token {
+ Token::Mul | Token::Div => {
+ if is_op {
+ return Err(Error::UnexpectedToken)
+ }
+ is_op = true;
+
+ let Some(prev_item) = out.pop() else { return Err(Error::UnexpectedToken) };
+ stack.push(token);
+ stack.push(prev_item);
+ }
+ _ => {
+ if is_op {
+ is_op = false;
+ let mut expr = std::mem::take(&mut stack);
+ expr.push(token);
+ out.push(Token::NestedExpr(Box::new(expr)));
+
+ continue
+ }
+
+ out.push(token)
+ }
+ }
+ }
+ //println!("out = {out:#?}");
+ //println!("stack = {stack:#?}");
+ //assert!(!is_op);
+ //assert!(stack.is_empty());
+ if is_op || !stack.is_empty() {
+ return Err(Error::UnexpectedToken)
+ }
+
+ // +-
+ let tokens = out.clone();
+ out.clear();
+ let mut is_op = false;
+ for token in tokens {
+ match token {
+ Token::Add | Token::Sub => {
+ if is_op {
+ return Err(Error::UnexpectedToken)
+ }
+ is_op = true;
+
+ let Some(prev_item) = out.pop() else { return Err(Error::UnexpectedToken) };
+ stack.push(token);
+ stack.push(prev_item);
+ }
+ _ => {
+ if is_op {
+ is_op = false;
+ let mut expr = std::mem::take(&mut stack);
+ expr.push(token);
+ out.push(Token::NestedExpr(Box::new(expr)));
+
+ continue
+ }
+
+ out.push(token)
+ }
+ }
+ }
+ //assert!(!is_op);
+ //assert!(stack.is_empty());
+ if is_op || !stack.is_empty() {
+ return Err(Error::UnexpectedToken)
+ }
+
+ // Flatten everything
+ let out = out.into_iter().map(|t| t.flatten()).flatten().collect();
+ Ok(out)
+}
+
+fn convert>(iter: &mut I) -> Result {
+ let Some(token) = iter.next() else { return Err(Error::UnexpectedToken) };
+
+ let op = match token {
+ Token::ConstFloat32(v) => Op::ConstFloat32(v),
+ Token::LoadVar(v) => Op::LoadVar(v),
+ Token::Add => {
+ let lhs = convert(iter)?;
+ let rhs = convert(iter)?;
+ Op::Add((Box::new(lhs), Box::new(rhs)))
+ }
+ Token::Sub => {
+ let lhs = convert(iter)?;
+ let rhs = convert(iter)?;
+ Op::Sub((Box::new(lhs), Box::new(rhs)))
+ }
+ Token::Mul => {
+ let lhs = convert(iter)?;
+ let rhs = convert(iter)?;
+ Op::Mul((Box::new(lhs), Box::new(rhs)))
+ }
+ Token::Div => {
+ let lhs = convert(iter)?;
+ let rhs = convert(iter)?;
+ Op::Div((Box::new(lhs), Box::new(rhs)))
+ }
+ _ => return Err(Error::UnexpectedToken),
+ };
+ Ok(op)
+}
+
+fn compile_line(stmt: &str) -> Result {
+ let stmt = remove_whitespace(stmt);
+ let tokens = tokenize(&stmt);
+ //println!("{tokens:#?}");
+ let tokens = to_rpn(tokens)?;
+ //println!("{tokens:#?}");
+ Ok(convert(&mut tokens.into_iter())?)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn single_line() {
+ let code = compile("h/2 - 200").unwrap();
+ #[rustfmt::skip]
+ let code2 = vec![Op::Sub((
+ Box::new(Op::Div((
+ Box::new(Op::LoadVar("h".to_string())),
+ Box::new(Op::ConstFloat32(2.)),
+ ))),
+ Box::new(Op::ConstFloat32(200.)),
+ ))];
+ assert_eq!(code, code2);
+
+ let code = compile("(x + h/2 + (y + 7)/5) - 200").unwrap();
+ #[rustfmt::skip]
+ let code2 = vec![Op::Sub((
+ Box::new(Op::Add((
+ Box::new(Op::Add((
+ Box::new(Op::LoadVar("x".to_string())),
+ Box::new(Op::Div((
+ Box::new(Op::LoadVar("h".to_string())),
+ Box::new(Op::ConstFloat32(2.))
+ ))),
+ ))),
+
+ Box::new(Op::Div((
+ Box::new(Op::Add((
+ Box::new(Op::LoadVar("y".to_string())),
+ Box::new(Op::ConstFloat32(7.))
+ ))),
+ Box::new(Op::ConstFloat32(5.))
+ ))),
+ ))),
+
+ Box::new(Op::ConstFloat32(200.))
+ ))];
+ assert_eq!(code, code2);
+ }
+}
diff --git a/bin/darkwallet/src/expr.rs b/bin/darkwallet/src/expr/mod.rs
similarity index 99%
rename from bin/darkwallet/src/expr.rs
rename to bin/darkwallet/src/expr/mod.rs
index 8e80a6f27..f95342702 100644
--- a/bin/darkwallet/src/expr.rs
+++ b/bin/darkwallet/src/expr/mod.rs
@@ -25,6 +25,9 @@ use darkfi_serial::{
};
use std::io::{Read, Write};
+mod compile;
+pub use compile::compile;
+
#[derive(Clone, Debug, PartialEq, SerialEncodable, SerialDecodable)]
pub enum SExprVal {
Null,
diff --git a/bin/darkwallet/src/main.rs b/bin/darkwallet/src/main.rs
index da9e61bca..4fc3a37af 100644
--- a/bin/darkwallet/src/main.rs
+++ b/bin/darkwallet/src/main.rs
@@ -26,6 +26,8 @@
#![feature(stmt_expr_attributes)]
// if let Some(is_foo) = is_foo && is_foo { ... }
#![feature(let_chains)]
+// consume a box
+#![feature(box_into_inner)]
// Use these to incrementally fix warnings with cargo fix
//#![allow(warnings, unused)]