/ bin / app / src / expr / compile.rs
compile.rs
  1  /* This file is part of DarkFi (https://dark.fi)
  2   *
  3   * Copyright (C) 2020-2025 Dyne.org foundation
  4   *
  5   * This program is free software: you can redistribute it and/or modify
  6   * it under the terms of the GNU Affero General Public License as
  7   * published by the Free Software Foundation, either version 3 of the
  8   * License, or (at your option) any later version.
  9   *
 10   * This program is distributed in the hope that it will be useful,
 11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13   * GNU Affero General Public License for more details.
 14   *
 15   * You should have received a copy of the GNU Affero General Public License
 16   * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17   */
 18  
 19  use crate::{
 20      error::{Error, Result},
 21      //prop::{Property, PropertySubType, PropertyType, PropertySExprValue},
 22  };
 23  use std::collections::HashMap;
 24  
 25  use super::{Op, SExprCode};
 26  
 27  #[derive(Debug, Clone)]
 28  enum Token {
 29      LoadVar(String),
 30      Add,
 31      Sub,
 32      Mul,
 33      Div,
 34      LeftParen,
 35      RightParen,
 36      ConstFloat32(f32),
 37      NestedExpr(Box<Vec<Token>>),
 38      SubExpr(Box<Vec<Token>>),
 39      If,
 40      Else,
 41      LeftBrace,
 42      RightBrace,
 43      LessThan,
 44      IfElse((Box<Vec<Token>>, Box<Vec<Token>>, Box<Vec<Token>>)),
 45      LessThanCompare((Box<Vec<Token>>, Box<Vec<Token>>)),
 46      Equals,
 47      SetValue((String, Box<Vec<Token>>)),
 48  }
 49  
 50  impl Token {
 51      fn flatten(self) -> Vec<Self> {
 52          match self {
 53              Self::NestedExpr(tokens) => {
 54                  let tokens: Vec<_> = *tokens;
 55                  tokens.into_iter().map(|t| t.flatten()).flatten().collect()
 56              }
 57              _ => vec![self],
 58          }
 59      }
 60  }
 61  
 62  #[derive(Clone)]
 63  pub struct Compiler {
 64      table: HashMap<String, Token>,
 65  }
 66  
 67  impl Compiler {
 68      pub fn new() -> Self {
 69          Self { table: HashMap::new() }
 70      }
 71  
 72      pub fn add_const_f32<S: Into<String>>(&mut self, name: S, val: f32) {
 73          self.table.insert(name.into(), Token::ConstFloat32(val));
 74      }
 75      /*
 76      pub fn get_const_f32<S: AsRef<str>>(&self, name: S) -> Option<f32> {
 77          let name = name.as_ref();
 78          let val = self.table.get(name)?;
 79          match val {
 80              Token::ConstFloat32(v) => Some(v)
 81              _ => None
 82          }
 83      }
 84      */
 85  
 86      pub fn compile<S: AsRef<str>>(&self, prestmts: S) -> Result<SExprCode> {
 87          let prestmts = prestmts.as_ref();
 88          // Strip all comments
 89          let mut stmts = String::new();
 90          for line in prestmts.lines() {
 91              if let Some(chr) = line.trim_start().chars().next() {
 92                  if chr != '#' {
 93                      stmts.push_str(line);
 94                  }
 95              }
 96          }
 97  
 98          let mut code = vec![];
 99          for stmt in stmts.split(';') {
100              code.push(self.compile_line(stmt)?);
101          }
102          Ok(code)
103      }
104  
105      fn compile_line(&self, stmt: &str) -> Result<Op> {
106          let tokens = self.tokenize(&stmt);
107          //println!("{tokens:#?}");
108          let tokens = to_rpn(tokens)?;
109          //println!("{tokens:#?}");
110          Ok(convert(&mut tokens.into_iter())?)
111      }
112  
113      fn tokenize(&self, stmt: &str) -> Vec<Token> {
114          let mut tokens = Vec::new();
115          let mut current_token = String::new();
116          for chr in stmt.chars() {
117              match chr {
118                  ' ' | '\t' | '\n' => {
119                      self.clear_accum(&mut current_token, &mut tokens);
120                  }
121                  '+' => {
122                      self.clear_accum(&mut current_token, &mut tokens);
123                      tokens.push(Token::Add);
124                  }
125                  '-' => {
126                      self.clear_accum(&mut current_token, &mut tokens);
127                      tokens.push(Token::Sub);
128                  }
129                  '*' => {
130                      self.clear_accum(&mut current_token, &mut tokens);
131                      tokens.push(Token::Mul);
132                  }
133                  '/' => {
134                      self.clear_accum(&mut current_token, &mut tokens);
135                      tokens.push(Token::Div);
136                  }
137                  '(' => {
138                      self.clear_accum(&mut current_token, &mut tokens);
139                      tokens.push(Token::LeftParen);
140                  }
141                  ')' => {
142                      self.clear_accum(&mut current_token, &mut tokens);
143                      tokens.push(Token::RightParen);
144                  }
145                  '{' => {
146                      self.clear_accum(&mut current_token, &mut tokens);
147                      tokens.push(Token::LeftBrace);
148                  }
149                  '}' => {
150                      self.clear_accum(&mut current_token, &mut tokens);
151                      tokens.push(Token::RightBrace);
152                  }
153                  '<' => {
154                      self.clear_accum(&mut current_token, &mut tokens);
155                      tokens.push(Token::LessThan);
156                  }
157                  '=' => {
158                      self.clear_accum(&mut current_token, &mut tokens);
159                      tokens.push(Token::Equals);
160                  }
161                  _ => current_token.push(chr),
162              }
163          }
164          self.clear_accum(&mut current_token, &mut tokens);
165          tokens
166      }
167  
168      fn clear_accum(&self, current_token: &mut String, tokens: &mut Vec<Token>) {
169          let prev_token = std::mem::replace(current_token, String::new());
170          if prev_token.is_empty() {
171              return
172          }
173  
174          if let Some(token) = self.table.get(&prev_token) {
175              tokens.push(token.clone());
176              return
177          }
178  
179          match prev_token.as_str() {
180              "if" => {
181                  tokens.push(Token::If);
182                  return
183              }
184              "else" => {
185                  tokens.push(Token::Else);
186                  return
187              }
188              _ => {}
189          }
190  
191          // Number or var?
192          match prev_token.parse::<f32>() {
193              Ok(v) => tokens.push(Token::ConstFloat32(v)),
194              Err(_) => tokens.push(Token::LoadVar(prev_token)),
195          }
196      }
197  }
198  
199  /// Convert from infix to reverse polish notation
200  fn to_rpn(tokens: Vec<Token>) -> Result<Vec<Token>> {
201      //println!("to_rpn = {tokens:#?}");
202      let mut out;
203      let mut stack = Vec::new();
204  
205      // equals
206      let mut iter = tokens.into_iter();
207      let mut var = String::new();
208      let mut lhs = vec![];
209      let mut rhs = vec![];
210      let mut comparison = 0;
211      // 0: none
212      // 1: =
213      while let Some(token) = iter.next() {
214          match token {
215              Token::Equals => {
216                  if comparison != 0 {
217                      return Err(Error::UnexpectedToken)
218                  }
219                  comparison = 1;
220                  if lhs.len() != 1 {
221                      return Err(Error::UnexpectedToken)
222                  }
223                  let lhs = std::mem::take(&mut lhs);
224                  match lhs.into_iter().next().unwrap() {
225                      Token::LoadVar(v) => var = v,
226                      _ => return Err(Error::UnexpectedToken),
227                  }
228              }
229              token => {
230                  if comparison == 0 {
231                      lhs.push(token);
232                  } else {
233                      rhs.push(token);
234                  }
235              }
236          }
237      }
238      if comparison == 1 {
239          let stack = std::mem::take(&mut rhs);
240          let rpn = to_rpn(stack)?;
241          out = vec![Token::SetValue((var, Box::new(rpn)))];
242      } else {
243          assert!(rhs.is_empty());
244          out = lhs;
245      }
246  
247      // Parens
248      let tokens = std::mem::take(&mut out);
249      let mut paren = 0;
250      for token in tokens {
251          match token {
252              Token::LeftParen => {
253                  // Is this the first opening paren for this subexpr?
254                  if paren > 0 {
255                      stack.push(token);
256                  }
257                  paren += 1;
258                  continue
259              }
260              Token::RightParen => {
261                  paren -= 1;
262  
263                  // Whoops non-matching number of parens!
264                  if paren < 0 {
265                      return Err(Error::UnexpectedToken)
266                  }
267  
268                  // Did we finally reach the closing paren for this subexpr?
269                  if paren == 0 {
270                      let stack = std::mem::take(&mut stack);
271                      let rpn = to_rpn(stack)?;
272                      out.push(Token::NestedExpr(Box::new(rpn)));
273                  } else {
274                      stack.push(token);
275                  }
276  
277                  continue
278              }
279              _ => {}
280          }
281          if paren > 0 {
282              stack.push(token);
283          } else {
284              out.push(token);
285          }
286      }
287      out.append(&mut stack);
288  
289      // Braces
290      let tokens = std::mem::take(&mut out);
291      assert!(stack.is_empty());
292      let mut paren = 0;
293      for token in tokens {
294          match token {
295              Token::LeftBrace => {
296                  // Is this the first opening paren for this subexpr?
297                  if paren > 0 {
298                      stack.push(token);
299                  }
300                  paren += 1;
301                  continue
302              }
303              Token::RightBrace => {
304                  paren -= 1;
305  
306                  // Whoops non-matching number of parens!
307                  if paren < 0 {
308                      return Err(Error::UnexpectedToken)
309                  }
310  
311                  // Did we finally reach the closing paren for this subexpr?
312                  if paren == 0 {
313                      let stack = std::mem::take(&mut stack);
314                      let rpn = to_rpn(stack)?;
315                      out.push(Token::SubExpr(Box::new(rpn)));
316                  } else {
317                      stack.push(token);
318                  }
319  
320                  continue
321              }
322              _ => {}
323          }
324          if paren > 0 {
325              stack.push(token);
326          } else {
327              out.push(token);
328          }
329      }
330      out.append(&mut stack);
331  
332      let tokens = std::mem::take(&mut out);
333      assert!(stack.is_empty());
334      let mut iter = tokens.into_iter();
335      'mainloop: while let Some(token) = iter.next() {
336          match token {
337              Token::If => {
338                  let mut section = 0;
339                  let mut cond_expr = vec![];
340                  let mut if_expr = vec![];
341                  let mut else_expr = vec![];
342                  while let Some(token) = iter.next() {
343                      match token {
344                          Token::SubExpr(tokens) => {
345                              if section == 0 {
346                                  let cexpr = std::mem::take(&mut cond_expr);
347                                  cond_expr = to_rpn(cexpr)?;
348  
349                                  if_expr = *tokens;
350                              } else if section == 1 {
351                                  else_expr = *tokens;
352                              } else {
353                                  return Err(Error::UnexpectedToken)
354                              }
355  
356                              section += 1;
357                          }
358                          Token::Else => {
359                              if section != 1 {
360                                  return Err(Error::UnexpectedToken)
361                              }
362                          }
363                          token => {
364                              if section != 0 {
365                                  out.push(Token::IfElse((
366                                      Box::new(cond_expr),
367                                      Box::new(if_expr),
368                                      Box::new(else_expr),
369                                  )));
370                                  out.push(token);
371                                  continue 'mainloop;
372                              }
373                              cond_expr.push(token);
374                          }
375                      }
376                  }
377                  // We reached the end
378                  out.push(Token::IfElse((
379                      Box::new(cond_expr),
380                      Box::new(if_expr),
381                      Box::new(else_expr),
382                  )));
383              }
384              token => out.push(token),
385          }
386      }
387  
388      // comparisons <>=
389      let tokens = std::mem::take(&mut out);
390      let mut iter = tokens.into_iter();
391      let mut lhs = vec![];
392      let mut rhs = vec![];
393      let mut comparison = 0;
394      // 0: none
395      // 1: <
396      while let Some(token) = iter.next() {
397          match token {
398              Token::LessThan => {
399                  if comparison != 0 {
400                      return Err(Error::UnexpectedToken)
401                  }
402                  comparison = 1;
403              }
404              token => {
405                  if comparison == 0 {
406                      lhs.push(token);
407                  } else {
408                      rhs.push(token);
409                  }
410              }
411          }
412      }
413      if comparison == 1 {
414          out = vec![Token::LessThanCompare((Box::new(lhs), Box::new(rhs)))];
415      } else {
416          assert!(rhs.is_empty());
417          out = lhs;
418      }
419  
420      // */
421      let tokens = std::mem::take(&mut out);
422      assert!(stack.is_empty());
423      let mut is_op = false;
424      for token in tokens {
425          match token {
426              Token::Mul | Token::Div => {
427                  if is_op {
428                      return Err(Error::UnexpectedToken)
429                  }
430                  is_op = true;
431  
432                  let Some(prev_item) = out.pop() else { return Err(Error::UnexpectedToken) };
433                  stack.push(token);
434                  stack.push(prev_item);
435              }
436              _ => {
437                  if is_op {
438                      is_op = false;
439                      let mut expr = std::mem::take(&mut stack);
440                      expr.push(token);
441                      out.push(Token::NestedExpr(Box::new(expr)));
442  
443                      continue
444                  }
445  
446                  out.push(token)
447              }
448          }
449      }
450      //println!("out = {out:#?}");
451      //println!("stack = {stack:#?}");
452      //assert!(!is_op);
453      //assert!(stack.is_empty());
454      if is_op || !stack.is_empty() {
455          return Err(Error::UnexpectedToken)
456      }
457  
458      // +-
459      let tokens = std::mem::take(&mut out);
460      let mut is_op = false;
461      for token in tokens {
462          match token {
463              Token::Add | Token::Sub => {
464                  if is_op {
465                      return Err(Error::UnexpectedToken)
466                  }
467                  is_op = true;
468  
469                  let Some(prev_item) = out.pop() else { return Err(Error::UnexpectedToken) };
470                  stack.push(token);
471                  stack.push(prev_item);
472              }
473              _ => {
474                  if is_op {
475                      is_op = false;
476                      let mut expr = std::mem::take(&mut stack);
477                      expr.push(token);
478                      out.push(Token::NestedExpr(Box::new(expr)));
479  
480                      continue
481                  }
482  
483                  out.push(token)
484              }
485          }
486      }
487      //assert!(!is_op);
488      //assert!(stack.is_empty());
489      if is_op || !stack.is_empty() {
490          return Err(Error::UnexpectedToken)
491      }
492  
493      // Flatten everything
494      let out = out.into_iter().map(|t| t.flatten()).flatten().collect();
495      Ok(out)
496  }
497  
498  fn convert<I: Iterator<Item = Token>>(iter: &mut I) -> Result<Op> {
499      let Some(token) = iter.next() else { return Err(Error::UnexpectedToken) };
500  
501      let op = match token {
502          Token::ConstFloat32(v) => Op::ConstFloat32(v),
503          Token::LoadVar(v) => Op::LoadVar(v),
504          Token::Add => {
505              let lhs = convert(iter)?;
506              let rhs = convert(iter)?;
507              Op::Add((Box::new(lhs), Box::new(rhs)))
508          }
509          Token::Sub => {
510              let lhs = convert(iter)?;
511              let rhs = convert(iter)?;
512              Op::Sub((Box::new(lhs), Box::new(rhs)))
513          }
514          Token::Mul => {
515              let lhs = convert(iter)?;
516              let rhs = convert(iter)?;
517              Op::Mul((Box::new(lhs), Box::new(rhs)))
518          }
519          Token::Div => {
520              let lhs = convert(iter)?;
521              let rhs = convert(iter)?;
522              Op::Div((Box::new(lhs), Box::new(rhs)))
523          }
524          Token::IfElse((cond, if_val, else_val)) => {
525              let cond = convert(&mut cond.into_iter())?;
526              let if_val = convert(&mut if_val.into_iter())?;
527              let else_val = convert(&mut else_val.into_iter())?;
528              Op::IfElse((Box::new(cond), vec![if_val], vec![else_val]))
529          }
530          Token::LessThanCompare((lhs, rhs)) => {
531              let lhs = convert(&mut lhs.into_iter())?;
532              let rhs = convert(&mut rhs.into_iter())?;
533              Op::LessThan((Box::new(lhs), Box::new(rhs)))
534          }
535          Token::SetValue((var, expr)) => {
536              let expr = convert(&mut expr.into_iter())?;
537              Op::StoreVar((var, Box::new(expr)))
538          }
539          _ => return Err(Error::UnexpectedToken),
540      };
541      Ok(op)
542  }
543  
544  #[cfg(test)]
545  mod tests {
546      use super::*;
547  
548      #[test]
549      fn single_line() {
550          let compiler = Compiler::new();
551  
552          let code = compiler.compile("h/2 - 200").unwrap();
553          #[rustfmt::skip]
554          let code2 = vec![Op::Sub((
555              Box::new(Op::Div((
556                  Box::new(Op::LoadVar("h".to_string())),
557                  Box::new(Op::ConstFloat32(2.)),
558              ))),
559              Box::new(Op::ConstFloat32(200.)),
560          ))];
561          assert_eq!(code, code2);
562  
563          let code = compiler.compile("(x + h/2 + (y + 7)/5) - 200").unwrap();
564          #[rustfmt::skip]
565          let code2 = vec![Op::Sub((
566              Box::new(Op::Add((
567                  Box::new(Op::Add((
568                      Box::new(Op::LoadVar("x".to_string())),
569                      Box::new(Op::Div((
570                          Box::new(Op::LoadVar("h".to_string())),
571                          Box::new(Op::ConstFloat32(2.))
572                      ))),
573                  ))),
574  
575                  Box::new(Op::Div((
576                      Box::new(Op::Add((
577                          Box::new(Op::LoadVar("y".to_string())),
578                          Box::new(Op::ConstFloat32(7.))
579                      ))),
580                      Box::new(Op::ConstFloat32(5.))
581                  ))),
582              ))),
583  
584              Box::new(Op::ConstFloat32(200.))
585          ))];
586          assert_eq!(code, code2);
587      }
588  
589      #[test]
590      fn h_minus_1() {
591          let mut compiler = Compiler::new();
592          let code = compiler.compile("h - 1").unwrap();
593          #[rustfmt::skip]
594          let code2 = vec![Op::Sub((
595              Box::new(Op::LoadVar("h".to_string())),
596              Box::new(Op::ConstFloat32(1.))
597          ))];
598          assert_eq!(code, code2);
599      }
600  
601      #[test]
602      fn dosub() {
603          let mut compiler = Compiler::new();
604          compiler.add_const_f32("HELLO", 110.);
605  
606          let code = compiler.compile("HELLO").unwrap();
607          let code2 = vec![Op::ConstFloat32(110.)];
608          assert_eq!(code, code2);
609      }
610  
611      #[test]
612      fn if_else() {
613          let mut compiler = Compiler::new();
614          let code = compiler
615              .compile(
616                  "
617              if h < 4 {
618                  h - 1
619              } else {
620                  2 * h + 5
621              }
622          ",
623              )
624              .unwrap();
625          let code2 = vec![Op::IfElse((
626              Box::new(Op::LessThan((
627                  Box::new(Op::LoadVar("h".to_string())),
628                  Box::new(Op::ConstFloat32(4.)),
629              ))),
630              vec![Op::Sub((Box::new(Op::LoadVar("h".to_string())), Box::new(Op::ConstFloat32(1.))))],
631              vec![Op::Add((
632                  Box::new(Op::Mul((
633                      Box::new(Op::ConstFloat32(2.)),
634                      Box::new(Op::LoadVar("h".to_string())),
635                  ))),
636                  Box::new(Op::ConstFloat32(5.)),
637              ))],
638          ))];
639          assert_eq!(code, code2);
640      }
641  
642      #[test]
643      fn set_val() {
644          let mut compiler = Compiler::new();
645          let code = compiler
646              .compile(
647                  "
648              r = 10 / 4
649          ",
650              )
651              .unwrap();
652          let code2 = vec![Op::StoreVar((
653              "r".to_string(),
654              Box::new(Op::Div((Box::new(Op::ConstFloat32(10.)), Box::new(Op::ConstFloat32(4.))))),
655          ))];
656          assert_eq!(code, code2);
657      }
658  
659      #[test]
660      fn multiline() {
661          let mut compiler = Compiler::new();
662          let code = compiler
663              .compile(
664                  "
665              # This is a comment
666              r = 10 / 4;
667  
668              s = if h < 4 {
669                  h - 1
670              } else {
671                  2 * h + 5
672              };
673  
674              r + 1
675          ",
676              )
677              .unwrap();
678          let code2 = vec![
679              Op::StoreVar((
680                  "r".to_string(),
681                  Box::new(Op::Div((
682                      Box::new(Op::ConstFloat32(10.)),
683                      Box::new(Op::ConstFloat32(4.)),
684                  ))),
685              )),
686              Op::StoreVar((
687                  "s".to_string(),
688                  Box::new(Op::IfElse((
689                      Box::new(Op::LessThan((
690                          Box::new(Op::LoadVar("h".to_string())),
691                          Box::new(Op::ConstFloat32(4.)),
692                      ))),
693                      vec![Op::Sub((
694                          Box::new(Op::LoadVar("h".to_string())),
695                          Box::new(Op::ConstFloat32(1.)),
696                      ))],
697                      vec![Op::Add((
698                          Box::new(Op::Mul((
699                              Box::new(Op::ConstFloat32(2.)),
700                              Box::new(Op::LoadVar("h".to_string())),
701                          ))),
702                          Box::new(Op::ConstFloat32(5.)),
703                      ))],
704                  ))),
705              )),
706              Op::Add((Box::new(Op::LoadVar("r".to_string())), Box::new(Op::ConstFloat32(1.)))),
707          ];
708          assert_eq!(code, code2);
709      }
710  }