Toy with chumsky and ariadne
This commit is contained in:
parent
6b0bb0016f
commit
966983920e
@ -66,6 +66,18 @@ impl AbstractNode for FunctionCall {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (type_parameter, expression) in parameter_types.iter().zip(self.arguments.iter()) {
|
||||||
|
let actual = expression.node.expected_type(context)?;
|
||||||
|
|
||||||
|
type_parameter.node.check(&actual).map_err(|conflict| {
|
||||||
|
ValidationError::TypeCheck {
|
||||||
|
conflict,
|
||||||
|
actual_position: expression.position,
|
||||||
|
expected_position: type_parameter.position,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::ExpectedFunction {
|
Err(ValidationError::ExpectedFunction {
|
||||||
|
@ -180,7 +180,7 @@ impl Display for Type {
|
|||||||
write!(f, ") : {}", return_type.node)
|
write!(f, ") : {}", return_type.node)
|
||||||
}
|
}
|
||||||
Type::Structure { name, .. } => write!(f, "{name}"),
|
Type::Structure { name, .. } => write!(f, "{name}"),
|
||||||
Type::Argument(_) => todo!(),
|
Type::Argument(identifier) => write!(f, "{identifier}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,8 @@ impl AbstractNode for ValueNode {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ValueNode::ParsedFunction {
|
if let ValueNode::ParsedFunction {
|
||||||
@ -150,6 +152,8 @@ impl AbstractNode for ValueNode {
|
|||||||
actual_position: body.position,
|
actual_position: body.position,
|
||||||
expected_position: return_type.position,
|
expected_position: return_type.position,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ValueNode::Structure {
|
if let ValueNode::Structure {
|
||||||
|
@ -13,7 +13,7 @@ use crate::{
|
|||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
pub enum BuiltInFunction {
|
pub enum BuiltInFunction {
|
||||||
ReadLine,
|
ReadLine,
|
||||||
Sleep,
|
Sleep,
|
||||||
@ -23,9 +23,9 @@ pub enum BuiltInFunction {
|
|||||||
impl BuiltInFunction {
|
impl BuiltInFunction {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
BuiltInFunction::ReadLine => todo!(),
|
BuiltInFunction::ReadLine => "__READ_LINE__",
|
||||||
BuiltInFunction::Sleep => todo!(),
|
BuiltInFunction::Sleep => "__SLEEP__",
|
||||||
BuiltInFunction::WriteLine => todo!(),
|
BuiltInFunction::WriteLine => "__WRITE_LINE__",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@ use std::{
|
|||||||
|
|
||||||
use chumsky::prelude::*;
|
use chumsky::prelude::*;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::{built_in_functions::BuiltInFunction, error::Error};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Token<'src> {
|
pub enum Token<'src> {
|
||||||
Boolean(bool),
|
Boolean(bool),
|
||||||
BuiltInIdentifier(BuiltInIdentifier),
|
BuiltInFunction(BuiltInFunction),
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
Float(f64),
|
Float(f64),
|
||||||
String(&'src str),
|
String(&'src str),
|
||||||
@ -24,7 +24,7 @@ impl<'src> Display for Token<'src> {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Token::Boolean(boolean) => write!(f, "{boolean}"),
|
Token::Boolean(boolean) => write!(f, "{boolean}"),
|
||||||
Token::BuiltInIdentifier(built_in_identifier) => write!(f, "{built_in_identifier}"),
|
Token::BuiltInFunction(built_in_identifier) => write!(f, "{built_in_identifier}"),
|
||||||
Token::Integer(integer) => write!(f, "{integer}"),
|
Token::Integer(integer) => write!(f, "{integer}"),
|
||||||
Token::Float(float) => write!(f, "{float}"),
|
Token::Float(float) => write!(f, "{float}"),
|
||||||
Token::String(string) => write!(f, "{string}"),
|
Token::String(string) => write!(f, "{string}"),
|
||||||
@ -36,23 +36,6 @@ impl<'src> Display for Token<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
||||||
pub enum BuiltInIdentifier {
|
|
||||||
ReadLine,
|
|
||||||
Sleep,
|
|
||||||
WriteLine,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for BuiltInIdentifier {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
BuiltInIdentifier::ReadLine => write!(f, "__READ_LINE__"),
|
|
||||||
BuiltInIdentifier::Sleep => write!(f, "__SLEEP__"),
|
|
||||||
BuiltInIdentifier::WriteLine => write!(f, "__WRITE_LINE__"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum Keyword {
|
pub enum Keyword {
|
||||||
Any,
|
Any,
|
||||||
@ -315,11 +298,11 @@ pub fn lexer<'src>() -> impl Parser<
|
|||||||
.map(Token::Keyword);
|
.map(Token::Keyword);
|
||||||
|
|
||||||
let built_in_identifier = choice((
|
let built_in_identifier = choice((
|
||||||
just("__READ_LINE__").to(BuiltInIdentifier::ReadLine),
|
just(BuiltInFunction::ReadLine.name()).to(BuiltInFunction::ReadLine),
|
||||||
just("__SLEEP__").to(BuiltInIdentifier::Sleep),
|
just(BuiltInFunction::Sleep.name()).to(BuiltInFunction::Sleep),
|
||||||
just("__WRITE_LINE__").to(BuiltInIdentifier::WriteLine),
|
just(BuiltInFunction::WriteLine.name()).to(BuiltInFunction::WriteLine),
|
||||||
))
|
))
|
||||||
.map(Token::BuiltInIdentifier);
|
.map(Token::BuiltInFunction);
|
||||||
|
|
||||||
choice((
|
choice((
|
||||||
boolean,
|
boolean,
|
||||||
|
@ -6,7 +6,7 @@ pub mod lexer;
|
|||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
use std::{ops::Range, rc::Rc};
|
use std::{cell::RefCell, ops::Range, rc::Rc, vec};
|
||||||
|
|
||||||
use abstract_tree::Type;
|
use abstract_tree::Type;
|
||||||
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
||||||
@ -16,20 +16,88 @@ use lexer::lex;
|
|||||||
use parser::parse;
|
use parser::parse;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
|
|
||||||
pub fn interpret(source_id: Rc<String>, source: &str) -> Result<Option<Value>, InterpreterError> {
|
pub fn interpret<'src>(source_id: &str, source: &str) -> Result<Option<Value>, InterpreterError> {
|
||||||
let mut interpreter = Interpreter::new(Context::new());
|
let mut interpreter = Interpreter::new(Context::new());
|
||||||
|
|
||||||
interpreter.load_std()?;
|
interpreter.run(Rc::new(source_id.to_string()), Rc::from(source))
|
||||||
interpreter.run(source_id, source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret_without_std(
|
pub fn interpret_without_std(
|
||||||
source_id: Rc<String>,
|
source_id: &str,
|
||||||
source: &str,
|
source: &str,
|
||||||
) -> Result<Option<Value>, InterpreterError> {
|
) -> Result<Option<Value>, InterpreterError> {
|
||||||
let mut interpreter = Interpreter::new(Context::new());
|
let mut interpreter = Interpreter::new(Context::new());
|
||||||
|
|
||||||
interpreter.run(source_id, source)
|
interpreter.run(Rc::new(source_id.to_string()), Rc::from(source))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Interpreter {
|
||||||
|
context: Context,
|
||||||
|
sources: Rc<RefCell<Vec<(Rc<String>, Rc<str>)>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpreter {
|
||||||
|
pub fn new(context: Context) -> Self {
|
||||||
|
Interpreter {
|
||||||
|
context,
|
||||||
|
sources: Rc::new(RefCell::new(Vec::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
&mut self,
|
||||||
|
source_id: Rc<String>,
|
||||||
|
source: Rc<str>,
|
||||||
|
) -> Result<Option<Value>, InterpreterError> {
|
||||||
|
let tokens = lex(source.as_ref()).map_err(|errors| InterpreterError {
|
||||||
|
source_id: source_id.clone(),
|
||||||
|
errors,
|
||||||
|
})?;
|
||||||
|
let abstract_tree = parse(&tokens).map_err(|errors| InterpreterError {
|
||||||
|
source_id: source_id.clone(),
|
||||||
|
errors,
|
||||||
|
})?;
|
||||||
|
let value_option = abstract_tree
|
||||||
|
.run(&self.context)
|
||||||
|
.map_err(|errors| InterpreterError {
|
||||||
|
source_id: source_id.clone(),
|
||||||
|
errors,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.sources.borrow_mut().push((source_id, source.into()));
|
||||||
|
|
||||||
|
Ok(value_option)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_all<T: IntoIterator<Item = (Rc<String>, Rc<str>)>>(
|
||||||
|
&mut self,
|
||||||
|
sources: T,
|
||||||
|
) -> Result<Option<Value>, InterpreterError> {
|
||||||
|
let mut previous_value_option = None;
|
||||||
|
|
||||||
|
for (source_id, source) in sources {
|
||||||
|
previous_value_option = self.run(source_id.clone(), source)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(previous_value_option)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_std(&mut self) -> Result<Option<Value>, InterpreterError> {
|
||||||
|
self.run_all([
|
||||||
|
(
|
||||||
|
Rc::new("std/io.ds".to_string()),
|
||||||
|
Rc::from(include_str!("../../std/io.ds")),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Rc::new("std/thread.ds".to_string()),
|
||||||
|
Rc::from(include_str!("../../std/thread.ds")),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sources(&self) -> vec::IntoIter<(Rc<String>, Rc<str>)> {
|
||||||
|
self.sources.borrow().clone().into_iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -45,7 +113,7 @@ impl InterpreterError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InterpreterError {
|
impl InterpreterError {
|
||||||
pub fn build_reports<'id>(self) -> Vec<Report<'id, (Rc<String>, Range<usize>)>> {
|
pub fn build_reports<'a>(self) -> Vec<Report<'a, (Rc<String>, Range<usize>)>> {
|
||||||
let mut reports = Vec::new();
|
let mut reports = Vec::new();
|
||||||
|
|
||||||
for error in self.errors {
|
for error in self.errors {
|
||||||
@ -170,8 +238,6 @@ impl InterpreterError {
|
|||||||
} => {
|
} => {
|
||||||
let TypeConflict { actual, expected } = conflict;
|
let TypeConflict { actual, expected } = conflict;
|
||||||
|
|
||||||
builder = builder.with_message("A type conflict was found.");
|
|
||||||
|
|
||||||
builder.add_labels([
|
builder.add_labels([
|
||||||
Label::new((
|
Label::new((
|
||||||
self.source_id.clone(),
|
self.source_id.clone(),
|
||||||
@ -257,46 +323,3 @@ impl InterpreterError {
|
|||||||
reports
|
reports
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Interpreter {
|
|
||||||
context: Context,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Interpreter {
|
|
||||||
pub fn new(context: Context) -> Self {
|
|
||||||
Interpreter { context }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(
|
|
||||||
&mut self,
|
|
||||||
source_id: Rc<String>,
|
|
||||||
source: &str,
|
|
||||||
) -> Result<Option<Value>, InterpreterError> {
|
|
||||||
let tokens = lex(source).map_err(|errors| InterpreterError {
|
|
||||||
source_id: source_id.clone(),
|
|
||||||
errors,
|
|
||||||
})?;
|
|
||||||
let abstract_tree = parse(&tokens).map_err(|errors| InterpreterError {
|
|
||||||
source_id: source_id.clone(),
|
|
||||||
errors,
|
|
||||||
})?;
|
|
||||||
let value_option = abstract_tree
|
|
||||||
.run(&self.context)
|
|
||||||
.map_err(|errors| InterpreterError { source_id, errors })?;
|
|
||||||
|
|
||||||
Ok(value_option)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_std(&mut self) -> Result<(), InterpreterError> {
|
|
||||||
self.run(
|
|
||||||
Rc::new("std/io.ds".to_string()),
|
|
||||||
include_str!("../../std/io.ds"),
|
|
||||||
)?;
|
|
||||||
self.run(
|
|
||||||
Rc::new("std/io.ds".to_string()),
|
|
||||||
include_str!("../../std/thread.ds"),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,9 +4,8 @@ use chumsky::{input::SpannedInput, pratt::*, prelude::*};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
abstract_tree::*,
|
abstract_tree::*,
|
||||||
built_in_functions::BuiltInFunction,
|
|
||||||
error::Error,
|
error::Error,
|
||||||
lexer::{BuiltInIdentifier, Control, Keyword, Operator, Token},
|
lexer::{Control, Keyword, Operator, Token},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ParserInput<'src> =
|
pub type ParserInput<'src> =
|
||||||
@ -256,10 +255,7 @@ pub fn parser<'src>() -> impl Parser<
|
|||||||
|
|
||||||
let built_in_function = {
|
let built_in_function = {
|
||||||
select! {
|
select! {
|
||||||
Token::BuiltInIdentifier(built_in_identifier) => {
|
Token::BuiltInFunction(built_in_function) => built_in_function,
|
||||||
match built_in_identifier {BuiltInIdentifier::ReadLine=>BuiltInFunction::ReadLine,BuiltInIdentifier::WriteLine=>BuiltInFunction::WriteLine,
|
|
||||||
BuiltInIdentifier::Sleep => BuiltInFunction::Sleep, }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.map_with(|built_in_function, state| {
|
.map_with(|built_in_function, state| {
|
||||||
@ -582,7 +578,7 @@ pub fn parser<'src>() -> impl Parser<
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::lexer::lex;
|
use crate::{built_in_functions::BuiltInFunction, lexer::lex};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -1,27 +1,22 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dust_lang::*;
|
use dust_lang::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn logic() {
|
fn logic() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "1 == 1").unwrap(),
|
interpret("test", "1 == 1").unwrap(),
|
||||||
Some(Value::boolean(true))
|
Some(Value::boolean(true))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "('42' == '42') && (42 != 0)").unwrap(),
|
interpret("test", "('42' == '42') && (42 != 0)").unwrap(),
|
||||||
Some(Value::boolean(true))
|
Some(Value::boolean(true))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn math() {
|
fn math() {
|
||||||
|
assert_eq!(interpret("test", "1 + 1").unwrap(), Some(Value::integer(2)));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "1 + 1").unwrap(),
|
interpret("test", "2 * (21 + 19 + 1 * 2) / 2").unwrap(),
|
||||||
Some(Value::integer(2))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
interpret(Rc::new("test".to_string()), "2 * (21 + 19 + 1 * 2) / 2").unwrap(),
|
|
||||||
Some(Value::integer(42))
|
Some(Value::integer(42))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -29,7 +24,7 @@ fn math() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn list_index() {
|
fn list_index() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "foo = [1, 2, 3]; foo[2]").unwrap(),
|
interpret("test", "foo = [1, 2, 3]; foo[2]").unwrap(),
|
||||||
Some(Value::integer(3))
|
Some(Value::integer(3))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -37,11 +32,11 @@ fn list_index() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn map_index() {
|
fn map_index() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "{ x = 3 }.x").unwrap(),
|
interpret("test", "{ x = 3 }.x").unwrap(),
|
||||||
Some(Value::integer(3))
|
Some(Value::integer(3))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "foo = { x = 3 }; foo.x").unwrap(),
|
interpret("test", "foo = { x = 3 }; foo.x").unwrap(),
|
||||||
Some(Value::integer(3))
|
Some(Value::integer(3))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dust_lang::{
|
use dust_lang::{
|
||||||
abstract_tree::Identifier,
|
abstract_tree::Identifier,
|
||||||
error::{Error, ValidationError},
|
error::{Error, ValidationError},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dust_lang::interpret;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_call() {
|
fn function_call() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
foobar = (message : str) str { message }
|
foobar = (message : str) str { message }
|
||||||
foobar('Hiya')
|
foobar('Hiya')
|
||||||
@ -24,7 +24,7 @@ fn function_call() {
|
|||||||
fn call_empty_function() {
|
fn call_empty_function() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
foobar = (message : str) none {}
|
foobar = (message : str) none {}
|
||||||
foobar('Hiya')
|
foobar('Hiya')
|
||||||
@ -38,7 +38,7 @@ fn call_empty_function() {
|
|||||||
fn callback() {
|
fn callback() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
foobar = (cb: fn() -> str) str {
|
foobar = (cb: fn() -> str) str {
|
||||||
cb()
|
cb()
|
||||||
@ -52,17 +52,14 @@ fn callback() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn built_in_function_call() {
|
fn built_in_function_call() {
|
||||||
assert_eq!(
|
assert_eq!(interpret("test", "io.write_line('Hiya')"), Ok(None));
|
||||||
interpret(Rc::new("test".to_string()), "io.write_line('Hiya')"),
|
|
||||||
Ok(None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn function_context_does_not_capture_values() {
|
fn function_context_does_not_capture_values() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
x = 1
|
x = 1
|
||||||
|
|
||||||
@ -79,7 +76,7 @@ fn function_context_does_not_capture_values() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
x = 1
|
x = 1
|
||||||
foo = (x: int) int { x }
|
foo = (x: int) int { x }
|
||||||
@ -94,7 +91,7 @@ fn function_context_does_not_capture_values() {
|
|||||||
fn function_context_captures_functions() {
|
fn function_context_captures_functions() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
bar = () int { 2 }
|
bar = () int { 2 }
|
||||||
foo = () int { bar() }
|
foo = () int { bar() }
|
||||||
@ -109,7 +106,7 @@ fn function_context_captures_functions() {
|
|||||||
fn recursion() {
|
fn recursion() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
fib = (i: int) int {
|
fib = (i: int) int {
|
||||||
if i <= 1 {
|
if i <= 1 {
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dust_lang::*;
|
use dust_lang::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn async_block() {
|
fn async_block() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
x = 41
|
x = 41
|
||||||
async {
|
async {
|
||||||
@ -24,7 +22,7 @@ fn async_block() {
|
|||||||
fn loops_and_breaks() {
|
fn loops_and_breaks() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
i = 0
|
i = 0
|
||||||
loop {
|
loop {
|
||||||
@ -41,7 +39,7 @@ fn loops_and_breaks() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
foobar = {
|
foobar = {
|
||||||
while true {
|
while true {
|
||||||
@ -60,7 +58,7 @@ fn loops_and_breaks() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn r#if() {
|
fn r#if() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "if true { 'foobar' }"),
|
interpret("test", "if true { 'foobar' }"),
|
||||||
Ok(Some(Value::string("foobar".to_string())))
|
Ok(Some(Value::string("foobar".to_string())))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -68,10 +66,7 @@ fn r#if() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn if_else() {
|
fn if_else() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret("test", "if false { 'foo' } else { 'bar' }"),
|
||||||
Rc::new("test".to_string()),
|
|
||||||
"if false { 'foo' } else { 'bar' }"
|
|
||||||
),
|
|
||||||
Ok(Some(Value::string("bar".to_string())))
|
Ok(Some(Value::string("bar".to_string())))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dust_lang::{
|
use dust_lang::{
|
||||||
abstract_tree::{Identifier, Type},
|
abstract_tree::{Identifier, Type},
|
||||||
error::{Error, TypeConflict, ValidationError},
|
error::{Error, TypeConflict, ValidationError},
|
||||||
*,
|
interpret, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_structure() {
|
fn simple_structure() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
struct Foo {
|
struct Foo {
|
||||||
bar : int,
|
bar : int,
|
||||||
@ -36,7 +35,7 @@ fn simple_structure() {
|
|||||||
fn field_type_error() {
|
fn field_type_error() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
struct Foo {
|
struct Foo {
|
||||||
bar : int,
|
bar : int,
|
||||||
@ -67,7 +66,7 @@ fn field_type_error() {
|
|||||||
fn nested_structure() {
|
fn nested_structure() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
struct Bar {
|
struct Bar {
|
||||||
baz : int
|
baz : int
|
||||||
@ -100,7 +99,7 @@ fn nested_structure() {
|
|||||||
fn undefined_struct() {
|
fn undefined_struct() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret(
|
||||||
Rc::new("test".to_string()),
|
"test",
|
||||||
"
|
"
|
||||||
Foo {
|
Foo {
|
||||||
bar = 42
|
bar = 42
|
||||||
@ -110,7 +109,7 @@ fn undefined_struct() {
|
|||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.errors(),
|
.errors(),
|
||||||
&vec![Error::Validation {
|
&vec![Error::Validation {
|
||||||
error: error::ValidationError::VariableNotFound(Identifier::new("Foo")),
|
error: ValidationError::VariableNotFound(Identifier::new("Foo")),
|
||||||
position: (17, 69).into()
|
position: (17, 69).into()
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{collections::BTreeMap, rc::Rc};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use dust_lang::{
|
use dust_lang::{
|
||||||
abstract_tree::{Identifier, Type, WithPos},
|
abstract_tree::{Identifier, Type, WithPos},
|
||||||
@ -8,37 +8,25 @@ use dust_lang::{
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn none() {
|
fn none() {
|
||||||
assert_eq!(interpret(Rc::new("test".to_string()), "x = 9"), Ok(None));
|
assert_eq!(interpret("test", "x = 9"), Ok(None));
|
||||||
assert_eq!(
|
assert_eq!(interpret("test", "x = 1 + 1"), Ok(None));
|
||||||
interpret(Rc::new("test".to_string()), "x = 1 + 1"),
|
|
||||||
Ok(None)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer() {
|
fn integer() {
|
||||||
assert_eq!(
|
assert_eq!(interpret("test", "1"), Ok(Some(Value::integer(1))));
|
||||||
interpret(Rc::new("test".to_string()), "1"),
|
assert_eq!(interpret("test", "123"), Ok(Some(Value::integer(123))));
|
||||||
Ok(Some(Value::integer(1)))
|
assert_eq!(interpret("test", "-666"), Ok(Some(Value::integer(-666))));
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
interpret(Rc::new("test".to_string()), "123"),
|
|
||||||
Ok(Some(Value::integer(123)))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
interpret(Rc::new("test".to_string()), "-666"),
|
|
||||||
Ok(Some(Value::integer(-666)))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer_saturation() {
|
fn integer_saturation() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "9223372036854775807 + 1"),
|
interpret("test", "9223372036854775807 + 1"),
|
||||||
Ok(Some(Value::integer(i64::MAX)))
|
Ok(Some(Value::integer(i64::MAX)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "-9223372036854775808 - 1"),
|
interpret("test", "-9223372036854775808 - 1"),
|
||||||
Ok(Some(Value::integer(i64::MIN)))
|
Ok(Some(Value::integer(i64::MIN)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -46,11 +34,11 @@ fn integer_saturation() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn float() {
|
fn float() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "1.7976931348623157e308"),
|
interpret("test", "1.7976931348623157e308"),
|
||||||
Ok(Some(Value::float(f64::MAX)))
|
Ok(Some(Value::float(f64::MAX)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "-1.7976931348623157e308"),
|
interpret("test", "-1.7976931348623157e308"),
|
||||||
Ok(Some(Value::float(f64::MIN)))
|
Ok(Some(Value::float(f64::MIN)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -58,11 +46,11 @@ fn float() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn float_saturation() {
|
fn float_saturation() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "1.7976931348623157e308 + 1"),
|
interpret("test", "1.7976931348623157e308 + 1"),
|
||||||
Ok(Some(Value::float(f64::MAX)))
|
Ok(Some(Value::float(f64::MAX)))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "-1.7976931348623157e308 - 1"),
|
interpret("test", "-1.7976931348623157e308 - 1"),
|
||||||
Ok(Some(Value::float(f64::MIN)))
|
Ok(Some(Value::float(f64::MIN)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -70,27 +58,27 @@ fn float_saturation() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn string() {
|
fn string() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "\"one\""),
|
interpret("test", "\"one\""),
|
||||||
Ok(Some(Value::string("one".to_string())))
|
Ok(Some(Value::string("one".to_string())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "'one'"),
|
interpret("test", "'one'"),
|
||||||
Ok(Some(Value::string("one".to_string())))
|
Ok(Some(Value::string("one".to_string())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "`one`"),
|
interpret("test", "`one`"),
|
||||||
Ok(Some(Value::string("one".to_string())))
|
Ok(Some(Value::string("one".to_string())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "`'one'`"),
|
interpret("test", "`'one'`"),
|
||||||
Ok(Some(Value::string("'one'".to_string())))
|
Ok(Some(Value::string("'one'".to_string())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "'`one`'"),
|
interpret("test", "'`one`'"),
|
||||||
Ok(Some(Value::string("`one`".to_string())))
|
Ok(Some(Value::string("`one`".to_string())))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "\"'one'\""),
|
interpret("test", "\"'one'\""),
|
||||||
Ok(Some(Value::string("'one'".to_string())))
|
Ok(Some(Value::string("'one'".to_string())))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -98,7 +86,7 @@ fn string() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn list() {
|
fn list() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"),
|
interpret("test", "[1, 2, 'foobar']"),
|
||||||
Ok(Some(Value::list(vec![
|
Ok(Some(Value::list(vec![
|
||||||
Value::integer(1).with_position((1, 2)),
|
Value::integer(1).with_position((1, 2)),
|
||||||
Value::integer(2).with_position((4, 5)),
|
Value::integer(2).with_position((4, 5)),
|
||||||
@ -109,10 +97,7 @@ fn list() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_list() {
|
fn empty_list() {
|
||||||
assert_eq!(
|
assert_eq!(interpret("test", "[]"), Ok(Some(Value::list(Vec::new()))));
|
||||||
interpret(Rc::new("test".to_string()), "[]"),
|
|
||||||
Ok(Some(Value::list(Vec::new())))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -123,7 +108,7 @@ fn map() {
|
|||||||
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
|
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "{ x = 1, foo = 'bar' }"),
|
interpret("test", "{ x = 1, foo = 'bar' }"),
|
||||||
Ok(Some(Value::map(map)))
|
Ok(Some(Value::map(map)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -131,7 +116,7 @@ fn map() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn empty_map() {
|
fn empty_map() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "{}"),
|
interpret("test", "{}"),
|
||||||
Ok(Some(Value::map(BTreeMap::new())))
|
Ok(Some(Value::map(BTreeMap::new())))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,10 +129,7 @@ fn map_types() {
|
|||||||
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
|
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret("test", "{ x : int = 1, foo : str = 'bar' }"),
|
||||||
Rc::new("test".to_string()),
|
|
||||||
"{ x : int = 1, foo : str = 'bar' }"
|
|
||||||
),
|
|
||||||
Ok(Some(Value::map(map)))
|
Ok(Some(Value::map(map)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -155,7 +137,7 @@ fn map_types() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn map_type_errors() {
|
fn map_type_errors() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "{ foo : bool = 'bar' }")
|
interpret("test", "{ foo : bool = 'bar' }")
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.errors(),
|
.errors(),
|
||||||
&vec![Error::Validation {
|
&vec![Error::Validation {
|
||||||
@ -174,8 +156,5 @@ fn map_type_errors() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range() {
|
fn range() {
|
||||||
assert_eq!(
|
assert_eq!(interpret("test", "0..100"), Ok(Some(Value::range(0..100))));
|
||||||
interpret(Rc::new("test".to_string()), "0..100"),
|
|
||||||
Ok(Some(Value::range(0..100)))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dust_lang::{
|
use dust_lang::{
|
||||||
abstract_tree::{Block, Expression, Identifier, Statement, Type, WithPos},
|
abstract_tree::{Block, Expression, Identifier, Statement, Type, WithPos},
|
||||||
error::{Error, TypeConflict, ValidationError},
|
error::{Error, TypeConflict, ValidationError},
|
||||||
@ -9,7 +7,7 @@ use dust_lang::{
|
|||||||
#[test]
|
#[test]
|
||||||
fn set_and_get_variable() {
|
fn set_and_get_variable() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "foobar = true; foobar"),
|
interpret("test", "foobar = true; foobar"),
|
||||||
Ok(Some(Value::boolean(true)))
|
Ok(Some(Value::boolean(true)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -17,7 +15,7 @@ fn set_and_get_variable() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn set_variable_with_type() {
|
fn set_variable_with_type() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "foobar: bool = true; foobar"),
|
interpret("test", "foobar: bool = true; foobar"),
|
||||||
Ok(Some(Value::boolean(true)))
|
Ok(Some(Value::boolean(true)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -25,7 +23,7 @@ fn set_variable_with_type() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn set_variable_with_type_error() {
|
fn set_variable_with_type_error() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(Rc::new("test".to_string()), "foobar: str = true")
|
interpret("test", "foobar: str = true")
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.errors(),
|
.errors(),
|
||||||
&vec![Error::Validation {
|
&vec![Error::Validation {
|
||||||
@ -45,10 +43,7 @@ fn set_variable_with_type_error() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn function_variable() {
|
fn function_variable() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
interpret(
|
interpret("test", "foobar = (x: int) int { x }; foobar"),
|
||||||
Rc::new("test".to_string()),
|
|
||||||
"foobar = (x: int) int { x }; foobar"
|
|
||||||
),
|
|
||||||
Ok(Some(Value::function(
|
Ok(Some(Value::function(
|
||||||
Vec::with_capacity(0),
|
Vec::with_capacity(0),
|
||||||
vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))],
|
vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))],
|
||||||
|
@ -80,7 +80,8 @@ pub fn run_shell(context: Context) -> Result<(), io::Error> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let run_result = interpreter.run(Rc::new("input".to_string()), &buffer);
|
let run_result =
|
||||||
|
interpreter.run(Rc::new("input".to_string()), Rc::from(buffer.as_str()));
|
||||||
|
|
||||||
match run_result {
|
match run_result {
|
||||||
Ok(Some(value)) => {
|
Ok(Some(value)) => {
|
||||||
@ -88,12 +89,11 @@ pub fn run_shell(context: Context) -> Result<(), io::Error> {
|
|||||||
}
|
}
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let source_id = Rc::new("input".to_string());
|
|
||||||
let reports = error.build_reports();
|
let reports = error.build_reports();
|
||||||
|
|
||||||
for report in reports {
|
for report in reports {
|
||||||
report
|
report
|
||||||
.write_for_stdout(sources([(source_id.clone(), &buffer)]), stderr())
|
.write_for_stdout(sources(interpreter.sources()), stderr())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dust_lang::{context::Context, interpret, interpret_without_std};
|
use dust_lang::{context::Context, Interpreter};
|
||||||
|
|
||||||
/// Command-line arguments to be parsed.
|
/// Command-line arguments to be parsed.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@ -42,10 +42,16 @@ fn main() {
|
|||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let context = Context::new();
|
let context = Context::new();
|
||||||
let (source, source_id) = if let Some(path) = args.path {
|
let mut interpreter = Interpreter::new(context.clone());
|
||||||
(read_to_string(&path).unwrap(), Rc::new(path))
|
|
||||||
|
interpreter.load_std().unwrap();
|
||||||
|
|
||||||
|
let (source_id, source) = if let Some(path) = args.path {
|
||||||
|
let source = read_to_string(&path).unwrap();
|
||||||
|
|
||||||
|
(Rc::new(path.to_string()), source)
|
||||||
} else if let Some(command) = args.command {
|
} else if let Some(command) = args.command {
|
||||||
(command, Rc::new("input".to_string()))
|
(Rc::new("input".to_string()), command)
|
||||||
} else {
|
} else {
|
||||||
match run_shell(context) {
|
match run_shell(context) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
@ -55,13 +61,9 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let eval_result = if args.no_std {
|
let run_result = interpreter.run(source_id, Rc::from(source));
|
||||||
interpret_without_std(source_id.clone(), &source)
|
|
||||||
} else {
|
|
||||||
interpret(source_id.clone(), &source)
|
|
||||||
};
|
|
||||||
|
|
||||||
match eval_result {
|
match run_result {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
println!("{value}")
|
println!("{value}")
|
||||||
@ -70,7 +72,7 @@ fn main() {
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
for report in error.build_reports() {
|
for report in error.build_reports() {
|
||||||
report
|
report
|
||||||
.write_for_stdout(sources([(source_id.clone(), source.as_str())]), stderr())
|
.write_for_stdout(sources(interpreter.sources()), stderr())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user