/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 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 std::collections::HashMap; use super::{Op, SExprCode}; #[derive(Debug, Clone)] enum Token { LoadVar(String), Add, Sub, Mul, Div, LeftParen, RightParen, ConstFloat32(f32), NestedExpr(Box>), SubExpr(Box>), If, Else, LeftBrace, RightBrace, LessThan, IfElse((Box>, Box>, Box>)), LessThanCompare((Box>, Box>)), Equals, SetValue((String, Box>)), } impl Token { fn flatten(self) -> Vec { match self { Self::NestedExpr(tokens) => { let tokens: Vec<_> = *tokens; tokens.into_iter().map(|t| t.flatten()).flatten().collect() } _ => vec![self], } } } #[derive(Clone)] pub struct Compiler { table: HashMap, } impl Compiler { pub fn new() -> Self { Self { table: HashMap::new() } } pub fn add_const_f32>(&mut self, name: S, val: f32) { self.table.insert(name.into(), Token::ConstFloat32(val)); } /* pub fn get_const_f32>(&self, name: S) -> Option { let name = name.as_ref(); let val = self.table.get(name)?; match val { Token::ConstFloat32(v) => Some(v) _ => None } } */ pub fn compile>(&self, prestmts: S) -> Result { let prestmts = prestmts.as_ref(); // Strip all comments let mut stmts = String::new(); for line in prestmts.lines() { if let Some(chr) = line.trim_start().chars().next() { if chr != '#' { stmts.push_str(line); } } } let mut code = vec![]; for stmt in stmts.split(';') { code.push(self.compile_line(stmt)?); } Ok(code) } fn compile_line(&self, stmt: &str) -> Result { let tokens = self.tokenize(&stmt); //println!("{tokens:#?}"); let tokens = to_rpn(tokens)?; //println!("{tokens:#?}"); Ok(convert(&mut tokens.into_iter())?) } fn tokenize(&self, stmt: &str) -> Vec { let mut tokens = Vec::new(); let mut current_token = String::new(); for chr in stmt.chars() { match chr { ' ' | '\t' | '\n' => { self.clear_accum(&mut current_token, &mut tokens); } '+' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::Add); } '-' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::Sub); } '*' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::Mul); } '/' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::Div); } '(' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::LeftParen); } ')' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::RightParen); } '{' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::LeftBrace); } '}' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::RightBrace); } '<' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::LessThan); } '=' => { self.clear_accum(&mut current_token, &mut tokens); tokens.push(Token::Equals); } _ => current_token.push(chr), } } self.clear_accum(&mut current_token, &mut tokens); tokens } fn clear_accum(&self, current_token: &mut String, tokens: &mut Vec) { let prev_token = std::mem::replace(current_token, String::new()); if prev_token.is_empty() { return } if let Some(token) = self.table.get(&prev_token) { tokens.push(token.clone()); return } match prev_token.as_str() { "if" => { tokens.push(Token::If); return } "else" => { tokens.push(Token::Else); 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; let mut stack = Vec::new(); // equals let mut iter = tokens.into_iter(); let mut var = String::new(); let mut lhs = vec![]; let mut rhs = vec![]; let mut comparison = 0; // 0: none // 1: = while let Some(token) = iter.next() { match token { Token::Equals => { if comparison != 0 { return Err(Error::UnexpectedToken) } comparison = 1; if lhs.len() != 1 { return Err(Error::UnexpectedToken) } let lhs = std::mem::take(&mut lhs); match lhs.into_iter().next().unwrap() { Token::LoadVar(v) => var = v, _ => return Err(Error::UnexpectedToken), } } token => { if comparison == 0 { lhs.push(token); } else { rhs.push(token); } } } } if comparison == 1 { let stack = std::mem::take(&mut rhs); let rpn = to_rpn(stack)?; out = vec![Token::SetValue((var, Box::new(rpn)))]; } else { assert!(rhs.is_empty()); out = lhs; } // Parens let tokens = std::mem::take(&mut out); 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 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); // Braces let tokens = std::mem::take(&mut out); assert!(stack.is_empty()); let mut paren = 0; for token in tokens { match token { Token::LeftBrace => { // Is this the first opening paren for this subexpr? if paren > 0 { stack.push(token); } paren += 1; continue } Token::RightBrace => { 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 rpn = to_rpn(stack)?; out.push(Token::SubExpr(Box::new(rpn))); } else { stack.push(token); } continue } _ => {} } if paren > 0 { stack.push(token); } else { out.push(token); } } out.append(&mut stack); let tokens = std::mem::take(&mut out); assert!(stack.is_empty()); let mut iter = tokens.into_iter(); 'mainloop: while let Some(token) = iter.next() { match token { Token::If => { let mut section = 0; let mut cond_expr = vec![]; let mut if_expr = vec![]; let mut else_expr = vec![]; while let Some(token) = iter.next() { match token { Token::SubExpr(tokens) => { if section == 0 { let cexpr = std::mem::take(&mut cond_expr); cond_expr = to_rpn(cexpr)?; if_expr = *tokens; } else if section == 1 { else_expr = *tokens; } else { return Err(Error::UnexpectedToken) } section += 1; } Token::Else => { if section != 1 { return Err(Error::UnexpectedToken) } } token => { if section != 0 { out.push(Token::IfElse(( Box::new(cond_expr), Box::new(if_expr), Box::new(else_expr), ))); out.push(token); continue 'mainloop; } cond_expr.push(token); } } } // We reached the end out.push(Token::IfElse(( Box::new(cond_expr), Box::new(if_expr), Box::new(else_expr), ))); } token => out.push(token), } } // comparisons <>= let tokens = std::mem::take(&mut out); let mut iter = tokens.into_iter(); let mut lhs = vec![]; let mut rhs = vec![]; let mut comparison = 0; // 0: none // 1: < while let Some(token) = iter.next() { match token { Token::LessThan => { if comparison != 0 { return Err(Error::UnexpectedToken) } comparison = 1; } token => { if comparison == 0 { lhs.push(token); } else { rhs.push(token); } } } } if comparison == 1 { out = vec![Token::LessThanCompare((Box::new(lhs), Box::new(rhs)))]; } else { assert!(rhs.is_empty()); out = lhs; } // */ let tokens = std::mem::take(&mut out); assert!(stack.is_empty()); 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 = std::mem::take(&mut out); 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))) } Token::IfElse((cond, if_val, else_val)) => { let cond = convert(&mut cond.into_iter())?; let if_val = convert(&mut if_val.into_iter())?; let else_val = convert(&mut else_val.into_iter())?; Op::IfElse((Box::new(cond), vec![if_val], vec![else_val])) } Token::LessThanCompare((lhs, rhs)) => { let lhs = convert(&mut lhs.into_iter())?; let rhs = convert(&mut rhs.into_iter())?; Op::LessThan((Box::new(lhs), Box::new(rhs))) } Token::SetValue((var, expr)) => { let expr = convert(&mut expr.into_iter())?; Op::StoreVar((var, Box::new(expr))) } _ => return Err(Error::UnexpectedToken), }; Ok(op) } #[cfg(test)] mod tests { use super::*; #[test] fn single_line() { let compiler = Compiler::new(); let code = compiler.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 = compiler.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); } #[test] fn h_minus_1() { let mut compiler = Compiler::new(); let code = compiler.compile("h - 1").unwrap(); #[rustfmt::skip] let code2 = vec![Op::Sub(( Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(1.)) ))]; assert_eq!(code, code2); } #[test] fn dosub() { let mut compiler = Compiler::new(); compiler.add_const_f32("HELLO", 110.); let code = compiler.compile("HELLO").unwrap(); let code2 = vec![Op::ConstFloat32(110.)]; assert_eq!(code, code2); } #[test] fn if_else() { let mut compiler = Compiler::new(); let code = compiler .compile( " if h < 4 { h - 1 } else { 2 * h + 5 } ", ) .unwrap(); let code2 = vec![Op::IfElse(( Box::new(Op::LessThan(( Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(4.)), ))), vec![Op::Sub((Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(1.))))], vec![Op::Add(( Box::new(Op::Mul(( Box::new(Op::ConstFloat32(2.)), Box::new(Op::LoadVar("h".to_string())), ))), Box::new(Op::ConstFloat32(5.)), ))], ))]; assert_eq!(code, code2); } #[test] fn set_val() { let mut compiler = Compiler::new(); let code = compiler .compile( " r = 10 / 4 ", ) .unwrap(); let code2 = vec![Op::StoreVar(( "r".to_string(), Box::new(Op::Div((Box::new(Op::ConstFloat32(10.)), Box::new(Op::ConstFloat32(4.))))), ))]; assert_eq!(code, code2); } #[test] fn multiline() { let mut compiler = Compiler::new(); let code = compiler .compile( " # This is a comment r = 10 / 4; s = if h < 4 { h - 1 } else { 2 * h + 5 }; r + 1 ", ) .unwrap(); let code2 = vec![ Op::StoreVar(( "r".to_string(), Box::new(Op::Div(( Box::new(Op::ConstFloat32(10.)), Box::new(Op::ConstFloat32(4.)), ))), )), Op::StoreVar(( "s".to_string(), Box::new(Op::IfElse(( Box::new(Op::LessThan(( Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(4.)), ))), vec![Op::Sub(( Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(1.)), ))], vec![Op::Add(( Box::new(Op::Mul(( Box::new(Op::ConstFloat32(2.)), Box::new(Op::LoadVar("h".to_string())), ))), Box::new(Op::ConstFloat32(5.)), ))], ))), )), Op::Add((Box::new(Op::LoadVar("r".to_string())), Box::new(Op::ConstFloat32(1.)))), ]; assert_eq!(code, code2); } }