2 releases

0.1.1 Feb 3, 2020
0.1.0 Sep 15, 2019

#24 in #parse-input

30 downloads per month
Used in gameshell

LGPL-3.0-or-later

32KB
618 lines

A zero-allocation, no_std, lisp-inspired command language parser for custom interpreters.

Here's an example:

use metac::{Data, Evaluate};
struct Eval { }
impl Evaluate<()> for Eval {
    fn evaluate(&mut self, statement: &[Data]) -> () {
        for part in statement {
            match part {
                Data::Atom(string) => {}
                Data::Command(command_string) => {}
            }
            println!("{:?}", part);
        }
    }
}

let mut eval = Eval { };
eval.interpret_single("Hello (World 1 2) 3").unwrap();

All you need to do is implement trait Evaluate on a structure, then, you call interpret on said struct.

This allows you to put anything in your struct, you decide how the interpreter is going to work. What this library does for you is parse the input into two things:

  1. Atoms - Basically strings
  2. Commands - ()-enclosed text.

Note that nested expressions are not expanded by metac, you have to do this yourself. A statement with nesting like something (alpha (beta gamma)) will be parsed as [Atom("something"), Command("alpha (beta gamma)")]. Your evaluator decides whether it will parse the contents or use it for something different.

More interesting example

use metac::{Data, Evaluate};
use std::collections::HashMap;
struct Eval {
    hashmap: HashMap<String, String>,
}
impl Eval {
    fn register(&mut self, key: &str, value: &str) {
        self.hashmap.insert(key.into(), value.into());
    }
}
impl Evaluate<String> for Eval {
    fn evaluate(&mut self, statement: &[Data]) -> String {
        if statement.len() == 2 {
            if let Data::Atom("Get") = statement[0] {
                if let Data::Atom(key) = statement[1] {
                    return self.hashmap.get(key).unwrap().clone();
                }
            }
        }
        "".into()
    }
}

let mut eval = Eval { hashmap: HashMap::new() };
eval.register("my-variable", "my-value");
assert_eq!("my-value", eval.interpret_single("Get my-variable").unwrap());

From here we can set up a more complex environment, callbacks, etc. It's all up to the implementer.

Multiline Support

Because this is a shell-like language, it's quite line oriented by nature. Feeding "a b c\nd e f" into the interpreter will separately interpret each line.

However, it is sometimes very desirable to write code on multiple lines. The only way to do this in metac is by using parentheses or interpret_single:

use metac::{Data, Evaluate};
struct Eval { }
impl Evaluate<usize> for Eval {
    fn evaluate(&mut self, statement: &[Data]) -> usize {
        statement.len()
    }
}

let mut eval = Eval { };

assert_eq!(5, eval.interpret_single("This is\na single statement").unwrap());

// Note: The return value is the result of interpreting the last statement, which is why
// it returns 3 instead of 2 (the first statement) or 5 (the sum).
assert_eq!(3, eval.interpret_multiple("Here are\ntwo unrelated statements").unwrap());
assert_eq!(5, eval.interpret_single("Here are\ntwo related statements").unwrap());

// Because the "\n" was present during an opening parenthesis, both lines are considered
// part of the same statement, hence 5 elements in this statement.
assert_eq!(5, eval.interpret_multiple("This is (\na) single statement").unwrap());

Dependencies

~73KB