Write docs; Refine library API
This commit is contained in:
parent
7c9be2151d
commit
4cbfdde4a3
@ -5,6 +5,7 @@ use crate::{
|
|||||||
SyntaxNode, SyntaxPosition, Type, TypeSpecification, Value,
|
SyntaxNode, SyntaxPosition, Type, TypeSpecification, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Variable assignment, including add-assign and subtract-assign operations.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct Assignment {
|
pub struct Assignment {
|
||||||
identifier: Identifier,
|
identifier: Identifier,
|
||||||
|
@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
|
use crate::{AbstractTree, Error, Format, Map, Result, SyntaxNode, Type, Value};
|
||||||
|
|
||||||
|
/// Operators that be used in an assignment statement.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum AssignmentOperator {
|
pub enum AssignmentOperator {
|
||||||
Equal,
|
Equal,
|
||||||
|
@ -14,36 +14,57 @@ static JSON: OnceLock<Value> = OnceLock::new();
|
|||||||
static RANDOM: OnceLock<Value> = OnceLock::new();
|
static RANDOM: OnceLock<Value> = OnceLock::new();
|
||||||
static STRING: OnceLock<Value> = OnceLock::new();
|
static STRING: OnceLock<Value> = OnceLock::new();
|
||||||
|
|
||||||
|
/// Returns the entire built-in value API.
|
||||||
pub fn built_in_values() -> impl Iterator<Item = BuiltInValue> {
|
pub fn built_in_values() -> impl Iterator<Item = BuiltInValue> {
|
||||||
all()
|
all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A variable with a hard-coded key that is globally available.
|
||||||
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub enum BuiltInValue {
|
pub enum BuiltInValue {
|
||||||
|
/// The arguments used to launch the current program.
|
||||||
Args,
|
Args,
|
||||||
|
|
||||||
|
/// Create an error if two values are not equal.
|
||||||
AssertEqual,
|
AssertEqual,
|
||||||
|
|
||||||
|
/// File system tools.
|
||||||
Fs,
|
Fs,
|
||||||
|
|
||||||
|
/// JSON format tools.
|
||||||
Json,
|
Json,
|
||||||
|
|
||||||
|
/// Get the length of a collection.
|
||||||
Length,
|
Length,
|
||||||
|
|
||||||
|
/// Print a value to stdout.
|
||||||
Output,
|
Output,
|
||||||
|
|
||||||
|
/// Random value generators.
|
||||||
Random,
|
Random,
|
||||||
|
|
||||||
|
/// String utilities.
|
||||||
Str,
|
Str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuiltInValue {
|
impl BuiltInValue {
|
||||||
|
/// Returns the hard-coded key used to identify the value.
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
BuiltInValue::Args => "args",
|
BuiltInValue::Args => "args",
|
||||||
BuiltInValue::AssertEqual => "assert_equal",
|
BuiltInValue::AssertEqual => "assert_equal",
|
||||||
BuiltInValue::Fs => "fs",
|
BuiltInValue::Fs => "fs",
|
||||||
BuiltInValue::Json => "json",
|
BuiltInValue::Json => "json",
|
||||||
BuiltInValue::Length => "length",
|
BuiltInValue::Length => BuiltInFunction::Length.name(),
|
||||||
BuiltInValue::Output => "output",
|
BuiltInValue::Output => "output",
|
||||||
BuiltInValue::Random => "random",
|
BuiltInValue::Random => "random",
|
||||||
BuiltInValue::Str => "str",
|
BuiltInValue::Str => "str",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a brief description of the value's features.
|
||||||
|
///
|
||||||
|
/// This is used by the shell when suggesting completions.
|
||||||
pub fn description(&self) -> &'static str {
|
pub fn description(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
BuiltInValue::Args => "The command line arguments sent to this program.",
|
BuiltInValue::Args => "The command line arguments sent to this program.",
|
||||||
@ -57,6 +78,9 @@ impl BuiltInValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the value's type.
|
||||||
|
///
|
||||||
|
/// This is checked with a unit test to ensure it matches the value.
|
||||||
pub fn r#type(&self) -> Type {
|
pub fn r#type(&self) -> Type {
|
||||||
match self {
|
match self {
|
||||||
BuiltInValue::Args => Type::list(Type::String),
|
BuiltInValue::Args => Type::list(Type::String),
|
||||||
@ -70,6 +94,8 @@ impl BuiltInValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the value by creating it or, if it has already been accessed, retrieving it from its
|
||||||
|
/// [OnceLock][].
|
||||||
pub fn get(&self) -> &Value {
|
pub fn get(&self) -> &Value {
|
||||||
match self {
|
match self {
|
||||||
BuiltInValue::Args => ARGS.get_or_init(|| {
|
BuiltInValue::Args => ARGS.get_or_init(|| {
|
||||||
@ -178,3 +204,18 @@ impl Format for BuiltInValue {
|
|||||||
output.push_str(&self.get().to_string());
|
output.push_str(&self.get().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::built_in_values;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_built_in_types() {
|
||||||
|
for built_in_value in built_in_values() {
|
||||||
|
let expected = built_in_value.r#type();
|
||||||
|
let actual = built_in_value.get().r#type();
|
||||||
|
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
178
src/interpret.rs
178
src/interpret.rs
@ -1,20 +1,48 @@
|
|||||||
//! Tools to run and/or format dust source code.
|
//! Tools to interpret dust source code.
|
||||||
//!
|
//!
|
||||||
//! You can use this library externally by calling either of the "interpret"
|
//! This module has three tools to run Dust code.
|
||||||
//! functions or by constructing your own Interpreter.
|
//!
|
||||||
use tree_sitter::{Parser, Tree as TSTree, TreeCursor};
|
//! - [interpret] is the simples way to run Dust code inside of an application or library
|
||||||
|
//! - [interpret_with_context] allows you to set variables on the execution context
|
||||||
|
//! - [Interpreter] is an advanced tool that can parse, verify, run and format Dust code
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! Run some Dust and get the result.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use dust_lang::*;
|
||||||
|
//! assert_eq!(
|
||||||
|
//! interpret("1 + 2 + 3"),
|
||||||
|
//! Ok(Value::Integer(6))
|
||||||
|
//! );
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Create a custom context with variables you can use in your code.
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # use dust_lang::*;
|
||||||
|
//! let context = Map::new();
|
||||||
|
//!
|
||||||
|
//! context.set("one".into(), 1.into());
|
||||||
|
//! context.set("two".into(), 2.into());
|
||||||
|
//! context.set("three".into(), 3.into());
|
||||||
|
//!
|
||||||
|
//! let dust_code = "four = 4; one + two + three + four";
|
||||||
|
//!
|
||||||
|
//! assert_eq!(
|
||||||
|
//! interpret_with_context(dust_code, context),
|
||||||
|
//! Ok(Value::Integer(10))
|
||||||
|
//! );
|
||||||
|
//! ```
|
||||||
|
use tree_sitter::{Node as SyntaxNode, Parser, Tree as SyntaxTree, TreeCursor};
|
||||||
|
|
||||||
use crate::{language, AbstractTree, Error, Format, Map, Result, Root, SyntaxNode, Value};
|
use crate::{language, AbstractTree, Error, Format, Map, Result, Root, Value};
|
||||||
|
|
||||||
/// Interpret the given source code. Returns the value of last statement or the
|
/// Interpret the given source code. Returns the value of last statement or the
|
||||||
/// first error encountered.
|
/// first error encountered.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// See the [module-level docs][self] for more info.
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use dust_lang::*;
|
|
||||||
/// assert_eq!(interpret("1 + 2 + 3"), Ok(Value::Integer(6)));
|
|
||||||
/// ```
|
|
||||||
pub fn interpret(source: &str) -> Result<Value> {
|
pub fn interpret(source: &str) -> Result<Value> {
|
||||||
interpret_with_context(source, Map::new())
|
interpret_with_context(source, Map::new())
|
||||||
}
|
}
|
||||||
@ -26,23 +54,7 @@ pub fn interpret(source: &str) -> Result<Value> {
|
|||||||
/// for the `<map>` type. Any value can be set, including functions and nested
|
/// for the `<map>` type. Any value can be set, including functions and nested
|
||||||
/// maps.
|
/// maps.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// See the [module-level docs][self] for more info.
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use dust_lang::*;
|
|
||||||
/// let context = Map::new();
|
|
||||||
///
|
|
||||||
/// context.set("one".into(), 1.into());
|
|
||||||
/// context.set("two".into(), 2.into());
|
|
||||||
/// context.set("three".into(), 3.into());
|
|
||||||
///
|
|
||||||
/// let dust_code = "four = 4 one + two + three + four";
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// interpret_with_context(dust_code, context),
|
|
||||||
/// Ok(Value::Integer(10))
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
||||||
let mut interpreter = Interpreter::new(context);
|
let mut interpreter = Interpreter::new(context);
|
||||||
let value = interpreter.run(source)?;
|
let value = interpreter.run(source)?;
|
||||||
@ -51,14 +63,17 @@ pub fn interpret_with_context(source: &str, context: Map) -> Result<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A source code interpreter for the Dust language.
|
/// A source code interpreter for the Dust language.
|
||||||
|
///
|
||||||
|
/// The interpreter's most important functions are used to parse dust source code, verify it is safe
|
||||||
|
/// and run it and they are written in a way that forces them to be used safely. Each step in this
|
||||||
|
/// process contains the prior steps, meaning that the same code is always used to create the syntax /// tree, abstract tree and final evaluation. This avoids a critical logic error.
|
||||||
pub struct Interpreter {
|
pub struct Interpreter {
|
||||||
parser: Parser,
|
parser: Parser,
|
||||||
context: Map,
|
context: Map,
|
||||||
syntax_tree: Option<TSTree>,
|
|
||||||
abstract_tree: Option<Root>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interpreter {
|
impl Interpreter {
|
||||||
|
/// Creates a new interpreter with the given variable context.
|
||||||
pub fn new(context: Map) -> Self {
|
pub fn new(context: Map) -> Self {
|
||||||
let mut parser = Parser::new();
|
let mut parser = Parser::new();
|
||||||
|
|
||||||
@ -66,15 +81,35 @@ impl Interpreter {
|
|||||||
.set_language(language())
|
.set_language(language())
|
||||||
.expect("Language version is incompatible with tree sitter version.");
|
.expect("Language version is incompatible with tree sitter version.");
|
||||||
|
|
||||||
Interpreter {
|
parser.set_logger(Some(Box::new(|log_type, message| {
|
||||||
parser,
|
log::info!("{}", message)
|
||||||
context,
|
})));
|
||||||
syntax_tree: None,
|
|
||||||
abstract_tree: None,
|
Interpreter { parser, context }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a syntax tree from the source. Returns an error if the the parser is cancelled for
|
||||||
|
/// taking too long. The syntax tree may contain error nodes, which represent syntax errors.
|
||||||
|
///
|
||||||
|
/// Tree sitter is designed to be run on every keystroke, so this is generally a lightweight
|
||||||
|
/// function to call.
|
||||||
|
pub fn parse(&mut self, source: &str) -> Result<SyntaxTree> {
|
||||||
|
if let Some(tree) = self.parser.parse(source, None) {
|
||||||
|
Ok(tree)
|
||||||
|
} else {
|
||||||
|
Err(Error::ParserCancelled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(&mut self, source: &str) -> Result<()> {
|
/// Checks the source for errors and generates an abstract tree.
|
||||||
|
///
|
||||||
|
/// The order in which this function works is:
|
||||||
|
///
|
||||||
|
/// - parse the source into a syntax tree
|
||||||
|
/// - check the syntax tree for errors
|
||||||
|
/// - generate an abstract tree from the source and syntax tree
|
||||||
|
/// - check the abstract tree for type errors
|
||||||
|
pub fn verify(&mut self, source: &str) -> Result<Root> {
|
||||||
fn check_for_error(node: SyntaxNode, source: &str, cursor: &mut TreeCursor) -> Result<()> {
|
fn check_for_error(node: SyntaxNode, source: &str, cursor: &mut TreeCursor) -> Result<()> {
|
||||||
if node.is_error() {
|
if node.is_error() {
|
||||||
Err(Error::Syntax {
|
Err(Error::Syntax {
|
||||||
@ -90,59 +125,44 @@ impl Interpreter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let syntax_tree = self.parser.parse(source, None);
|
let syntax_tree = self.parse(source)?;
|
||||||
|
let root = syntax_tree.root_node();
|
||||||
if let Some(tree) = &syntax_tree {
|
let mut cursor = syntax_tree.root_node().walk();
|
||||||
let root = tree.root_node();
|
|
||||||
let mut cursor = root.walk();
|
|
||||||
|
|
||||||
check_for_error(root, source, &mut cursor)?;
|
check_for_error(root, source, &mut cursor)?;
|
||||||
}
|
|
||||||
|
|
||||||
self.syntax_tree = syntax_tree;
|
let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self, source: &str) -> Result<Value> {
|
|
||||||
self.parse(source)?;
|
|
||||||
|
|
||||||
self.abstract_tree = if let Some(syntax_tree) = &self.syntax_tree {
|
|
||||||
Some(Root::from_syntax(
|
|
||||||
syntax_tree.root_node(),
|
|
||||||
source,
|
|
||||||
&self.context,
|
|
||||||
)?)
|
|
||||||
} else {
|
|
||||||
return Err(Error::ParserCancelled);
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(abstract_tree) = &self.abstract_tree {
|
|
||||||
abstract_tree.check_type(source, &self.context)?;
|
abstract_tree.check_type(source, &self.context)?;
|
||||||
abstract_tree.run(source, &self.context)
|
|
||||||
} else {
|
Ok(abstract_tree)
|
||||||
Ok(Value::none())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn syntax_tree(&self) -> Result<String> {
|
/// Runs the source, returning the final statement's value or first error.
|
||||||
if let Some(syntax_tree) = &self.syntax_tree {
|
///
|
||||||
Ok(syntax_tree.root_node().to_sexp())
|
/// This function [parses][Self::parse], [verifies][Self::verify] and [runs][Root::run] using
|
||||||
} else {
|
/// the same source code.
|
||||||
Err(Error::ParserCancelled)
|
pub fn run(&mut self, source: &str) -> Result<Value> {
|
||||||
}
|
self.verify(source)?.run(source, &self.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(&self) -> String {
|
/// Return an s-expression displaying a syntax tree of the source, or the ParserCancelled error
|
||||||
if let Some(root_node) = &self.abstract_tree {
|
/// if the parser takes too long.
|
||||||
let mut formatted_source = String::new();
|
pub fn syntax_tree(&mut self, source: &str) -> Result<String> {
|
||||||
|
Ok(self.parse(source)?.root_node().to_sexp())
|
||||||
root_node.format(&mut formatted_source, 0);
|
|
||||||
|
|
||||||
formatted_source
|
|
||||||
} else {
|
|
||||||
String::with_capacity(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return formatted Dust code generated from the current abstract tree, or None if no source
|
||||||
|
/// code has been run successfully.
|
||||||
|
///
|
||||||
|
/// You should call [verify][Interpreter::verify] before calling this function. You can only
|
||||||
|
/// create formatted source from a valid abstract tree.
|
||||||
|
pub fn format(&mut self, source: &str) -> Result<String> {
|
||||||
|
let mut formatted_output = String::new();
|
||||||
|
|
||||||
|
self.verify(source)?.format(&mut formatted_output, 0);
|
||||||
|
|
||||||
|
Ok(formatted_output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/lib.rs
12
src/lib.rs
@ -1,19 +1,21 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
//! The Dust library is used to parse, format and run dust source code.
|
//! The Dust library is used to parse, format and run dust source code.
|
||||||
//!
|
//!
|
||||||
//! See the [interpret] module for more information.
|
//! See the [interpret] module for more information.
|
||||||
|
//!
|
||||||
|
//! You can use this library externally by calling either of the "interpret"
|
||||||
|
//! functions or by constructing your own Interpreter.
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
abstract_tree::*, built_in_functions::BuiltInFunction, error::*, interpret::*, value::*,
|
abstract_tree::*, built_in_functions::BuiltInFunction, error::*, interpret::*, value::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use tree_sitter::Node as SyntaxNode;
|
pub use tree_sitter::Node as SyntaxNode;
|
||||||
|
|
||||||
mod abstract_tree;
|
pub mod abstract_tree;
|
||||||
pub mod built_in_functions;
|
pub mod built_in_functions;
|
||||||
mod error;
|
pub mod error;
|
||||||
mod interpret;
|
pub mod interpret;
|
||||||
mod value;
|
pub mod value;
|
||||||
|
|
||||||
use tree_sitter::Language;
|
use tree_sitter::Language;
|
||||||
|
|
||||||
|
@ -89,18 +89,17 @@ fn main() {
|
|||||||
|
|
||||||
if let Some(CliCommand::Syntax { path }) = args.cli_command {
|
if let Some(CliCommand::Syntax { path }) = args.cli_command {
|
||||||
let source = read_to_string(path).unwrap();
|
let source = read_to_string(path).unwrap();
|
||||||
|
let syntax_tree_sexp = interpreter.syntax_tree(&source).unwrap();
|
||||||
|
|
||||||
interpreter.parse(&source).unwrap();
|
println!("{syntax_tree_sexp}");
|
||||||
|
|
||||||
println!("{}", interpreter.syntax_tree().unwrap());
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(CliCommand::Format) = args.cli_command {
|
if let Some(CliCommand::Format) = args.cli_command {
|
||||||
interpreter.parse(&source).unwrap();
|
let formatted = interpreter.format(&source).unwrap();
|
||||||
|
|
||||||
println!("{}", interpreter.format());
|
println!("{formatted}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,7 @@ use dust_lang::*;
|
|||||||
fn format_simple_program() {
|
fn format_simple_program() {
|
||||||
let mut interpreter = Interpreter::new(Map::new());
|
let mut interpreter = Interpreter::new(Map::new());
|
||||||
|
|
||||||
interpreter.run("x=1").unwrap();
|
assert_eq!(interpreter.format("x=1"), Ok("x = 1\n".to_string()));
|
||||||
|
|
||||||
assert_eq!(interpreter.format(), "x = 1\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FORMATTED_BLOCK: &str = "{
|
const FORMATTED_BLOCK: &str = "{
|
||||||
@ -20,9 +18,10 @@ const FORMATTED_BLOCK: &str = "{
|
|||||||
fn format_block() {
|
fn format_block() {
|
||||||
let mut interpreter = Interpreter::new(Map::new());
|
let mut interpreter = Interpreter::new(Map::new());
|
||||||
|
|
||||||
interpreter.run("{1 2 3}").unwrap();
|
assert_eq!(
|
||||||
|
interpreter.format("{1 2 3}"),
|
||||||
assert_eq!(FORMATTED_BLOCK, interpreter.format());
|
Ok(FORMATTED_BLOCK.to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FORMATTED_MAP: &str = "{
|
const FORMATTED_MAP: &str = "{
|
||||||
@ -37,9 +36,10 @@ const FORMATTED_MAP: &str = "{
|
|||||||
fn format_map() {
|
fn format_map() {
|
||||||
let mut interpreter = Interpreter::new(Map::new());
|
let mut interpreter = Interpreter::new(Map::new());
|
||||||
|
|
||||||
interpreter.run("{{x=1 y <int> = 2}}").unwrap();
|
assert_eq!(
|
||||||
|
interpreter.format("{{x=1 y <int> = 2}}"),
|
||||||
assert_eq!(FORMATTED_MAP, interpreter.format());
|
Ok(FORMATTED_MAP.to_string())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FORMATTED_FUNCTION: &str = "(x <int>) <num> {
|
const FORMATTED_FUNCTION: &str = "(x <int>) <num> {
|
||||||
@ -50,8 +50,8 @@ const FORMATTED_FUNCTION: &str = "(x <int>) <num> {
|
|||||||
#[test]
|
#[test]
|
||||||
fn format_function() {
|
fn format_function() {
|
||||||
let mut interpreter = Interpreter::new(Map::new());
|
let mut interpreter = Interpreter::new(Map::new());
|
||||||
|
assert_eq!(
|
||||||
interpreter.run("( x< int > )<num>{x/2}").unwrap();
|
interpreter.format("( x< int > )<num>{x/2}"),
|
||||||
|
Ok(FORMATTED_FUNCTION.to_string())
|
||||||
assert_eq!(FORMATTED_FUNCTION, interpreter.format());
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user