Refine error reports
This commit is contained in:
parent
004b7be27a
commit
7263507e84
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -384,6 +384,7 @@ dependencies = [
|
||||
name = "dust-shell"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ariadne",
|
||||
"clap",
|
||||
"colored",
|
||||
"dust-lang",
|
||||
|
@ -144,11 +144,11 @@ impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Type::Any => write!(f, "any"),
|
||||
Type::Boolean => write!(f, "boolean"),
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Integer => write!(f, "integer"),
|
||||
Type::Integer => write!(f, "int"),
|
||||
Type::List => write!(f, "list"),
|
||||
Type::ListOf(item_type) => write!(f, "list of {item_type}"),
|
||||
Type::ListOf(item_type) => write!(f, "list({item_type})"),
|
||||
Type::ListExact(item_types) => {
|
||||
write!(f, "[")?;
|
||||
|
||||
@ -165,7 +165,7 @@ impl Display for Type {
|
||||
Type::Map => write!(f, "map"),
|
||||
Type::None => write!(f, "none"),
|
||||
Type::Range => write!(f, "range"),
|
||||
Type::String => write!(f, "string"),
|
||||
Type::String => write!(f, "str"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
|
@ -5,12 +5,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::{
|
||||
abstract_tree::{Action, Identifier, Type, WithPosition},
|
||||
abstract_tree::{Action, Type},
|
||||
context::Context,
|
||||
error::{RuntimeError, ValidationError},
|
||||
error::RuntimeError,
|
||||
value::ValueInner,
|
||||
Value,
|
||||
};
|
||||
@ -18,6 +16,7 @@ use crate::{
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum BuiltInFunction {
|
||||
ReadLine,
|
||||
Sleep,
|
||||
WriteLine,
|
||||
}
|
||||
|
||||
@ -25,6 +24,7 @@ impl BuiltInFunction {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInFunction::ReadLine => todo!(),
|
||||
BuiltInFunction::Sleep => todo!(),
|
||||
BuiltInFunction::WriteLine => todo!(),
|
||||
}
|
||||
}
|
||||
@ -47,32 +47,6 @@ impl BuiltInFunction {
|
||||
|
||||
pub fn call(&self, arguments: Vec<Value>, context: &Context) -> Result<Action, RuntimeError> {
|
||||
match self {
|
||||
BuiltInFunction::ReadLine => {
|
||||
let string = arguments.get(0).unwrap();
|
||||
|
||||
if let ValueInner::String(_string) = string.inner().as_ref() {
|
||||
// let integer = string.parse();
|
||||
|
||||
todo!()
|
||||
|
||||
// Ok(Action::Return(Value::integer(integer)))
|
||||
} else {
|
||||
let mut actual = Vec::with_capacity(arguments.len());
|
||||
|
||||
for value in arguments {
|
||||
let r#type = value.r#type(context)?;
|
||||
|
||||
actual.push(r#type);
|
||||
}
|
||||
|
||||
Err(RuntimeError::ValidationFailure(
|
||||
ValidationError::WrongArguments {
|
||||
expected: vec![Type::String],
|
||||
actual,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
// "INT_RANDOM_RANGE" => {
|
||||
// let range = arguments.get(0).unwrap();
|
||||
|
||||
@ -91,18 +65,18 @@ impl BuiltInFunction {
|
||||
|
||||
Ok(Action::Return(Value::string(input)))
|
||||
}
|
||||
BuiltInFunction::Sleep => {
|
||||
if let ValueInner::Integer(milliseconds) = arguments[0].inner().as_ref() {
|
||||
thread::sleep(Duration::from_millis(*milliseconds as u64));
|
||||
}
|
||||
|
||||
Ok(Action::None)
|
||||
}
|
||||
BuiltInFunction::WriteLine => {
|
||||
println!("{}", arguments[0]);
|
||||
|
||||
Ok(Action::None)
|
||||
}
|
||||
// "SLEEP" => {
|
||||
// if let ValueInner::Integer(milliseconds) = arguments[0].inner().as_ref() {
|
||||
// thread::sleep(Duration::from_millis(*milliseconds as u64));
|
||||
// }
|
||||
|
||||
// Ok(Action::None)
|
||||
// }
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{io, sync::PoisonError};
|
||||
use std::{fmt::Debug, hash::Hash, io, ops::Range, sync::PoisonError};
|
||||
|
||||
use ariadne::{sources, Color, Fmt, Label, Report, ReportKind};
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
||||
use chumsky::{prelude::Rich, span::Span};
|
||||
|
||||
use crate::{
|
||||
@ -31,7 +31,10 @@ pub enum Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn build_report(self, source: &str) -> Result<Vec<u8>, io::Error> {
|
||||
pub fn build_report<'id, Id: Debug + Hash + Eq + Clone>(
|
||||
self,
|
||||
source_id: Id,
|
||||
) -> Result<Report<'id, (Id, Range<usize>)>, io::Error> {
|
||||
let (mut builder, validation_error, error_position) = match self {
|
||||
Error::Parse {
|
||||
expected,
|
||||
@ -47,12 +50,12 @@ impl Error {
|
||||
(
|
||||
Report::build(
|
||||
ReportKind::Custom("Parsing Error", Color::Yellow),
|
||||
"input",
|
||||
source_id.clone(),
|
||||
span.1,
|
||||
)
|
||||
.with_message(description)
|
||||
.with_label(
|
||||
Label::new(("input", span.0..span.1))
|
||||
Label::new((source_id.clone(), span.0..span.1))
|
||||
.with_message(reason)
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
@ -74,12 +77,12 @@ impl Error {
|
||||
(
|
||||
Report::build(
|
||||
ReportKind::Custom("Lexing Error", Color::Yellow),
|
||||
"input",
|
||||
source_id.clone(),
|
||||
span.1,
|
||||
)
|
||||
.with_message(description)
|
||||
.with_label(
|
||||
Label::new(("input", span.0..span.1))
|
||||
Label::new((source_id.clone(), span.0..span.1))
|
||||
.with_message(reason)
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
@ -90,7 +93,7 @@ impl Error {
|
||||
Error::Runtime { error, position } => (
|
||||
Report::build(
|
||||
ReportKind::Custom("Runtime Error", Color::Red),
|
||||
"input",
|
||||
source_id.clone(),
|
||||
position.1,
|
||||
)
|
||||
.with_message("An error occured that forced the program to exit.")
|
||||
@ -110,7 +113,7 @@ impl Error {
|
||||
Error::Validation { error, position } => (
|
||||
Report::build(
|
||||
ReportKind::Custom("Validation Error", Color::Magenta),
|
||||
"input",
|
||||
source_id.clone(),
|
||||
position.1,
|
||||
)
|
||||
.with_message("The syntax is valid but this code is not sound.")
|
||||
@ -126,22 +129,22 @@ impl Error {
|
||||
if let Some(validation_error) = validation_error {
|
||||
match validation_error {
|
||||
ValidationError::ExpectedBoolean { actual, position } => {
|
||||
builder.add_label(Label::new(("input", position.0..position.1)).with_message(
|
||||
format!(
|
||||
builder.add_label(
|
||||
Label::new((source_id, position.0..position.1)).with_message(format!(
|
||||
"Expected {} but got {}.",
|
||||
"boolean".fg(type_color),
|
||||
actual.fg(type_color)
|
||||
),
|
||||
));
|
||||
)),
|
||||
);
|
||||
}
|
||||
ValidationError::ExpectedIntegerOrFloat(position) => {
|
||||
builder.add_label(Label::new(("input", position.0..position.1)).with_message(
|
||||
format!(
|
||||
builder.add_label(
|
||||
Label::new((source_id, position.0..position.1)).with_message(format!(
|
||||
"Expected {} or {}.",
|
||||
"integer".fg(type_color),
|
||||
"float".fg(type_color)
|
||||
),
|
||||
));
|
||||
)),
|
||||
);
|
||||
}
|
||||
ValidationError::RwLockPoison(_) => todo!(),
|
||||
ValidationError::TypeCheck {
|
||||
@ -154,15 +157,17 @@ impl Error {
|
||||
builder = builder.with_message("A type conflict was found.");
|
||||
|
||||
builder.add_labels([
|
||||
Label::new(("input", expected_postion.0..expected_postion.1)).with_message(
|
||||
format!("Type {} established here.", expected.fg(type_color)),
|
||||
),
|
||||
Label::new(("input", actual_position.0..actual_position.1))
|
||||
Label::new((source_id.clone(), expected_postion.0..expected_postion.1))
|
||||
.with_message(format!(
|
||||
"Type {} established here.",
|
||||
expected.fg(type_color)
|
||||
)),
|
||||
Label::new((source_id, actual_position.0..actual_position.1))
|
||||
.with_message(format!("Got type {} here.", actual.fg(type_color))),
|
||||
]);
|
||||
}
|
||||
ValidationError::VariableNotFound(identifier) => builder.add_label(
|
||||
Label::new(("input", error_position.0..error_position.1)).with_message(
|
||||
Label::new((source_id, error_position.0..error_position.1)).with_message(
|
||||
format!(
|
||||
"Variable {} does not exist in this context.",
|
||||
identifier.fg(identifier_color)
|
||||
@ -170,7 +175,7 @@ impl Error {
|
||||
),
|
||||
),
|
||||
ValidationError::CannotIndex { r#type, position } => builder.add_label(
|
||||
Label::new(("input", position.0..position.1))
|
||||
Label::new((source_id, position.0..position.1))
|
||||
.with_message(format!("Cannot index into a {}.", r#type.fg(type_color))),
|
||||
),
|
||||
ValidationError::CannotIndexWith {
|
||||
@ -186,12 +191,14 @@ impl Error {
|
||||
));
|
||||
|
||||
builder.add_labels([
|
||||
Label::new(("input", collection_position.0..collection_position.1))
|
||||
.with_message(format!(
|
||||
"This has type {}.",
|
||||
collection_type.fg(type_color),
|
||||
)),
|
||||
Label::new(("input", index_position.0..index_position.1))
|
||||
Label::new((
|
||||
source_id.clone(),
|
||||
collection_position.0..collection_position.1,
|
||||
))
|
||||
.with_message(
|
||||
format!("This has type {}.", collection_type.fg(type_color),),
|
||||
),
|
||||
Label::new((source_id, index_position.0..index_position.1))
|
||||
.with_message(format!("This has type {}.", index_type.fg(type_color),)),
|
||||
])
|
||||
}
|
||||
@ -203,13 +210,7 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = Vec::new();
|
||||
|
||||
builder
|
||||
.finish()
|
||||
.write_for_stdout(sources([("input", source)]), &mut output)?;
|
||||
|
||||
Ok(output)
|
||||
Ok(builder.finish())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,7 @@ impl<'src> Display for Token<'src> {
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum BuiltInIdentifier {
|
||||
ReadLine,
|
||||
Sleep,
|
||||
WriteLine,
|
||||
}
|
||||
|
||||
@ -46,6 +47,7 @@ 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__"),
|
||||
}
|
||||
}
|
||||
@ -302,6 +304,7 @@ pub fn lexer<'src>() -> impl Parser<
|
||||
|
||||
let built_in_identifier = choice((
|
||||
just("__READ_LINE__").to(BuiltInIdentifier::ReadLine),
|
||||
just("__SLEEP__").to(BuiltInIdentifier::Sleep),
|
||||
just("__WRITE_LINE__").to(BuiltInIdentifier::WriteLine),
|
||||
))
|
||||
.map(Token::BuiltInIdentifier);
|
||||
|
@ -236,10 +236,8 @@ pub fn parser<'src>() -> impl Parser<
|
||||
let built_in_function = {
|
||||
select! {
|
||||
Token::BuiltInIdentifier(built_in_identifier) => {
|
||||
match built_in_identifier {
|
||||
BuiltInIdentifier::ReadLine => BuiltInFunction::ReadLine,
|
||||
BuiltInIdentifier::WriteLine => BuiltInFunction::WriteLine,
|
||||
}
|
||||
match built_in_identifier {BuiltInIdentifier::ReadLine=>BuiltInFunction::ReadLine,BuiltInIdentifier::WriteLine=>BuiltInFunction::WriteLine,
|
||||
BuiltInIdentifier::Sleep => BuiltInFunction::Sleep, }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ariadne = "0.4.0"
|
||||
clap = { version = "4.5.3", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
dust-lang = { path = "../dust-lang" }
|
||||
|
@ -5,6 +5,7 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use ariadne::sources;
|
||||
use dust_lang::{
|
||||
context::{Context, ValueData},
|
||||
*,
|
||||
@ -87,9 +88,11 @@ pub fn run_shell(context: Context) {
|
||||
Ok(None) => {}
|
||||
Err(errors) => {
|
||||
for error in errors {
|
||||
let report = error.build_report(&buffer).unwrap();
|
||||
let report = error.build_report(&"input").unwrap();
|
||||
|
||||
stderr().write_all(&report).unwrap();
|
||||
report
|
||||
.write_for_stdout(sources([(&"input", buffer.clone())]), stderr())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
218
dust-shell/src/error.rs
Normal file
218
dust-shell/src/error.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use ariadne::{Color, Fmt, Label, Report, ReportKind};
|
||||
use dust_lang::error::{Error as DustError, RuntimeError, TypeConflict, ValidationError};
|
||||
use std::{fmt::Debug, io, ops::Range, path::Path, rc::Rc};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Dust {
|
||||
errors: Vec<dust_lang::error::Error>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn build_reports<'id>(
|
||||
self,
|
||||
source_id: Rc<String>,
|
||||
source: &str,
|
||||
) -> Result<Vec<Report<'id, (Rc<String>, Range<usize>)>>, io::Error> {
|
||||
if let Error::Dust { errors } = self {
|
||||
let mut reports = Vec::new();
|
||||
|
||||
for error in errors {
|
||||
let (mut builder, validation_error, error_position) = match error {
|
||||
DustError::Parse {
|
||||
expected,
|
||||
span,
|
||||
reason,
|
||||
} => {
|
||||
let description = if expected.is_empty() {
|
||||
"Invalid token.".to_string()
|
||||
} else {
|
||||
format!("Expected {expected}.")
|
||||
};
|
||||
|
||||
(
|
||||
Report::build(
|
||||
ReportKind::Custom("Parsing Error", Color::Yellow),
|
||||
source_id.clone(),
|
||||
span.1,
|
||||
)
|
||||
.with_message(description)
|
||||
.with_label(
|
||||
Label::new((source_id.clone(), span.0..span.1))
|
||||
.with_message(reason)
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
None,
|
||||
span.into(),
|
||||
)
|
||||
}
|
||||
DustError::Lex {
|
||||
expected,
|
||||
span,
|
||||
reason,
|
||||
} => {
|
||||
let description = if expected.is_empty() {
|
||||
"Invalid character.".to_string()
|
||||
} else {
|
||||
format!("Expected {expected}.")
|
||||
};
|
||||
|
||||
(
|
||||
Report::build(
|
||||
ReportKind::Custom("Lexing Error", Color::Yellow),
|
||||
source_id.clone(),
|
||||
span.1,
|
||||
)
|
||||
.with_message(description)
|
||||
.with_label(
|
||||
Label::new((source_id.clone(), span.0..span.1))
|
||||
.with_message(reason)
|
||||
.with_color(Color::Red),
|
||||
),
|
||||
None,
|
||||
span.into(),
|
||||
)
|
||||
}
|
||||
DustError::Runtime { error, position } => (
|
||||
Report::build(
|
||||
ReportKind::Custom("Runtime Error", Color::Red),
|
||||
source_id.clone(),
|
||||
position.1,
|
||||
)
|
||||
.with_message("An error occured that forced the program to exit.")
|
||||
.with_note(
|
||||
"There may be unexpected side-effects because the program could not finish.",
|
||||
)
|
||||
.with_help(
|
||||
"This is the interpreter's fault. Please submit a bug with this error message.",
|
||||
),
|
||||
if let RuntimeError::ValidationFailure(validation_error) = error {
|
||||
Some(validation_error)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
position,
|
||||
),
|
||||
DustError::Validation { error, position } => (
|
||||
Report::build(
|
||||
ReportKind::Custom("Validation Error", Color::Magenta),
|
||||
source_id.clone(),
|
||||
position.1,
|
||||
)
|
||||
.with_message("The syntax is valid but this code is not sound.")
|
||||
.with_note("This error was detected by the interpreter before running the code."),
|
||||
Some(error),
|
||||
position,
|
||||
),
|
||||
};
|
||||
|
||||
let type_color = Color::Green;
|
||||
let identifier_color = Color::Blue;
|
||||
|
||||
if let Some(validation_error) = validation_error {
|
||||
match validation_error {
|
||||
ValidationError::ExpectedBoolean { actual, position } => {
|
||||
builder.add_label(
|
||||
Label::new((source_id.clone(), position.0..position.1))
|
||||
.with_message(format!(
|
||||
"Expected {} but got {}.",
|
||||
"boolean".fg(type_color),
|
||||
actual.fg(type_color)
|
||||
)),
|
||||
);
|
||||
}
|
||||
ValidationError::ExpectedIntegerOrFloat(position) => {
|
||||
builder.add_label(
|
||||
Label::new((source_id.clone(), position.0..position.1))
|
||||
.with_message(format!(
|
||||
"Expected {} or {}.",
|
||||
"integer".fg(type_color),
|
||||
"float".fg(type_color)
|
||||
)),
|
||||
);
|
||||
}
|
||||
ValidationError::RwLockPoison(_) => todo!(),
|
||||
ValidationError::TypeCheck {
|
||||
conflict,
|
||||
actual_position,
|
||||
expected_position: expected_postion,
|
||||
} => {
|
||||
let TypeConflict { actual, expected } = conflict;
|
||||
|
||||
builder = builder.with_message("A type conflict was found.");
|
||||
|
||||
builder.add_labels([
|
||||
Label::new((
|
||||
source_id.clone(),
|
||||
expected_postion.0..expected_postion.1,
|
||||
))
|
||||
.with_message(format!(
|
||||
"Type {} established here.",
|
||||
expected.fg(type_color)
|
||||
)),
|
||||
Label::new((
|
||||
source_id.clone(),
|
||||
actual_position.0..actual_position.1,
|
||||
))
|
||||
.with_message(format!("Got type {} here.", actual.fg(type_color))),
|
||||
]);
|
||||
}
|
||||
ValidationError::VariableNotFound(identifier) => builder.add_label(
|
||||
Label::new((source_id.clone(), error_position.0..error_position.1))
|
||||
.with_message(format!(
|
||||
"Variable {} does not exist in this context.",
|
||||
identifier.fg(identifier_color)
|
||||
)),
|
||||
),
|
||||
ValidationError::CannotIndex { r#type, position } => builder.add_label(
|
||||
Label::new((source_id.clone(), position.0..position.1)).with_message(
|
||||
format!("Cannot index into a {}.", r#type.fg(type_color)),
|
||||
),
|
||||
),
|
||||
ValidationError::CannotIndexWith {
|
||||
collection_type,
|
||||
collection_position,
|
||||
index_type,
|
||||
index_position,
|
||||
} => {
|
||||
builder = builder.with_message(format!(
|
||||
"Cannot index into {} with {}.",
|
||||
collection_type.clone().fg(type_color),
|
||||
index_type.clone().fg(type_color)
|
||||
));
|
||||
|
||||
builder.add_labels([
|
||||
Label::new((
|
||||
source_id.clone(),
|
||||
collection_position.0..collection_position.1,
|
||||
))
|
||||
.with_message(format!(
|
||||
"This has type {}.",
|
||||
collection_type.fg(type_color),
|
||||
)),
|
||||
Label::new((source_id.clone(), index_position.0..index_position.1))
|
||||
.with_message(format!(
|
||||
"This has type {}.",
|
||||
index_type.fg(type_color),
|
||||
)),
|
||||
])
|
||||
}
|
||||
ValidationError::InterpreterExpectedReturn(_) => todo!(),
|
||||
ValidationError::ExpectedFunction { .. } => todo!(),
|
||||
ValidationError::ExpectedValue(_) => todo!(),
|
||||
ValidationError::PropertyNotFound { .. } => todo!(),
|
||||
ValidationError::WrongArguments { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
let report = builder.finish();
|
||||
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
return Ok(reports);
|
||||
} else {
|
||||
return Ok(Vec::with_capacity(0));
|
||||
};
|
||||
}
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
//! Command line interface for the dust programming language.
|
||||
mod cli;
|
||||
mod error;
|
||||
|
||||
use ariadne::sources;
|
||||
use clap::Parser;
|
||||
use cli::run_shell;
|
||||
use colored::Colorize;
|
||||
use error::Error;
|
||||
|
||||
use std::{
|
||||
fs::read_to_string,
|
||||
io::{stderr, Write},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use dust_lang::{context::Context, interpret};
|
||||
@ -37,11 +42,10 @@ fn main() {
|
||||
|
||||
let args = Args::parse();
|
||||
let context = Context::new();
|
||||
|
||||
let source = if let Some(path) = &args.path {
|
||||
read_to_string(path).unwrap()
|
||||
let (source, source_id) = if let Some(path) = args.path {
|
||||
(read_to_string(&path).unwrap(), Rc::new(path))
|
||||
} else if let Some(command) = args.command {
|
||||
command
|
||||
(command, Rc::new("input".to_string()))
|
||||
} else {
|
||||
return run_shell(context);
|
||||
};
|
||||
@ -55,10 +59,14 @@ fn main() {
|
||||
}
|
||||
}
|
||||
Err(errors) => {
|
||||
for error in errors {
|
||||
let report = error.build_report(&source).unwrap();
|
||||
let reports = Error::Dust { errors }
|
||||
.build_reports(source_id.clone(), &source)
|
||||
.unwrap();
|
||||
|
||||
stderr().write_all(&report).unwrap();
|
||||
for report in reports {
|
||||
report
|
||||
.write_for_stdout(sources([(source_id.clone(), source.clone())]), stderr())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
std/thread.ds
Normal file
5
std/thread.ds
Normal file
@ -0,0 +1,5 @@
|
||||
thread = {
|
||||
sleep = (milliseconds: int) : none {
|
||||
__SLEEP__()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user