Improve Intepreter API for shell use
This commit is contained in:
parent
049c28795b
commit
6be9204123
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -316,7 +316,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dust-lang"
|
||||
version = "0.3.8"
|
||||
version = "0.3.9"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"cc",
|
||||
|
@ -153,25 +153,25 @@ impl AbstractTree for Assignment {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, Error, List, Type, Value};
|
||||
use crate::{interpret, Error, List, Type, Value};
|
||||
|
||||
#[test]
|
||||
fn simple_assignment() {
|
||||
let test = evaluate("x = 1 x").unwrap();
|
||||
let test = interpret("x = 1 x").unwrap();
|
||||
|
||||
assert_eq!(Value::Integer(1), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_assignment_with_type() {
|
||||
let test = evaluate("x <int> = 1 x").unwrap();
|
||||
let test = interpret("x <int> = 1 x").unwrap();
|
||||
|
||||
assert_eq!(Value::Integer(1), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_add_assign() {
|
||||
let test = evaluate(
|
||||
let test = interpret(
|
||||
"
|
||||
x <[int]> = []
|
||||
x += 1
|
||||
@ -185,7 +185,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn list_add_wrong_type() {
|
||||
let result = evaluate(
|
||||
let result = interpret(
|
||||
"
|
||||
x <[str]> = []
|
||||
x += 1
|
||||
|
@ -173,12 +173,12 @@ impl AbstractTree for FunctionCall {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, Value};
|
||||
use crate::{interpret, Value};
|
||||
|
||||
#[test]
|
||||
fn evaluate_function_call() {
|
||||
assert_eq!(
|
||||
evaluate(
|
||||
interpret(
|
||||
"
|
||||
foobar = (fn message <str>) <str> { message }
|
||||
(foobar 'Hiya')
|
||||
@ -191,7 +191,7 @@ mod tests {
|
||||
#[test]
|
||||
fn evaluate_callback() {
|
||||
assert_eq!(
|
||||
evaluate(
|
||||
interpret(
|
||||
"
|
||||
foobar = (fn cb <() -> str>) <str> {
|
||||
(cb)
|
||||
@ -206,6 +206,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn evaluate_built_in_function_call() {
|
||||
assert_eq!(evaluate("(output 'Hiya')"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("(output 'Hiya')"), Ok(Value::Option(None)));
|
||||
}
|
||||
}
|
||||
|
@ -88,21 +88,24 @@ impl AbstractTree for IfElse {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, Value};
|
||||
use crate::{interpret, Value};
|
||||
|
||||
#[test]
|
||||
fn evaluate_if() {
|
||||
assert_eq!(
|
||||
evaluate("if true { 'true' }"),
|
||||
interpret("if true { 'true' }"),
|
||||
Ok(Value::String("true".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_if_else() {
|
||||
assert_eq!(evaluate("if false { 1 } else { 2 }"), Ok(Value::Integer(2)));
|
||||
assert_eq!(
|
||||
evaluate("if true { 1.0 } else { 42.0 }"),
|
||||
interpret("if false { 1 } else { 2 }"),
|
||||
Ok(Value::Integer(2))
|
||||
);
|
||||
assert_eq!(
|
||||
interpret("if true { 1.0 } else { 42.0 }"),
|
||||
Ok(Value::Float(1.0))
|
||||
);
|
||||
}
|
||||
@ -110,7 +113,7 @@ mod tests {
|
||||
#[test]
|
||||
fn evaluate_if_else_else_if_else() {
|
||||
assert_eq!(
|
||||
evaluate(
|
||||
interpret(
|
||||
"
|
||||
if false {
|
||||
'no'
|
||||
@ -128,7 +131,7 @@ mod tests {
|
||||
#[test]
|
||||
fn evaluate_if_else_if_else_if_else_if_else() {
|
||||
assert_eq!(
|
||||
evaluate(
|
||||
interpret(
|
||||
"
|
||||
if false {
|
||||
'no'
|
||||
|
@ -100,25 +100,25 @@ impl AbstractTree for Index {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::evaluate;
|
||||
use crate::interpret;
|
||||
|
||||
#[test]
|
||||
fn list_index() {
|
||||
let test = evaluate("x = [1 [2] 3] x:1:0").unwrap();
|
||||
let test = interpret("x = [1 [2] 3] x:1:0").unwrap();
|
||||
|
||||
assert_eq!(Value::Integer(2), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_index() {
|
||||
let test = evaluate("x = {y = {z = 2}} x:y:z").unwrap();
|
||||
let test = interpret("x = {y = {z = 2}} x:y:z").unwrap();
|
||||
|
||||
assert_eq!(Value::Integer(2), test);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_index() {
|
||||
let test = evaluate(
|
||||
let test = interpret(
|
||||
"
|
||||
x = [1 2 3]
|
||||
y = (fn) <int> { 0 }
|
||||
|
@ -85,11 +85,11 @@ impl AbstractTree for Match {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, Value};
|
||||
use crate::{interpret, Value};
|
||||
|
||||
#[test]
|
||||
fn evaluate_match() {
|
||||
let test = evaluate(
|
||||
let test = interpret(
|
||||
"
|
||||
match 1 {
|
||||
3 => false
|
||||
@ -105,7 +105,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn evaluate_match_assignment() {
|
||||
let test = evaluate(
|
||||
let test = interpret(
|
||||
"
|
||||
x = match 1 {
|
||||
3 => false
|
||||
|
@ -261,13 +261,13 @@ impl Display for Type {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::evaluate;
|
||||
use crate::interpret;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_type_check() {
|
||||
let result = evaluate("x <bool> = 1");
|
||||
let result = interpret("x <bool> = 1");
|
||||
|
||||
assert!(result.unwrap_err().is_type_check_error(&Error::TypeCheck {
|
||||
expected: Type::Boolean,
|
||||
@ -277,7 +277,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn callback_type_check() {
|
||||
let result = evaluate(
|
||||
let result = interpret(
|
||||
"
|
||||
x = (fn cb <() -> bool>) <bool> {
|
||||
(cb)
|
||||
|
@ -3,7 +3,7 @@ use std::fs::read_to_string;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{evaluate_with_context, AbstractTree, Error, Map, Result, Type, Value};
|
||||
use crate::{interpret_with_context, AbstractTree, Error, Map, Result, Type, Value};
|
||||
|
||||
/// Abstract representation of a use statement.
|
||||
///
|
||||
@ -29,7 +29,7 @@ impl AbstractTree for Use {
|
||||
let file_contents = read_to_string(&self.path)?;
|
||||
let mut file_context = Map::new();
|
||||
|
||||
evaluate_with_context(&file_contents, &mut file_context)?;
|
||||
interpret_with_context(&file_contents, &mut file_context)?;
|
||||
|
||||
for (key, (value, r#type)) in file_context.variables()?.iter() {
|
||||
context.set(key.clone(), value.clone(), Some(r#type.clone()))?;
|
||||
|
@ -247,39 +247,39 @@ impl AbstractTree for ValueNode {
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, List};
|
||||
use crate::{interpret, List};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn evaluate_empty() {
|
||||
assert_eq!(evaluate("x = 9"), Ok(Value::Option(None)));
|
||||
assert_eq!(evaluate("x = 1 + 1"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("x = 9"), Ok(Value::Option(None)));
|
||||
assert_eq!(interpret("x = 1 + 1"), Ok(Value::Option(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_integer() {
|
||||
assert_eq!(evaluate("1"), Ok(Value::Integer(1)));
|
||||
assert_eq!(evaluate("123"), Ok(Value::Integer(123)));
|
||||
assert_eq!(evaluate("-666"), Ok(Value::Integer(-666)));
|
||||
assert_eq!(interpret("1"), Ok(Value::Integer(1)));
|
||||
assert_eq!(interpret("123"), Ok(Value::Integer(123)));
|
||||
assert_eq!(interpret("-666"), Ok(Value::Integer(-666)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_float() {
|
||||
assert_eq!(evaluate("0.1"), Ok(Value::Float(0.1)));
|
||||
assert_eq!(evaluate("12.3"), Ok(Value::Float(12.3)));
|
||||
assert_eq!(evaluate("-6.66"), Ok(Value::Float(-6.66)));
|
||||
assert_eq!(interpret("0.1"), Ok(Value::Float(0.1)));
|
||||
assert_eq!(interpret("12.3"), Ok(Value::Float(12.3)));
|
||||
assert_eq!(interpret("-6.66"), Ok(Value::Float(-6.66)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_string() {
|
||||
assert_eq!(evaluate("\"one\""), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(evaluate("'one'"), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(evaluate("`one`"), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(evaluate("`'one'`"), Ok(Value::String("'one'".to_string())));
|
||||
assert_eq!(evaluate("'`one`'"), Ok(Value::String("`one`".to_string())));
|
||||
assert_eq!(interpret("\"one\""), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(interpret("'one'"), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(interpret("`one`"), Ok(Value::String("one".to_string())));
|
||||
assert_eq!(interpret("`'one'`"), Ok(Value::String("'one'".to_string())));
|
||||
assert_eq!(interpret("'`one`'"), Ok(Value::String("`one`".to_string())));
|
||||
assert_eq!(
|
||||
evaluate("\"'one'\""),
|
||||
interpret("\"'one'\""),
|
||||
Ok(Value::String("'one'".to_string()))
|
||||
);
|
||||
}
|
||||
@ -287,7 +287,7 @@ mod tests {
|
||||
#[test]
|
||||
fn evaluate_list() {
|
||||
assert_eq!(
|
||||
evaluate("[1, 2, 'foobar']"),
|
||||
interpret("[1, 2, 'foobar']"),
|
||||
Ok(Value::List(List::with_items(vec![
|
||||
Value::Integer(1),
|
||||
Value::Integer(2),
|
||||
@ -304,7 +304,7 @@ mod tests {
|
||||
map.set("foo".to_string(), Value::String("bar".to_string()), None)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(evaluate("{ x = 1, foo = 'bar' }"), Ok(Value::Map(map)));
|
||||
assert_eq!(interpret("{ x = 1, foo = 'bar' }"), Ok(Value::Map(map)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -321,14 +321,14 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
evaluate("{ x <int> = 1, foo <str> = 'bar' }"),
|
||||
interpret("{ x <int> = 1, foo <str> = 'bar' }"),
|
||||
Ok(Value::Map(map))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn evaluate_map_type_errors() {
|
||||
assert!(evaluate("{ foo <bool> = 'bar' }")
|
||||
assert!(interpret("{ foo <bool> = 'bar' }")
|
||||
.unwrap_err()
|
||||
.is_type_check_error(&Error::TypeCheck {
|
||||
expected: Type::Boolean,
|
||||
@ -338,14 +338,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn evaluate_function() {
|
||||
let result = evaluate("(fn) <int> { 1 }");
|
||||
let result = interpret("(fn) <int> { 1 }");
|
||||
let value = result.unwrap();
|
||||
let function = value.as_function().unwrap();
|
||||
|
||||
assert_eq!(&Vec::<Identifier>::with_capacity(0), function.parameters());
|
||||
assert_eq!(Ok(&Type::Integer), function.return_type());
|
||||
|
||||
let result = evaluate("(fn x <bool>) <bool> {true}");
|
||||
let result = interpret("(fn x <bool>) <bool> {true}");
|
||||
let value = result.unwrap();
|
||||
let function = value.as_function().unwrap();
|
||||
|
||||
@ -358,7 +358,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn evaluate_option() {
|
||||
let result = evaluate("x <option(int)> = some(1); x").unwrap();
|
||||
let result = interpret("x <option(int)> = some(1); x").unwrap();
|
||||
|
||||
assert_eq!(Value::Option(Some(Box::new(Value::Integer(1)))), result);
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ impl AbstractTree for While {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{evaluate, Value};
|
||||
use crate::{interpret, Value};
|
||||
|
||||
#[test]
|
||||
fn evalualate_while_loop() {
|
||||
assert_eq!(evaluate("while false { 'foo' }"), Ok(Value::Option(None)))
|
||||
assert_eq!(interpret("while false { 'foo' }"), Ok(Value::Option(None)))
|
||||
}
|
||||
}
|
||||
|
30
src/built_in_functions/map.rs
Normal file
30
src/built_in_functions/map.rs
Normal file
@ -0,0 +1,30 @@
|
||||
//! Functions related to [Map][crate::Map] values.
|
||||
|
||||
use crate::{BuiltInFunction, List, Map, Result, Type, Value};
|
||||
|
||||
pub struct Keys;
|
||||
|
||||
impl BuiltInFunction for Keys {
|
||||
fn name(&self) -> &'static str {
|
||||
"keys"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||
let map = arguments.first().unwrap_or_default().as_map()?;
|
||||
let variables = map.variables()?;
|
||||
let mut keys = Vec::with_capacity(variables.len());
|
||||
|
||||
for (key, _) in variables.iter() {
|
||||
keys.push(Value::String(key.clone()))
|
||||
}
|
||||
|
||||
Ok(Value::List(List::with_items(keys)))
|
||||
}
|
||||
|
||||
fn r#type(&self) -> Type {
|
||||
Type::Function {
|
||||
parameter_types: vec![Type::Map],
|
||||
return_type: Box::new(Type::List(Box::new(Type::String))),
|
||||
}
|
||||
}
|
||||
}
|
14
src/error.rs
14
src/error.rs
@ -3,7 +3,7 @@
|
||||
//! To deal with errors from dependencies, either create a new error variant
|
||||
//! or use the ToolFailure variant if the error can only occur inside a tool.
|
||||
|
||||
use tree_sitter::{Node, Point};
|
||||
use tree_sitter::{LanguageError, Node, Point};
|
||||
|
||||
use crate::{value::Value, BuiltInFunction, Type};
|
||||
|
||||
@ -164,6 +164,8 @@ pub enum Error {
|
||||
},
|
||||
|
||||
SerdeJson(String),
|
||||
|
||||
ParserCancelled,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
@ -225,6 +227,12 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LanguageError> for Error {
|
||||
fn from(value: LanguageError) -> Self {
|
||||
Error::External(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for Error {
|
||||
fn from(value: PoisonError<T>) -> Self {
|
||||
Error::External(value.to_string())
|
||||
@ -412,6 +420,10 @@ impl fmt::Display for Error {
|
||||
source,
|
||||
} => write!(f, "{error} Occured at {location}: \"{source}\""),
|
||||
SerdeJson(message) => write!(f, "JSON processing error: {message}"),
|
||||
ParserCancelled => write!(
|
||||
f,
|
||||
"Parsing was cancelled either manually or because it took too long."
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
//! The top level of Dust's API with functions to interpret Dust code.
|
||||
//!
|
||||
//! You can use this library externally by calling either of the "eval"
|
||||
//! functions or by constructing your own Evaluator.
|
||||
use tree_sitter::{Parser, Tree as TSTree};
|
||||
|
||||
use crate::{language, AbstractTree, Map, Result, Root, Value};
|
||||
|
||||
/// Evaluate the given source code.
|
||||
///
|
||||
/// Returns a vector of results from evaluating the source code. Each comment
|
||||
/// and statemtent will have its own result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// assert_eq!(evaluate("1 + 2 + 3"), Ok(Value::Integer(6)));
|
||||
/// ```
|
||||
pub fn evaluate(source: &str) -> Result<Value> {
|
||||
let mut context = Map::new();
|
||||
|
||||
evaluate_with_context(source, &mut context)
|
||||
}
|
||||
|
||||
/// Evaluate the given source code with the given context.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// let mut context = Map::new();
|
||||
///
|
||||
/// context.set("one".into(), 1.into(), None);
|
||||
/// context.set("two".into(), 2.into(), None);
|
||||
/// context.set("three".into(), 3.into(), None);
|
||||
///
|
||||
/// let dust_code = "four = 4 one + two + three + four";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// evaluate_with_context(dust_code, &mut context),
|
||||
/// Ok(Value::Integer(10))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn evaluate_with_context(source: &str, context: &mut Map) -> Result<Value> {
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language()).unwrap();
|
||||
|
||||
Interpreter::parse(parser, context, source)?.run()
|
||||
}
|
||||
|
||||
/// A source code interpreter for the Dust language.
|
||||
pub struct Interpreter<'c, 's> {
|
||||
_parser: Parser,
|
||||
context: &'c mut Map,
|
||||
source: &'s str,
|
||||
syntax_tree: TSTree,
|
||||
abstract_tree: Root,
|
||||
}
|
||||
|
||||
impl<'c, 's> Interpreter<'c, 's> {
|
||||
pub fn parse(mut parser: Parser, context: &'c mut Map, source: &'s str) -> Result<Self> {
|
||||
let syntax_tree = parser.parse(source, None).unwrap();
|
||||
let abstract_tree = Root::from_syntax_node(source, syntax_tree.root_node(), context)?;
|
||||
|
||||
Ok(Interpreter {
|
||||
_parser: parser,
|
||||
context,
|
||||
source,
|
||||
syntax_tree,
|
||||
abstract_tree,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Value> {
|
||||
self.abstract_tree.run(self.source, self.context)
|
||||
}
|
||||
|
||||
pub fn syntax_tree(&self) -> String {
|
||||
self.syntax_tree.root_node().to_sexp()
|
||||
}
|
||||
}
|
109
src/interpret.rs
Normal file
109
src/interpret.rs
Normal file
@ -0,0 +1,109 @@
|
||||
//! The top level of Dust's API with functions to interpret Dust code.
|
||||
//!
|
||||
//! You can use this library externally by calling either of the "eval"
|
||||
//! functions or by constructing your own Evaluator.
|
||||
use tree_sitter::{Parser, Tree as TSTree};
|
||||
|
||||
use crate::{language, AbstractTree, Map, Result, Root, Value};
|
||||
|
||||
/// Interpret the given source code.
|
||||
///
|
||||
/// Returns a vector of results from evaluating the source code. Each comment
|
||||
/// and statemtent will have its own result.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// assert_eq!(interpret("1 + 2 + 3"), Ok(Value::Integer(6)));
|
||||
/// ```
|
||||
pub fn interpret(source: &str) -> Result<Value> {
|
||||
let mut context = Map::new();
|
||||
|
||||
interpret_with_context(source, &mut context)
|
||||
}
|
||||
|
||||
/// Interpret the given source code with the given context.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dust_lang::*;
|
||||
/// let mut context = Map::new();
|
||||
///
|
||||
/// context.set("one".into(), 1.into(), None);
|
||||
/// context.set("two".into(), 2.into(), None);
|
||||
/// context.set("three".into(), 3.into(), None);
|
||||
///
|
||||
/// let dust_code = "four = 4 one + two + three + four";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// interpret_with_context(dust_code, &mut context),
|
||||
/// Ok(Value::Integer(10))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn interpret_with_context(source: &str, context: &mut Map) -> Result<Value> {
|
||||
let mut parser = Parser::new();
|
||||
parser.set_language(language())?;
|
||||
|
||||
let mut interpreter = Interpreter::new(context, source)?;
|
||||
let value = interpreter.run()?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// A source code interpreter for the Dust language.
|
||||
pub struct Interpreter<'c, 's> {
|
||||
_parser: Parser,
|
||||
context: &'c mut Map,
|
||||
source: &'s str,
|
||||
syntax_tree: Option<TSTree>,
|
||||
abstract_tree: Option<Root>,
|
||||
}
|
||||
|
||||
impl<'c, 's> Interpreter<'c, 's> {
|
||||
pub fn new(context: &'c mut Map, source: &'s str) -> Result<Self> {
|
||||
let mut parser = Parser::new();
|
||||
|
||||
parser.set_language(language())?;
|
||||
|
||||
Ok(Interpreter {
|
||||
_parser: parser,
|
||||
context,
|
||||
source,
|
||||
syntax_tree: None,
|
||||
abstract_tree: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_source(&mut self, source: &'s str) {
|
||||
self.source = source;
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Value> {
|
||||
self.syntax_tree = self._parser.parse(self.source, self.syntax_tree.as_ref());
|
||||
self.abstract_tree = if let Some(syntax_tree) = &self.syntax_tree {
|
||||
Some(Root::from_syntax_node(
|
||||
self.source,
|
||||
syntax_tree.root_node(),
|
||||
&self.context,
|
||||
)?)
|
||||
} else {
|
||||
return Err(crate::Error::ParserCancelled);
|
||||
};
|
||||
|
||||
if let Some(abstract_tree) = &self.abstract_tree {
|
||||
abstract_tree.run(self.source, &self.context)
|
||||
} else {
|
||||
Ok(Value::Option(None))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syntax_tree(&self) -> Option<String> {
|
||||
if let Some(syntax_tree) = &self.syntax_tree {
|
||||
Some(syntax_tree.root_node().to_sexp())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@ pub use crate::{
|
||||
abstract_tree::*,
|
||||
built_in_functions::{BuiltInFunction, BUILT_IN_FUNCTIONS},
|
||||
error::*,
|
||||
evaluate::*,
|
||||
interpret::*,
|
||||
value::{function::Function, list::List, map::Map, Value},
|
||||
};
|
||||
|
||||
mod abstract_tree;
|
||||
pub mod built_in_functions;
|
||||
mod error;
|
||||
mod evaluate;
|
||||
mod interpret;
|
||||
mod value;
|
||||
|
||||
use tree_sitter::Language;
|
||||
|
@ -12,7 +12,7 @@ use tree_sitter::Parser as TSParser;
|
||||
|
||||
use std::{borrow::Cow, fs::read_to_string};
|
||||
|
||||
use dust_lang::{evaluate_with_context, language, Interpreter, Map, Value};
|
||||
use dust_lang::{interpret_with_context, language, Interpreter, Map, Value};
|
||||
|
||||
/// Command-line arguments to be parsed.
|
||||
#[derive(Parser, Debug)]
|
||||
@ -195,7 +195,7 @@ fn run_cli_shell() {
|
||||
|
||||
rl.add_history_entry(line).unwrap();
|
||||
|
||||
let eval_result = evaluate_with_context(line, &mut context);
|
||||
let eval_result = interpret_with_context(line, &mut context);
|
||||
|
||||
match eval_result {
|
||||
Ok(value) => println!("{value}"),
|
||||
|
182
src/tui/editor.rs
Normal file
182
src/tui/editor.rs
Normal file
@ -0,0 +1,182 @@
|
||||
use dust_lang::Result;
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
prelude::*,
|
||||
style::{Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::Display,
|
||||
fs::File,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
use tui_textarea::{CursorMove, Input, Key, TextArea};
|
||||
|
||||
use super::Action;
|
||||
|
||||
pub struct Editor<'a> {
|
||||
current: usize,
|
||||
buffers: Vec<Buffer<'a>>,
|
||||
message: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl<'a> Editor<'a> {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
current: 0,
|
||||
buffers: Vec::new(),
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn current_buffer(&self) -> &Buffer {
|
||||
&self.buffers[self.current]
|
||||
}
|
||||
|
||||
pub fn add_buffer(&mut self, buffer: Buffer<'a>) {
|
||||
self.buffers.push(buffer);
|
||||
}
|
||||
|
||||
pub fn run(&mut self, frame: &mut Frame, areas: &[Rect]) -> Option<Action> {
|
||||
let buffer = &self.buffers[self.current];
|
||||
let textarea = &buffer.textarea;
|
||||
let widget = textarea.widget();
|
||||
|
||||
frame.render_widget(widget, areas[0]);
|
||||
|
||||
// Render status line
|
||||
let modified = if buffer.modified { " [modified]" } else { "" };
|
||||
let slot = format!("[{}/{}]", self.current + 1, self.buffers.len());
|
||||
let path_text = if let Some(path) = &buffer.path {
|
||||
format!(" {}{} ", path.display(), modified)
|
||||
} else {
|
||||
"scratch".to_string()
|
||||
};
|
||||
let (row, col) = textarea.cursor();
|
||||
let cursor = format!("({},{})", row + 1, col + 1);
|
||||
|
||||
let status_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(slot.len() as u16),
|
||||
Constraint::Min(1),
|
||||
Constraint::Length(cursor.len() as u16),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(areas[1]);
|
||||
let status_style = Style::default().add_modifier(Modifier::REVERSED);
|
||||
frame.render_widget(Paragraph::new(slot).style(status_style), status_chunks[0]);
|
||||
frame.render_widget(
|
||||
Paragraph::new(path_text).style(status_style),
|
||||
status_chunks[1],
|
||||
);
|
||||
frame.render_widget(Paragraph::new(cursor).style(status_style), status_chunks[2]);
|
||||
|
||||
// Render message at bottom
|
||||
let message = if let Some(message) = self.message.take() {
|
||||
Line::from(Span::raw(message))
|
||||
} else {
|
||||
Line::from(vec![
|
||||
Span::raw("Press "),
|
||||
Span::styled("^Q", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to quit, "),
|
||||
Span::styled("^S", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to save, "),
|
||||
Span::styled("^G", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to search, "),
|
||||
Span::styled("^T", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to switch buffer "),
|
||||
Span::styled("^R", Style::default().add_modifier(Modifier::BOLD)),
|
||||
Span::raw(" to run"),
|
||||
])
|
||||
};
|
||||
frame.render_widget(Paragraph::new(message), areas[2]);
|
||||
|
||||
match crossterm::event::read().unwrap().into() {
|
||||
Input {
|
||||
key: Key::Char('r'),
|
||||
ctrl: true,
|
||||
..
|
||||
} => return Some(Action::Submit),
|
||||
Input {
|
||||
key: Key::Char('q'),
|
||||
ctrl: true,
|
||||
..
|
||||
} => return Some(Action::Quit),
|
||||
Input {
|
||||
key: Key::Char('t'),
|
||||
ctrl: true,
|
||||
..
|
||||
} => {
|
||||
self.current = (self.current + 1) % self.buffers.len();
|
||||
self.message = Some(format!("Switched to buffer #{}", self.current + 1).into());
|
||||
}
|
||||
Input {
|
||||
key: Key::Char('s'),
|
||||
ctrl: true,
|
||||
..
|
||||
} => {
|
||||
self.buffers[self.current].save().unwrap();
|
||||
self.message = Some("Saved!".into());
|
||||
}
|
||||
input => {
|
||||
let buffer = &mut self.buffers[self.current];
|
||||
buffer.modified = buffer.textarea.input(input);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Buffer<'a> {
|
||||
textarea: TextArea<'a>,
|
||||
path: Option<PathBuf>,
|
||||
modified: bool,
|
||||
}
|
||||
|
||||
impl<'a> Buffer<'a> {
|
||||
pub fn new(content: String) -> Result<Self> {
|
||||
let mut textarea = TextArea::new(content.lines().map(|line| line.to_string()).collect());
|
||||
|
||||
textarea.set_line_number_style(Style::default().fg(Color::DarkGray));
|
||||
|
||||
Ok(Self {
|
||||
textarea,
|
||||
path: None,
|
||||
modified: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.textarea.lines().join("\n")
|
||||
}
|
||||
|
||||
fn save(&mut self) -> io::Result<()> {
|
||||
if !self.modified {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = if let Some(path) = &self.path {
|
||||
File::create(path)?
|
||||
} else {
|
||||
File::create("/tmp/dust_buffer")?
|
||||
};
|
||||
|
||||
let mut writer = io::BufWriter::new(file);
|
||||
|
||||
for line in self.textarea.lines() {
|
||||
writer.write_all(line.as_bytes())?;
|
||||
writer.write_all(b"\n")?;
|
||||
}
|
||||
|
||||
self.modified = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
84
src/tui/log.rs
Normal file
84
src/tui/log.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use directories::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
pub use tracing::error;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PROJECT_NAME: String = env!("CARGO_CRATE_NAME").to_uppercase().to_string();
|
||||
pub static ref DATA_FOLDER: Option<PathBuf> =
|
||||
std::env::var(format!("{}_DATA", PROJECT_NAME.clone()))
|
||||
.ok()
|
||||
.map(PathBuf::from);
|
||||
pub static ref LOG_ENV: String = format!("{}_LOGLEVEL", PROJECT_NAME.clone());
|
||||
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
|
||||
}
|
||||
|
||||
fn project_directory() -> Option<ProjectDirs> {
|
||||
ProjectDirs::from("io", "jeffa", env!("CARGO_PKG_NAME"))
|
||||
}
|
||||
|
||||
pub fn get_data_dir() -> PathBuf {
|
||||
let directory = if let Some(s) = DATA_FOLDER.clone() {
|
||||
s
|
||||
} else if let Some(proj_dirs) = project_directory() {
|
||||
proj_dirs.data_local_dir().to_path_buf()
|
||||
} else {
|
||||
PathBuf::from(".").join(".data")
|
||||
};
|
||||
directory
|
||||
}
|
||||
|
||||
pub fn initialize_logging() -> Result<()> {
|
||||
let directory = get_data_dir();
|
||||
std::fs::create_dir_all(directory.clone())?;
|
||||
let log_path = directory.join(LOG_FILE.clone());
|
||||
let log_file = std::fs::File::create(log_path)?;
|
||||
std::env::set_var(
|
||||
"RUST_LOG",
|
||||
std::env::var("RUST_LOG")
|
||||
.or_else(|_| std::env::var(LOG_ENV.clone()))
|
||||
.unwrap_or_else(|_| format!("{}=info", env!("CARGO_CRATE_NAME"))),
|
||||
);
|
||||
let file_subscriber = tracing_subscriber::fmt::layer()
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_writer(log_file)
|
||||
.with_target(false)
|
||||
.with_ansi(false)
|
||||
.with_filter(tracing_subscriber::filter::EnvFilter::from_default_env());
|
||||
tracing_subscriber::registry()
|
||||
.with(file_subscriber)
|
||||
.with(ErrorLayer::default())
|
||||
.init();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Similar to the `std::dbg!` macro, but generates `tracing` events rather
|
||||
/// than printing to stdout.
|
||||
///
|
||||
/// By default, the verbosity level for the generated events is `DEBUG`, but
|
||||
/// this can be customized.
|
||||
#[macro_export]
|
||||
macro_rules! trace_dbg {
|
||||
(target: $target:expr, level: $level:expr, $ex:expr) => {{
|
||||
match $ex {
|
||||
value => {
|
||||
tracing::event!(target: $target, $level, ?value, stringify!($ex));
|
||||
value
|
||||
}
|
||||
}
|
||||
}};
|
||||
(level: $level:expr, $ex:expr) => {
|
||||
trace_dbg!(target: module_path!(), level: $level, $ex)
|
||||
};
|
||||
(target: $target:expr, $ex:expr) => {
|
||||
trace_dbg!(target: $target, level: tracing::Level::DEBUG, $ex)
|
||||
};
|
||||
($ex:expr) => {
|
||||
trace_dbg!(level: tracing::Level::DEBUG, $ex)
|
||||
};
|
||||
}
|
245
src/tui/mod.rs
Normal file
245
src/tui/mod.rs
Normal file
@ -0,0 +1,245 @@
|
||||
mod log;
|
||||
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::{
|
||||
cursor,
|
||||
event::{
|
||||
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
|
||||
Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent,
|
||||
},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ratatui::backend::CrosstermBackend as Backend;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum Event {
|
||||
Init,
|
||||
Quit,
|
||||
Error,
|
||||
Closed,
|
||||
Tick,
|
||||
Render,
|
||||
FocusGained,
|
||||
FocusLost,
|
||||
Paste(String),
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize(u16, u16),
|
||||
}
|
||||
|
||||
pub struct Tui {
|
||||
pub terminal: ratatui::Terminal<Backend<std::io::Stderr>>,
|
||||
pub task: JoinHandle<()>,
|
||||
pub cancellation_token: CancellationToken,
|
||||
pub event_rx: UnboundedReceiver<Event>,
|
||||
pub event_tx: UnboundedSender<Event>,
|
||||
pub frame_rate: f64,
|
||||
pub tick_rate: f64,
|
||||
pub mouse: bool,
|
||||
pub paste: bool,
|
||||
}
|
||||
|
||||
impl Tui {
|
||||
pub fn new() -> Result<Self> {
|
||||
let tick_rate = 4.0;
|
||||
let frame_rate = 60.0;
|
||||
let terminal = ratatui::Terminal::new(Backend::new(std::io::stderr()))?;
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let task = tokio::spawn(async {});
|
||||
let mouse = false;
|
||||
let paste = false;
|
||||
Ok(Self {
|
||||
terminal,
|
||||
task,
|
||||
cancellation_token,
|
||||
event_rx,
|
||||
event_tx,
|
||||
frame_rate,
|
||||
tick_rate,
|
||||
mouse,
|
||||
paste,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tick_rate(mut self, tick_rate: f64) -> Self {
|
||||
self.tick_rate = tick_rate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn frame_rate(mut self, frame_rate: f64) -> Self {
|
||||
self.frame_rate = frame_rate;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mouse(mut self, mouse: bool) -> Self {
|
||||
self.mouse = mouse;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn paste(mut self, paste: bool) -> Self {
|
||||
self.paste = paste;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
let tick_delay = std::time::Duration::from_secs_f64(1.0 / self.tick_rate);
|
||||
let render_delay = std::time::Duration::from_secs_f64(1.0 / self.frame_rate);
|
||||
self.cancel();
|
||||
self.cancellation_token = CancellationToken::new();
|
||||
let _cancellation_token = self.cancellation_token.clone();
|
||||
let _event_tx = self.event_tx.clone();
|
||||
self.task = tokio::spawn(async move {
|
||||
let mut reader = crossterm::event::EventStream::new();
|
||||
let mut tick_interval = tokio::time::interval(tick_delay);
|
||||
let mut render_interval = tokio::time::interval(render_delay);
|
||||
_event_tx.send(Event::Init).unwrap();
|
||||
loop {
|
||||
let tick_delay = tick_interval.tick();
|
||||
let render_delay = render_interval.tick();
|
||||
let crossterm_event = reader.next().fuse();
|
||||
tokio::select! {
|
||||
_ = _cancellation_token.cancelled() => {
|
||||
break;
|
||||
}
|
||||
maybe_event = crossterm_event => {
|
||||
match maybe_event {
|
||||
Some(Ok(evt)) => {
|
||||
match evt {
|
||||
CrosstermEvent::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
_event_tx.send(Event::Key(key)).unwrap();
|
||||
}
|
||||
},
|
||||
CrosstermEvent::Mouse(mouse) => {
|
||||
_event_tx.send(Event::Mouse(mouse)).unwrap();
|
||||
},
|
||||
CrosstermEvent::Resize(x, y) => {
|
||||
_event_tx.send(Event::Resize(x, y)).unwrap();
|
||||
},
|
||||
CrosstermEvent::FocusLost => {
|
||||
_event_tx.send(Event::FocusLost).unwrap();
|
||||
},
|
||||
CrosstermEvent::FocusGained => {
|
||||
_event_tx.send(Event::FocusGained).unwrap();
|
||||
},
|
||||
CrosstermEvent::Paste(s) => {
|
||||
_event_tx.send(Event::Paste(s)).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
_event_tx.send(Event::Error).unwrap();
|
||||
}
|
||||
None => {},
|
||||
}
|
||||
},
|
||||
_ = tick_delay => {
|
||||
_event_tx.send(Event::Tick).unwrap();
|
||||
},
|
||||
_ = render_delay => {
|
||||
_event_tx.send(Event::Render).unwrap();
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<()> {
|
||||
self.cancel();
|
||||
let mut counter = 0;
|
||||
while !self.task.is_finished() {
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
counter += 1;
|
||||
if counter > 50 {
|
||||
self.task.abort();
|
||||
}
|
||||
if counter > 100 {
|
||||
log::error!("Failed to abort task in 100 milliseconds for unknown reason");
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enter(&mut self) -> Result<()> {
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||
if self.mouse {
|
||||
crossterm::execute!(std::io::stderr(), EnableMouseCapture)?;
|
||||
}
|
||||
if self.paste {
|
||||
crossterm::execute!(std::io::stderr(), EnableBracketedPaste)?;
|
||||
}
|
||||
self.start();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn exit(&mut self) -> Result<()> {
|
||||
self.stop()?;
|
||||
if crossterm::terminal::is_raw_mode_enabled()? {
|
||||
self.flush()?;
|
||||
if self.paste {
|
||||
crossterm::execute!(std::io::stderr(), DisableBracketedPaste)?;
|
||||
}
|
||||
if self.mouse {
|
||||
crossterm::execute!(std::io::stderr(), DisableMouseCapture)?;
|
||||
}
|
||||
crossterm::execute!(std::io::stderr(), LeaveAlternateScreen, cursor::Show)?;
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cancel(&self) {
|
||||
self.cancellation_token.cancel();
|
||||
}
|
||||
|
||||
pub fn suspend(&mut self) -> Result<()> {
|
||||
self.exit()?;
|
||||
#[cfg(not(windows))]
|
||||
signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resume(&mut self) -> Result<()> {
|
||||
self.enter()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn next(&mut self) -> Option<Event> {
|
||||
self.event_rx.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Tui {
|
||||
type Target = ratatui::Terminal<Backend<std::io::Stderr>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Tui {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.terminal
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Tui {
|
||||
fn drop(&mut self) {
|
||||
self.exit().unwrap();
|
||||
}
|
||||
}
|
33
src/tui/output_display.rs
Normal file
33
src/tui/output_display.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use dust_lang::Value;
|
||||
use ratatui::{prelude::Rect, widgets::Paragraph, Frame};
|
||||
|
||||
pub struct OutputDisplay {
|
||||
values: Vec<Value>,
|
||||
}
|
||||
|
||||
impl OutputDisplay {
|
||||
pub fn new() -> Self {
|
||||
OutputDisplay { values: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn add_value(&mut self, value: Value) {
|
||||
self.values.push(value);
|
||||
}
|
||||
|
||||
pub fn run(&self, frame: &mut Frame, area: Rect) {
|
||||
for value in &self.values {
|
||||
match value {
|
||||
Value::List(_) => todo!(),
|
||||
Value::Map(_) => todo!(),
|
||||
Value::Function(_) => todo!(),
|
||||
Value::String(string) => frame.render_widget(Paragraph::new(string.as_str()), area),
|
||||
Value::Float(_) => todo!(),
|
||||
Value::Integer(integer) => {
|
||||
frame.render_widget(Paragraph::new(integer.to_string()), area)
|
||||
}
|
||||
Value::Boolean(_) => todo!(),
|
||||
Value::Option(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
src/tui/search_box.rs
Normal file
0
src/tui/search_box.rs
Normal file
@ -6,7 +6,7 @@ use dust_lang::*;
|
||||
fn r#async() {
|
||||
let file_contents = read_to_string("examples/async.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -14,14 +14,14 @@ fn r#async() {
|
||||
fn async_download() {
|
||||
let file_contents = read_to_string("examples/async_download.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clue_solver() {
|
||||
let file_contents = read_to_string("examples/clue_solver.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -29,89 +29,89 @@ fn clue_solver() {
|
||||
fn fetch() {
|
||||
let file_contents = read_to_string("examples/fetch.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fibonacci() {
|
||||
let file_contents = read_to_string("examples/fibonacci.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fizzbuzz() {
|
||||
let file_contents = read_to_string("examples/fizzbuzz.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_loop() {
|
||||
let file_contents = read_to_string("examples/for_loop.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
let file_contents = read_to_string("examples/hello_world.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jq_data() {
|
||||
let file_contents = read_to_string("examples/jq_data.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
let file_contents = read_to_string("examples/list.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map() {
|
||||
let file_contents = read_to_string("examples/map.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random() {
|
||||
let file_contents = read_to_string("examples/random.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sea_creatures() {
|
||||
let file_contents = read_to_string("examples/sea_creatures.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variables() {
|
||||
let file_contents = read_to_string("examples/variables.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_loop() {
|
||||
let file_contents = read_to_string("examples/while_loop.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#yield() {
|
||||
let file_contents = read_to_string("examples/yield.ds").unwrap();
|
||||
|
||||
evaluate(&file_contents).unwrap();
|
||||
interpret(&file_contents).unwrap();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user