mech/
lib.rs

1#![allow(warnings)]
2// # Mech
3
4// ## Prelude
5
6pub extern crate mech_core as core;
7pub extern crate mech_syntax as syntax;
8
9pub use mech_core::*;
10use mech_core::nodes::Program;
11pub use mech_interpreter::Interpreter;
12
13extern crate colored;
14use colored::*;
15
16extern crate bincode;
17use std::io::{Write, BufReader, BufWriter, stdout};
18use std::fs::{OpenOptions, File, canonicalize, create_dir};
19use crossterm::{
20  ExecutableCommand, QueueableCommand,
21  terminal, cursor, style::Print,
22};
23
24use tabled::{
25  builder::Builder,
26  grid::config::HorizontalLine,
27  settings::{object::Rows,Panel, Span, Alignment, Modify, Style},
28  Tabled,
29};
30use std::path::{Path, PathBuf};
31use std::io;
32use std::io::prelude::*;
33use std::time::{Duration, Instant, SystemTime};
34use std::thread::{self, JoinHandle};
35use std::sync::Mutex;
36use std::sync::RwLock;
37//use websocket::sync::Server;
38use std::net::{SocketAddr, UdpSocket, TcpListener, TcpStream};
39use std::collections::HashMap;
40use crossbeam_channel::Sender;
41use crossbeam_channel::{unbounded, Receiver};
42use std::{fs,env};
43#[macro_use]
44extern crate lazy_static;
45
46#[cfg(feature = "wasm")]
47use web_sys::{Crypto, Window, console};
48use rand::rngs::OsRng;
49use rand::RngCore;
50use notify::{recommended_watcher, Event, RecursiveMode, Result as NResult, Watcher};
51use std::sync::mpsc;
52use std::sync::Arc;
53use std::collections::HashSet;
54
55lazy_static! {
56  static ref CORE_MAP: Mutex<HashMap<SocketAddr, (String, SystemTime)>> = Mutex::new(HashMap::new());
57}
58
59mod repl;
60mod serve;
61mod run;
62mod mechfs;
63
64pub use self::repl::*;
65pub use self::serve::*;
66pub use self::run::*;
67pub use self::mechfs::*;
68
69pub use mech_core::*;
70pub use mech_syntax::*;
71
72// Print a prompt 
73// 4, 8, 15, 16, 23, 42
74pub fn print_prompt() {
75  stdout().flush();
76  print!("{}", ">: ".truecolor(246,192,78));
77  stdout().flush();
78}
79
80// Generate a new id for creating unique owner ids
81#[cfg(not(feature = "wasm"))]
82pub fn generate_uuid() -> u64 {
83  OsRng.next_u64()
84}
85
86#[cfg(feature = "wasm")]
87pub fn generate_uuid() -> u64 {
88  let mut rng = WebCryptoRng{};
89  rng.next_u64()
90}
91
92pub fn mech_table_style() -> Style<(),(),(),(),(),(),2,0> {
93  Style::empty()
94    .horizontals([
95      (1, HorizontalLine::filled('-').into()),
96      (2, HorizontalLine::filled('-').into()),
97    ])
98}
99
100pub fn help() -> String {
101  let mut builder = Builder::default();
102  builder.push_record(vec!["Command","Short","Parameters","Description"]);
103  builder.push_record(vec![
104    ":help".to_string(),
105    ":h".to_string(),
106    "".to_string(),
107    "Display this help message".to_string()
108  ]);
109  builder.push_record(vec![
110    ":quit".to_string(),
111    ":q".to_string(),
112    "".to_string(),
113    "Quit the REPL".to_string()
114  ]);
115  builder.push_record(vec![
116    ":symbols".to_string(),
117    ":s".to_string(),
118    "[search pattern]".to_string(),
119    "Search symbols".to_string()
120  ]);
121  builder.push_record(vec![
122    ":plan".to_string(),
123    ":p".to_string(),
124    "".to_string(),
125    "Display the plan".to_string()
126  ]);
127  builder.push_record(vec![
128    ":whos".to_string(),
129    ":w".to_string(),
130    "[search pattern]".to_string(),
131    "Search symbol directory".to_string()
132  ]);
133  builder.push_record(vec![
134    ":clc".to_string(),
135    ":c".to_string(),
136    "".to_string(),
137    "Clear the screen".to_string()
138  ]);
139  builder.push_record(vec![
140    ":clear".to_string(),
141    "".to_string(),
142    "[target variable]".to_string(),
143    "Clear the interpreter state".to_string()
144  ]);
145  builder.push_record(vec![
146    ":load".to_string(),
147    "".to_string(),
148    "[file path]".to_string(),
149    "Load a file".to_string()
150  ]);
151  builder.push_record(vec![
152    ":ls".to_string(),
153    "".to_string(),
154    "[target path]".to_string(),
155    "List directory contents".to_string()
156  ]);
157  builder.push_record(vec![
158    ":cd".to_string(),
159    "".to_string(),
160    "[target path]".to_string(),
161    "Change directory".to_string()
162  ]);
163  builder.push_record(vec![
164    ":step".to_string(),
165    "".to_string(),
166    "[step count]".to_string(),
167    "Iterate plan".to_string()
168  ]);
169  let mut table = builder.build();
170  table.with(mech_table_style())
171       .with(Panel::header(format!("{}","🧭 Help".truecolor(0xdf,0xb9,0x9f))));
172  format!("\n{table}\n")
173}
174
175
176pub fn ls() -> String {
177  let current_dir = env::current_dir().unwrap();
178  let mut builder = Builder::default();
179  builder.push_record(vec!["Mode","Last Write Time","Length","Name"]);
180  for entry in fs::read_dir("./").unwrap() {
181    let entry = entry.unwrap();
182    let path = entry.path();
183    let metadata = fs::metadata(&path).unwrap();
184    let file_type = if metadata.is_dir() { "d----" } else { "-a---" };
185    let last_write_time = metadata.modified().unwrap();
186    let last_write_time: chrono::DateTime<chrono::Local> = last_write_time.into();
187    let length = if metadata.is_file() { metadata.len().to_string() } else { "".to_string() };
188    let name = format!("{}", path.file_name().unwrap().to_str().unwrap());
189    builder.push_record(vec![file_type.to_string(), last_write_time.format("%m/%d/%Y %I:%M %p").to_string(), length, name.to_string()]);
190  }
191  let mut table = builder.build();
192  table.with(mech_table_style())
193       .with(Panel::header(format!("{}","📂 Directory Listing".truecolor(0xdf,0xb9,0x9f))));
194  format!("\nDirectory: {}\n\n{table}\n",current_dir.display())
195}
196
197pub fn pretty_print_tree(tree: &Program) -> String {
198  let tree_hash = hash_str(&format!("{:#?}", tree));
199  let formatted_tree = format_parse_tree(tree);
200  let mut builder = Builder::default();
201  builder.push_record(vec![format!("Hash: {}", tree_hash)]);
202  builder.push_record(vec![format!("{}", formatted_tree)]);
203  let mut table = builder.build();
204  table.with(Style::modern_rounded())
205       .with(Panel::header("🌳 Syntax Tree"));
206  format!("{table}")
207}
208
209pub fn whos(intrp: &Interpreter) -> String {
210  let mut builder = Builder::default();
211  builder.push_record(vec!["Name","Size","Bytes","Kind"]);
212  let dictionary = intrp.dictionary();
213  for (id,name) in dictionary.borrow().iter() {
214    let value = intrp.get_symbol(*id).unwrap();
215    let value_brrw = value.borrow();
216    builder.push_record(vec![
217      name.clone(),
218      format!("{:?}",value_brrw.shape()),
219      format!("{:?}",value_brrw.size_of()),
220      format!("{:?}",value_brrw.kind()),
221    ]);
222  }
223
224  let mut table = builder.build();
225  table.with(mech_table_style())   
226        .with(Panel::header(format!("{}","🔍 Whos".truecolor(0xdf,0xb9,0x9f))));
227  format!("\n{table}\n")
228}
229
230pub fn pretty_print_symbols(intrp: &Interpreter) -> String {
231  let mut builder = Builder::default();
232  let symbol_table = intrp.pretty_print_symbols();
233  builder.push_record(vec![
234    format!("{}",symbol_table),
235  ]);
236
237  let mut table = builder.build();
238  table.with(mech_table_style())   
239        .with(Panel::header(format!("{}","🔣 Symbols".truecolor(0xdf,0xb9,0x9f))));
240  format!("\n{table}\n")
241}
242
243pub fn clc() {
244  let mut stdo = stdout();
245  stdo.execute(terminal::Clear(terminal::ClearType::All));
246  stdo.execute(cursor::MoveTo(0,0));
247}
248
249pub fn pretty_print_plan(intrp: &Interpreter) -> String {
250  let mut builder = Builder::default();
251
252  let mut row = vec![];
253  let plan = intrp.plan();
254  let plan_brrw = plan.borrow();
255  if plan_brrw.is_empty() {
256    builder.push_record(vec!["".to_string()]);
257  } else {
258    for (ix, fxn) in plan_brrw.iter().enumerate() {
259      let plan_str = format!("{}. {}\n", ix + 1, fxn.to_string());
260      row.push(plan_str.clone());
261      if row.len() == 4 {
262        builder.push_record(row.clone());
263        row.clear();
264      }
265    }
266  }
267  if row.is_empty() == false {
268    builder.push_record(row.clone());
269  }
270  let mut table = builder.build();
271  table.with(Style::modern_rounded())
272       .with(Panel::header("📋 Plan"));
273  format!("{table}")
274}
275
276pub fn format_parse_tree(program: &Program) -> String {
277  let json_string = serde_json::to_string_pretty(&program).unwrap();
278
279  let depth = |line: &str|->usize{line.chars().take_while(|&c| c == ' ').count()};
280  let mut result = String::new();
281  let lines: Vec<&str> = json_string.lines().collect();
282  result.push_str("Program\n");
283  for (index, line) in lines.iter().enumerate() {
284    let trm = line.trim();
285    if trm == "}" || 
286       trm == "},"|| 
287       trm == "{" || 
288       trm == "[" || 
289       trm == "],"|| 
290       trm == "]" {
291      continue;
292    }
293
294    // Count leading spaces to determine depth
295    let d = depth(line);
296    // Construct the tree-like prefix
297    let mut prefix = String::new();
298    for _ in 0..d {
299      prefix.push_str(" ");
300    }
301    if index == lines.len() {
302      prefix.push_str("└ ");
303    } else {
304      if depth(lines[index + 1]) != d {
305        prefix.push_str("└ ");
306      } else {
307        prefix.push_str("├ ");
308      }
309    }
310    let trm = line.trim();
311    let trm = trm.trim_end_matches('{')
312                  .trim_start_matches('"')
313                  .trim_end_matches(':')
314                  .trim_end_matches('"')
315                  .trim_end_matches('[');
316    prefix.push_str(trm);
317
318    // Append formatted line to result
319    result.push_str(&prefix);
320    result.push('\n');
321    result = result.replace("\":", "");
322  }
323  let mut indexed_str = IndexedString::new(&result);
324  'rows: for i in 0..indexed_str.rows {
325    let rowz = &indexed_str.index_map[i];
326    for j in 0..rowz.len() {
327      let c = match indexed_str.get(i,j) {
328        Some(c) => c,
329        None => continue,
330      };
331      if c == '└' {
332        for k in i+1..indexed_str.rows {
333          match indexed_str.get(k,j) {
334            Some(c2) => {
335              if c2 == '└' {
336                indexed_str.set(i,j,'├');
337                for l in i+1..k {
338                  match indexed_str.get(l,j) {
339                    Some(' ') => {indexed_str.set(l,j,'│');},
340                    Some('└') => {indexed_str.set(l,j,'├');},
341                    _ => (),
342                  }
343                }
344              } else if c2 == ' ' {
345                continue;
346              } else {
347                continue 'rows;
348              }
349            },
350            None => continue,
351          }
352
353        }
354      } else if c == ' ' || c == '│' {
355        continue;
356      } else {
357        continue 'rows;
358      }
359    }
360  }
361  indexed_str.to_string()
362}
363
364#[derive(Clone, Debug, PartialEq, Eq)]
365struct IndexedString {
366  pub data: Vec<char>,
367  pub index_map: Vec<Vec<usize>>,
368  pub rows: usize,
369  pub cols: usize,
370}
371
372impl IndexedString {
373  
374  fn new(input: &str) -> Self {
375      let mut data = Vec::new();
376      let mut index_map = Vec::new();
377      let mut current_row = 0;
378      let mut current_col = 0;
379      index_map.push(Vec::new());
380      for c in input.chars() {
381        data.push(c);
382        if c == '\n' {
383          index_map.push(Vec::new());
384          current_row += 1;
385          current_col = 0;
386        } else {
387          index_map[current_row].push(data.len() - 1);
388          current_col += 1;
389        }
390      }
391      let rows = index_map.len();
392      let cols = if rows > 0 { index_map[0].len() } else { 0 };
393      IndexedString {
394          data,
395          index_map,
396          rows,
397          cols,
398      }
399  }
400
401  fn to_string(&self) -> String {
402    self.data.iter().collect()
403  }
404
405  fn get(&self, row: usize, col: usize) -> Option<char> {
406    if row < self.rows {
407      let rowz = &self.index_map[row];
408      if col < rowz.len() {
409        let index = self.index_map[row][col];
410        Some(self.data[index])
411      } else {
412        None
413      }
414    } else {
415      None
416    }
417  }
418
419  fn set(&mut self, row: usize, col: usize, new_char: char) -> Result<(), String> {
420    if row < self.rows {
421      let row_indices = &mut self.index_map[row];
422      if col < row_indices.len() {
423        let index = row_indices[col];
424        self.data[index] = new_char;
425        Ok(())
426      } else {
427        Err("Column index out of bounds".to_string())
428      }
429    } else {
430      Err("Row index out of bounds".to_string())
431    }
432  }
433}