funki_templates/
lib.rs

1extern crate core;
2#[macro_use]
3extern crate lalrpop_util;
4
5use std::cmp;
6use std::collections::HashMap;
7use std::fmt::{Debug, Formatter};
8
9use itertools::Itertools;
10use lalrpop_util::ParseError;
11
12use crate::ast::{ParserState, Template};
13use crate::data_types::{InterpretError, InterpretVal};
14use crate::external_operators::{
15  CustomBinOp, CustomBuiltIn, CustomType, CustomUnaryOp, OperatorChars,
16};
17use crate::interpreter::{interpret, Customs};
18use crate::parser::language_definition::TemplateParser;
19
20mod ast;
21mod data_types;
22mod interpreter;
23mod parser;
24mod test;
25
26pub mod external_operators;
27
28/// Represents a language to be parsed
29pub struct Language<C: CustomType> {
30  unary_operators: HashMap<OperatorChars, CustomUnaryOp<C>>,
31  binary_operators: HashMap<OperatorChars, CustomBinOp<C>>,
32  built_ins: HashMap<String, CustomBuiltIn<C>>,
33}
34
35/// Represents a set of template functions
36#[derive(Debug)]
37pub struct ParsedTemplate<C: CustomType> {
38  lang: String,
39  temp: Template,
40  unary_operators: HashMap<OperatorChars, CustomUnaryOp<C>>,
41  binary_operators: HashMap<OperatorChars, CustomBinOp<C>>,
42  built_ins: HashMap<String, CustomBuiltIn<C>>,
43}
44
45/// Represents an argument being parsed in to a function call
46pub enum Argument<C: CustomType> {
47  Int(i32),
48  String(String),
49  Tuple(Vec<Argument<C>>),
50  Custom(C),
51}
52
53impl<C: CustomType> Clone for Argument<C> {
54  fn clone(&self) -> Self {
55    match &self {
56      Argument::Int(i) => Argument::Int(*i),
57      Argument::String(s) => Argument::String(s.clone()),
58      Argument::Tuple(t) => Argument::Tuple(t.clone()),
59      Argument::Custom(c) => Argument::Custom(c.clone()),
60    }
61  }
62}
63
64/// Represents a function from a template
65/// Can contain an argument also
66pub struct LangFunc<'a, C: CustomType> {
67  lang: &'a ParsedTemplate<C>,
68  name: String,
69  arg: Option<Argument<C>>,
70  text: String,
71}
72
73impl<C: CustomType> Default for Language<C> {
74  fn default() -> Self {
75    Self::new()
76  }
77}
78
79impl<C: CustomType> Language<C> {
80  pub fn new() -> Self {
81    Self {
82      unary_operators: Default::default(),
83      binary_operators: Default::default(),
84      built_ins: Default::default(),
85    }
86  }
87
88  pub fn add_bin_op(&mut self, char: OperatorChars, op: CustomBinOp<C>) -> &Self {
89    self.binary_operators.entry(char).or_insert(op);
90    self
91  }
92
93  pub fn add_unary_op(&mut self, char: OperatorChars, op: CustomUnaryOp<C>) -> &Self {
94    self.unary_operators.entry(char).or_insert(op);
95    self
96  }
97
98  pub fn add_custom_function(&mut self, name: String, func: CustomBuiltIn<C>) -> &Self {
99    self.built_ins.entry(name).or_insert(func);
100    self
101  }
102
103  pub fn parse(&self, code: String) -> Result<ParsedTemplate<C>, LanguageErr> {
104    let parser = TemplateParser::new();
105    let parser_state = ParserState {
106      unary_ops: self.unary_operators.keys().cloned().collect(),
107      binary_ops: self.binary_operators.keys().cloned().collect(),
108    };
109    let res: Result<Template, ParseError<usize, _, (usize, String, usize)>> =
110      parser.parse(&parser_state, &code);
111
112    match res {
113      Ok(l) => Ok(ParsedTemplate {
114        temp: l,
115        lang: code,
116        unary_operators: self.unary_operators.clone(),
117        binary_operators: self.binary_operators.clone(),
118        built_ins: self.built_ins.clone(),
119      }),
120      Err(e) => Err(LanguageErr::new_from_parser_err(
121        e.map_token(|_| "".to_string()),
122        code,
123      )),
124    }
125  }
126}
127
128impl<C: CustomType> ParsedTemplate<C> {
129  /// Builds a language from a code string
130  ///
131  /// ## Example
132  /// ```
133  /// use funki_templates::{ParsedTemplate, BlankCustom};
134  /// let x = ParsedTemplate::<BlankCustom>::from_text("#main x -> x + 1;");
135  /// ```
136  pub fn from_text(lang: &str) -> Result<Self, LanguageErr> {
137    let parser: TemplateParser = TemplateParser::new();
138    let res = parser.parse(&ParserState::new(), lang);
139    match res {
140      Ok(l) => Ok(Self {
141        temp: l,
142        lang: lang.to_string(),
143        unary_operators: Default::default(),
144        binary_operators: Default::default(),
145        built_ins: Default::default(),
146      }),
147      Err(e) => {
148        // println!("{}", e);
149        Err(LanguageErr::new_from_parser_err(
150          e.map_token(|_| "".to_string()),
151          lang.to_string(),
152        ))
153      }
154    }
155  }
156
157  /// Lists the available functions
158  ///
159  /// ## Example
160  /// ```
161  /// use funki_templates::{ParsedTemplate, BlankCustom};
162  /// let x = ParsedTemplate::<BlankCustom>::from_text("#main x -> x + 1;").unwrap();
163  /// x.list(); // -> ["main"]
164  /// ```
165  pub fn list(&self) -> Vec<String> {
166    return self.temp.env.keys().map(|s| s.to_string()).collect();
167  }
168
169  /// Selects a function from the template
170  ///
171  /// ## Example
172  /// ```
173  /// use funki_templates::{ParsedTemplate, BlankCustom};
174  /// let x = ParsedTemplate::<BlankCustom>::from_text("#main x -> x + 1;").unwrap();
175  /// let f = x.function("main");
176  /// ```
177  pub fn function(&self, name: &str) -> Result<LangFunc<C>, LanguageErr> {
178    if self.temp.env.contains_key(name) {
179      Ok(LangFunc {
180        lang: self,
181        name: name.to_string(),
182        arg: None,
183        text: self.lang.clone(),
184      })
185    } else {
186      Err(LanguageErr::new_no_loc(format!(
187        "Cannot find function \"{}\".",
188        name
189      )))
190    }
191  }
192}
193
194/// Type for the values returned from the interpretation
195pub enum ReturnVal<T> {
196  String(String),
197  Int(i32),
198  Bool(bool),
199  Tuple(Vec<ReturnVal<T>>),
200  List(Vec<ReturnVal<T>>),
201  Custom(T),
202}
203
204impl<'a, C: CustomType> LangFunc<'a, C> {
205  /// Adds an argument for a function call
206  ///
207  /// ## Example
208  /// ```
209  /// use funki_templates::{Argument, ParsedTemplate, BlankCustom};
210  /// let x = ParsedTemplate::<BlankCustom>::from_text("#main x -> x + 4;").unwrap();
211  /// let f = x.function("main").unwrap();
212  /// let f = f.arg(Argument::Int(5));
213  /// f.call().unwrap(); // -> ReturnVal::Int(9)
214  /// ```
215  pub fn arg(mut self, arg: Argument<C>) -> Self {
216    self.arg = Some(arg);
217    self
218  }
219  /// Interprets this function
220  /// Can return a language error if the interpretation fails
221  ///
222  /// ## Example
223  /// ```
224  /// use funki_templates::{Language, ParsedTemplate, BlankCustom};
225  /// let x = ParsedTemplate::<BlankCustom>::from_text("#main 5;").unwrap();
226  /// let f = x.function("main").unwrap();
227  /// f.call().unwrap(); // -> ReturnVal::Int(5)
228  /// ```
229  pub fn call(&self) -> Result<ReturnVal<C>, LanguageErr> {
230    if let Some(x) = &self.arg {
231      interpret(
232        &self.lang.temp,
233        self.name.as_str(),
234        InterpretVal::from_arg(x),
235        &Customs::new_from_hash(
236          self.lang.binary_operators.clone(),
237          self.lang.unary_operators.clone(),
238          self.lang.built_ins.clone(),
239        ),
240      )
241    } else {
242      interpret(
243        &self.lang.temp,
244        self.name.as_str(),
245        InterpretVal::Tuple(vec![]),
246        &Customs::new_from_hash(Default::default(), Default::default(), Default::default()),
247      )
248    }
249    .map_err(|e| LanguageErr::new_from_int_err(e, self.text.clone()))
250  }
251}
252
253/// A language error with a location
254pub struct LocationLangErr {
255  message: String,
256  lines: (usize, usize),
257  char: (usize, usize),
258  section: String,
259}
260
261/// An enum for the possible types of error that can result from interpretation
262pub enum LanguageErr {
263  NoLoc(String),
264  Loc(LocationLangErr),
265}
266
267impl LanguageErr {
268  /// Creates a language error with location information
269  /// Adds in the original language string so the line numbers and string section can be found
270  fn new_loc(message: String, location: (usize, usize), lang: String) -> Self {
271    let (start_line, start_char) = get_lang_pos(&lang, location.0);
272    let (end_line, end_char) = get_lang_pos(&lang, location.1);
273    LanguageErr::Loc(LocationLangErr {
274      lines: (start_line, end_line),
275      section: lang[location.0..location.1].to_string(),
276      char: (start_char, end_char),
277      message,
278    })
279  }
280
281  /// Creates a location error with no location data
282  fn new_no_loc(message: String) -> Self {
283    LanguageErr::NoLoc(message)
284  }
285
286  /// Creates a location error from an interpretation error
287  fn new_from_int_err(err: InterpretError, lang: String) -> Self {
288    if err.location.is_some() {
289      Self::new_loc(err.message, err.location.unwrap(), lang)
290    } else {
291      Self::new_no_loc(err.message)
292    }
293  }
294
295  /// Creates a location error from a parser error
296  fn new_from_parser_err(
297    err: ParseError<usize, String, (usize, String, usize)>,
298    lang: String,
299  ) -> Self {
300    match err {
301      ParseError::InvalidToken { location } => {
302        let (line, char) = get_lang_pos(&lang, location);
303        Self::Loc(LocationLangErr {
304          message: "Invalid token".to_string(),
305          lines: (line, line),
306          char: (char, char),
307          section: lang[location..location + 10].to_string(),
308        })
309      }
310      ParseError::UnrecognizedEOF { location, .. } => {
311        let (line, char) = get_lang_pos(&lang, location);
312        Self::Loc(LocationLangErr {
313          message: "Unexpected End of File".to_string(),
314          lines: (line, line),
315          char: (char, char),
316          section: lang[cmp::min(location - 10, 0)..].to_string(),
317        })
318      }
319      ParseError::UnrecognizedToken {
320        token: (l, _, r), ..
321      } => {
322        let (start_line, start_char) = get_lang_pos(&lang, l);
323        let (end_line, end_char) = get_lang_pos(&lang, r);
324        Self::Loc(LocationLangErr {
325          message: "Unrecognised token".to_string(),
326          lines: (start_line, end_line),
327          char: (start_char, end_char),
328          section: lang[l..r].to_string(),
329        })
330      }
331      ParseError::ExtraToken {
332        token: (l, _, r), ..
333      } => {
334        let (start_line, start_char) = get_lang_pos(&lang, l);
335        let (end_line, end_char) = get_lang_pos(&lang, r);
336        Self::Loc(LocationLangErr {
337          message: "Extra token".to_string(),
338          lines: (start_line, end_line),
339          char: (start_char, end_char),
340          section: lang[l..r].to_string(),
341        })
342      }
343      ParseError::User { error: (l, m, r) } => {
344        let (start_line, start_char) = get_lang_pos(&lang, l);
345        let (end_line, end_char) = get_lang_pos(&lang, r);
346        Self::Loc(LocationLangErr {
347          message: m,
348          lines: (start_line, end_line),
349          char: (start_char, end_char),
350          section: lang[l..r].to_string(),
351        })
352      }
353    }
354  }
355}
356
357fn get_lang_pos(lang: &str, pos: usize) -> (usize, usize) {
358  let new_lines = lang[0..pos]
359    .as_bytes()
360    .iter()
361    .enumerate()
362    .filter(|(_, c)| **c == b'\n');
363  let line_num = new_lines.clone().count();
364  let char = pos - new_lines.last().unwrap_or((0, &b'x')).0;
365
366  (line_num, char)
367}
368
369impl Debug for LanguageErr {
370  fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
371    match self {
372      LanguageErr::Loc(l) => {
373        write!(
374          fmt,
375          "Error: \"{}\"\nAt lines: {}:{} - {}:{}\nCode: `{}`",
376          l.message,
377          l.lines.0 + 1,
378          l.char.0,
379          l.lines.1 + 1,
380          l.char.1,
381          l.section
382        )
383      }
384      LanguageErr::NoLoc(l) => {
385        write!(fmt, "Error: {}", l)
386      }
387    }
388  }
389}
390
391impl<C: CustomType> Debug for ReturnVal<C> {
392  fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
393    match self {
394      ReturnVal::Bool(b) => write!(fmt, "Bool({})", b),
395      ReturnVal::Int(i) => write!(fmt, "Int({})", i),
396      ReturnVal::String(s) => write!(fmt, "String({})", s),
397      ReturnVal::Tuple(v) => write!(
398        fmt,
399        "Tuple({})",
400        v.iter().map(|i| format!("{:?}", i)).join(", ")
401      ),
402      ReturnVal::List(v) => write!(
403        fmt,
404        "List({})",
405        v.iter().map(|i| format!("{:?}", i)).join(", ")
406      ),
407      ReturnVal::Custom(v) => write!(fmt, "Custom({:?})", v),
408    }
409  }
410}
411
412impl<C: CustomType> ToString for ReturnVal<C> {
413  fn to_string(&self) -> String {
414    match self {
415      ReturnVal::Bool(b) => b.to_string(),
416      ReturnVal::Int(i) => i.to_string(),
417      ReturnVal::String(s) => s.to_string(),
418      ReturnVal::Tuple(v) => format!("({})", v.iter().map(|i| i.to_string()).join(", ")),
419      ReturnVal::List(v) => format!("[{}]", v.iter().map(|i| i.to_string()).join(", ")),
420      ReturnVal::Custom(v) => v.to_string(),
421    }
422  }
423}
424
425/// A blank custom type to use if no custom types are required
426#[derive(Clone, Debug, PartialEq)]
427pub struct BlankCustom {}
428
429impl ToString for BlankCustom {
430  fn to_string(&self) -> String {
431    "Blank".to_string()
432  }
433}
434
435impl CustomType for BlankCustom {}