1
0

Pass all tests

This commit is contained in:
Jeff 2024-03-24 09:10:49 -04:00
parent 7dfc026be5
commit 2b797c19f7
20 changed files with 623 additions and 558 deletions

104
Cargo.lock generated
View File

@ -115,7 +115,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29"
dependencies = [
"concolor",
"unicode-width",
"yansi",
]
@ -262,26 +261,6 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "concolor"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b946244a988c390a94667ae0e3958411fa40cc46ea496a929b263d883f5f9c3"
dependencies = [
"bitflags 1.3.2",
"concolor-query",
"is-terminal",
]
[[package]]
name = "concolor-query"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf"
dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
@ -537,12 +516,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "home"
version = "0.5.9"
@ -591,17 +564,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.12.1"
@ -1456,15 +1418,6 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@ -1483,21 +1436,6 @@ dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@ -1528,12 +1466,6 @@ dependencies = [
"windows_x86_64_msvc 0.52.4",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
@ -1546,12 +1478,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@ -1564,12 +1490,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@ -1582,12 +1502,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@ -1600,12 +1514,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@ -1618,12 +1526,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@ -1636,12 +1538,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"

View File

@ -9,7 +9,7 @@ readme.workspace = true
repository.workspace = true
[dependencies]
ariadne = { version = "0.4.0", features = ["auto-color"] }
ariadne = "0.4.0"
chumsky = { version = "1.0.0-alpha.6", features = ["pratt", "label"] }
clap = { version = "4.5.2", features = ["derive"] }
colored = "2.1.0"

View File

@ -13,6 +13,7 @@ use super::{AbstractNode, Action, WithPosition};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Type {
Any,
Argument(Identifier),
Boolean,
Float,
Function {
@ -179,6 +180,7 @@ impl Display for Type {
write!(f, ") : {return_type}")
}
Type::Structure { name, .. } => write!(f, "{name}"),
Type::Argument(_) => todo!(),
}
}
}

View File

@ -30,6 +30,7 @@ pub enum ValueNode {
fields: Vec<(Identifier, WithPosition<Expression>)>,
},
ParsedFunction {
type_arguments: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>,
body: WithPosition<Block>,
@ -112,6 +113,7 @@ impl AbstractNode for ValueNode {
}
if let ValueNode::ParsedFunction {
type_arguments,
parameters,
return_type,
body,
@ -121,6 +123,12 @@ impl AbstractNode for ValueNode {
function_context.inherit_types_from(context)?;
for r#type in type_arguments {
if let Type::Argument(identifier) = &r#type.node {
function_context.set_type(identifier.clone(), r#type.node.clone())?;
}
}
for (identifier, r#type) in parameters {
function_context.set_type(identifier.clone(), r#type.node.clone())?;
}
@ -210,10 +218,11 @@ impl AbstractNode for ValueNode {
ValueNode::Range(range) => Value::range(range),
ValueNode::String(string) => Value::string(string),
ValueNode::ParsedFunction {
type_arguments,
parameters,
return_type,
body,
} => Value::function(parameters, return_type, body),
} => Value::function(type_arguments, parameters, return_type, body),
ValueNode::Structure {
name,
fields: expressions,
@ -281,11 +290,13 @@ impl Ord for ValueNode {
(String(_), _) => Ordering::Greater,
(
ParsedFunction {
type_arguments: left_type_arguments,
parameters: left_parameters,
return_type: left_return,
body: left_body,
},
ParsedFunction {
type_arguments: right_type_arguments,
parameters: right_parameters,
return_type: right_return,
body: right_body,
@ -297,7 +308,13 @@ impl Ord for ValueNode {
let return_cmp = left_return.cmp(right_return);
if return_cmp.is_eq() {
let type_argument_cmp = left_type_arguments.cmp(right_type_arguments);
if type_argument_cmp.is_eq() {
left_body.cmp(right_body)
} else {
type_argument_cmp
}
} else {
return_cmp
}

View File

@ -56,11 +56,7 @@ impl Context {
}
pub fn contains(&self, identifier: &Identifier) -> Result<bool, RwLockPoisonError> {
if self.inner.read()?.contains_key(identifier) {
Ok(true)
} else {
Ok(false)
}
Ok(self.inner.read()?.contains_key(identifier))
}
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ValidationError> {
@ -93,9 +89,9 @@ impl Context {
}
pub fn set_value(&self, identifier: Identifier, value: Value) -> Result<(), RwLockPoisonError> {
let mut inner = self.inner.write()?;
inner.insert(identifier, ValueData::Value(value));
self.inner
.write()?
.insert(identifier, ValueData::Value(value));
Ok(())
}

View File

@ -61,6 +61,7 @@ pub enum Keyword {
Break,
Else,
Float,
Fn,
Int,
If,
List,
@ -69,6 +70,7 @@ pub enum Keyword {
Range,
Struct,
Str,
Type,
Loop,
While,
}
@ -82,6 +84,7 @@ impl Display for Keyword {
Keyword::Break => write!(f, "break"),
Keyword::Else => write!(f, "else"),
Keyword::Float => write!(f, "float"),
Keyword::Fn => write!(f, "fn"),
Keyword::Int => write!(f, "int"),
Keyword::If => write!(f, "if"),
Keyword::List => write!(f, "list"),
@ -92,6 +95,7 @@ impl Display for Keyword {
Keyword::Str => write!(f, "str"),
Keyword::Loop => write!(f, "loop"),
Keyword::While => write!(f, "while"),
Keyword::Type => write!(f, "type"),
}
}
}
@ -143,13 +147,13 @@ impl Display for Operator {
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Control {
Arrow,
CurlyOpen,
CurlyClose,
SquareOpen,
SquareClose,
ParenOpen,
ParenClose,
Pipe,
Comma,
DoubleColon,
Colon,
@ -157,12 +161,13 @@ pub enum Control {
Dot,
DoubleDot,
Semicolon,
SkinnyArrow,
FatArrow,
}
impl Display for Control {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Control::Arrow => write!(f, "->"),
Control::CurlyOpen => write!(f, "{{"),
Control::CurlyClose => write!(f, "}}"),
Control::Dollar => write!(f, "$"),
@ -170,12 +175,15 @@ impl Display for Control {
Control::SquareClose => write!(f, "]"),
Control::ParenOpen => write!(f, "("),
Control::ParenClose => write!(f, ")"),
Control::Pipe => write!(f, "|"),
Control::Comma => write!(f, ","),
Control::DoubleColon => write!(f, "::"),
Control::Colon => write!(f, ":"),
Control::Dot => write!(f, "."),
Control::Semicolon => write!(f, ";"),
Control::DoubleDot => write!(f, ".."),
Control::SkinnyArrow => write!(f, "->"),
Control::FatArrow => write!(f, "=>"),
}
}
}
@ -265,13 +273,15 @@ pub fn lexer<'src>() -> impl Parser<
.map(Token::Operator);
let control = choice((
just("->").to(Control::Arrow),
just("->").to(Control::SkinnyArrow),
just("=>").to(Control::FatArrow),
just("{").to(Control::CurlyOpen),
just("}").to(Control::CurlyClose),
just("[").to(Control::SquareOpen),
just("]").to(Control::SquareClose),
just("(").to(Control::ParenOpen),
just(")").to(Control::ParenClose),
just("|").to(Control::Pipe),
just(",").to(Control::Comma),
just(";").to(Control::Semicolon),
just("::").to(Control::DoubleColon),
@ -289,6 +299,7 @@ pub fn lexer<'src>() -> impl Parser<
just("break").to(Keyword::Break),
just("else").to(Keyword::Else),
just("float").to(Keyword::Float),
just("fn").to(Keyword::Fn),
just("int").to(Keyword::Int),
just("if").to(Keyword::If),
just("list").to(Keyword::List),
@ -297,6 +308,7 @@ pub fn lexer<'src>() -> impl Parser<
just("range").to(Keyword::Range),
just("struct").to(Keyword::Struct),
just("str").to(Keyword::Str),
just("type").to(Keyword::Type),
just("loop").to(Keyword::Loop),
just("while").to(Keyword::While),
))

View File

@ -6,18 +6,256 @@ pub mod lexer;
pub mod parser;
pub mod value;
use std::{ops::Range, rc::Rc};
use abstract_tree::Type;
use ariadne::{Color, Fmt, Label, Report, ReportKind};
use context::Context;
use error::Error;
use error::{Error, RuntimeError, TypeConflict, ValidationError};
use lexer::lex;
use parser::parse;
pub use value::Value;
pub fn interpret(source: &str) -> Result<Option<Value>, Vec<Error>> {
pub fn interpret(source_id: Rc<String>, source: &str) -> Result<Option<Value>, InterpreterError> {
let mut interpreter = Interpreter::new(Context::new());
interpreter.run(include_str!("../../std/io.ds"))?;
interpreter.run(include_str!("../../std/thread.ds"))?;
interpreter.run(source)
interpreter.load_std()?;
interpreter.run(source_id, source)
}
pub fn interpret_without_std(
source_id: Rc<String>,
source: &str,
) -> Result<Option<Value>, InterpreterError> {
let mut interpreter = Interpreter::new(Context::new());
interpreter.run(source_id, source)
}
#[derive(Debug, PartialEq)]
pub struct InterpreterError {
source_id: Rc<String>,
errors: Vec<Error>,
}
impl InterpreterError {
pub fn errors(&self) -> &Vec<Error> {
&self.errors
}
}
impl InterpreterError {
pub fn build_reports<'id>(self) -> Vec<Report<'id, (Rc<String>, Range<usize>)>> {
let mut reports = Vec::new();
for error in self.errors {
let (mut builder, validation_error, error_position) = match error {
Error::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),
self.source_id.clone(),
span.1,
)
.with_message(description)
.with_label(
Label::new((self.source_id.clone(), span.0..span.1))
.with_message(reason)
.with_color(Color::Red),
),
None,
span.into(),
)
}
Error::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),
self.source_id.clone(),
span.1,
)
.with_message(description)
.with_label(
Label::new((self.source_id.clone(), span.0..span.1))
.with_message(reason)
.with_color(Color::Red),
),
None,
span.into(),
)
}
Error::Runtime { error, position } => (
Report::build(
ReportKind::Custom("Runtime Error", Color::Red),
self.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,
),
Error::Validation { error, position } => (
Report::build(
ReportKind::Custom("Validation Error", Color::Magenta),
self.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((self.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((self.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((
self.source_id.clone(),
expected_postion.0..expected_postion.1,
))
.with_message(format!(
"Type {} established here.",
expected.fg(type_color)
)),
Label::new((
self.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((self.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((self.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((
self.source_id.clone(),
collection_position.0..collection_position.1,
))
.with_message(format!(
"This has type {}.",
collection_type.fg(type_color),
)),
Label::new((
self.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!(),
ValidationError::ExpectedIntegerFloatOrString { actual, position } => {
builder = builder.with_message(format!(
"Expected an {}, {} or {}.",
Type::Integer.fg(type_color),
Type::Float.fg(type_color),
Type::String.fg(type_color)
));
builder.add_labels([Label::new((
self.source_id.clone(),
position.0..position.1,
))
.with_message(format!("This has type {}.", actual.fg(type_color),))])
}
}
}
let report = builder.finish();
reports.push(report);
}
reports
}
}
pub struct Interpreter {
@ -29,11 +267,36 @@ impl Interpreter {
Interpreter { context }
}
pub fn run(&mut self, source: &str) -> Result<Option<Value>, Vec<Error>> {
let tokens = lex(source)?;
let abstract_tree = parse(&tokens)?;
let value_option = abstract_tree.run(&self.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(())
}
}

View File

@ -66,15 +66,18 @@ pub fn parser<'src>() -> impl Parser<
.map_with(|value, state| Expression::Value(value).with_position(state.span()));
let r#type = recursive(|r#type| {
let function_type = r#type
let function_type = just(Token::Keyword(Keyword::Fn))
.ignore_then(
r#type
.clone()
.separated_by(just(Token::Control(Control::Comma)))
.collect()
.delimited_by(
just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)),
),
)
.then_ignore(just(Token::Control(Control::Arrow)))
.then_ignore(just(Token::Control(Control::SkinnyArrow)))
.then(r#type.clone())
.map(|(parameter_types, return_type)| Type::Function {
parameter_types,
@ -110,20 +113,21 @@ pub fn parser<'src>() -> impl Parser<
just(Token::Keyword(Keyword::Range)).to(Type::Range),
just(Token::Keyword(Keyword::Str)).to(Type::String),
just(Token::Keyword(Keyword::List)).to(Type::List),
identifier.clone().try_map(move |identifier, span| {
custom_types
.0
.borrow()
.get(&identifier)
.cloned()
.ok_or_else(|| {
Rich::custom(span, format!("There is no type named {identifier}."))
})
identifier.clone().map(move |identifier| {
if let Some(r#type) = custom_types.0.borrow().get(&identifier) {
r#type.clone()
} else {
Type::Argument(identifier)
}
}),
))
})
.map_with(|r#type, state| r#type.with_position(state.span()));
let type_argument = identifier
.clone()
.map_with(|identifier, state| Type::Argument(identifier).with_position(state.span()));
let type_specification = just(Token::Control(Control::Colon)).ignore_then(r#type.clone());
let structure_field_definition = identifier.clone().then(type_specification.clone());
@ -213,7 +217,20 @@ pub fn parser<'src>() -> impl Parser<
.with_position(state.span())
});
let parsed_function = identifier
let type_arguments = type_argument
.clone()
.separated_by(just(Token::Control(Control::Comma)))
.at_least(1)
.collect()
.delimited_by(
just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)),
);
let parsed_function = type_arguments
.or_not()
.then(
identifier
.clone()
.then(type_specification.clone())
.separated_by(just(Token::Control(Control::Comma)))
@ -222,16 +239,20 @@ pub fn parser<'src>() -> impl Parser<
just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)),
)
.then(type_specification.clone())
.then(block.clone())
.map_with(|((parameters, return_type), body), state| {
.then(r#type.clone())
.then(block.clone()),
)
.map_with(
|(type_arguments, ((parameters, return_type), body)), state| {
Expression::Value(ValueNode::ParsedFunction {
type_arguments: type_arguments.unwrap_or_else(|| Vec::with_capacity(0)),
parameters,
return_type,
body: body.with_position(state.span()),
})
.with_position(state.span())
});
},
);
let built_in_function = {
select! {
@ -246,21 +267,45 @@ pub fn parser<'src>() -> impl Parser<
.with_position(state.span())
});
use Operator::*;
let structure_field = identifier
.clone()
.then_ignore(just(Token::Operator(Operator::Assign)))
.then(positioned_expression.clone());
let structure_instance = identifier
.clone()
.then(
structure_field
.separated_by(just(Token::Control(Control::Comma)))
.allow_trailing()
.collect()
.delimited_by(
just(Token::Control(Control::CurlyOpen)),
just(Token::Control(Control::CurlyClose)),
),
)
.map_with(|(name, fields), state| {
Expression::Value(ValueNode::Structure { name, fields })
.with_position(state.span())
});
let atom = choice((
built_in_function.clone(),
range.clone(),
structure_instance.clone(),
parsed_function.clone(),
identifier_expression.clone(),
basic_value.clone(),
built_in_function.clone(),
list.clone(),
map.clone(),
basic_value.clone(),
identifier_expression.clone(),
positioned_expression.clone().delimited_by(
just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)),
),
));
use Operator::*;
let logic_math_indexes_and_function_calls = atom.pratt((
prefix(2, just(Token::Operator(Not)), |_, expression, span| {
Expression::Logic(Box::new(Logic::Not(expression))).with_position(span)
@ -395,32 +440,10 @@ pub fn parser<'src>() -> impl Parser<
),
));
let structure_field = identifier
.clone()
.then_ignore(just(Token::Operator(Operator::Assign)))
.then(positioned_expression.clone());
let structure_instance = identifier
.clone()
.then(
structure_field
.separated_by(just(Token::Control(Control::Comma)))
.allow_trailing()
.collect()
.delimited_by(
just(Token::Control(Control::CurlyOpen)),
just(Token::Control(Control::CurlyClose)),
),
)
.map_with(|(name, fields), state| {
Expression::Value(ValueNode::Structure { name, fields })
.with_position(state.span())
});
choice((
structure_instance,
range,
logic_math_indexes_and_function_calls,
range,
structure_instance,
parsed_function,
built_in_function,
list,
@ -742,7 +765,7 @@ mod tests {
#[test]
fn function_type() {
assert_eq!(
parse(&lex("foobar : () -> any = some_function").unwrap()).unwrap()[0].node,
parse(&lex("foobar : fn() -> any = some_function").unwrap()).unwrap()[0].node,
Statement::Assignment(Assignment::new(
Identifier::new("foobar").with_position((0, 6)),
Some(
@ -750,11 +773,11 @@ mod tests {
parameter_types: vec![],
return_type: Box::new(Type::Any)
}
.with_position((9, 18))
.with_position((9, 20))
),
AssignmentOperator::Assign,
Statement::Expression(Expression::Identifier(Identifier::new("some_function")))
.with_position((21, 34))
.with_position((23, 36))
),)
);
}
@ -762,9 +785,13 @@ mod tests {
#[test]
fn function_call() {
assert_eq!(
parse(&lex("output()").unwrap()).unwrap()[0].node,
parse(&lex("io.write_line()").unwrap()).unwrap()[0].node,
Statement::Expression(Expression::FunctionCall(FunctionCall::new(
Expression::Identifier(Identifier::new("output")).with_position((0, 6)),
Expression::MapIndex(Box::new(MapIndex::new(
Expression::Identifier(Identifier::new("io")).with_position((0, 2)),
Expression::Identifier(Identifier::new("write_line")).with_position((3, 13))
)))
.with_position((0, 13)),
Vec::with_capacity(0),
)))
)
@ -781,15 +808,45 @@ mod tests {
#[test]
fn function() {
assert_eq!(
parse(&lex("(x: int) : int { x }").unwrap()).unwrap()[0].node,
parse(&lex("(x: int) int { x }").unwrap()).unwrap()[0].node,
Statement::Expression(Expression::Value(ValueNode::ParsedFunction {
type_arguments: Vec::with_capacity(0),
parameters: vec![(Identifier::new("x"), Type::Integer.with_position((4, 7)))],
return_type: Type::Integer.with_position((11, 14)),
return_type: Type::Integer.with_position((9, 12)),
body: Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x")
),)
.with_position((17, 18))])
.with_position((0, 20))
.with_position((15, 16))])
.with_position((0, 18)),
}),)
)
}
#[test]
fn function_with_type_arguments() {
assert_eq!(
parse(&lex("(T, U)(x: T, y: U) T { x }").unwrap()).unwrap()[0].node,
Statement::Expression(Expression::Value(ValueNode::ParsedFunction {
type_arguments: vec![
Type::Argument(Identifier::new("T")).with_position((1, 2)),
Type::Argument(Identifier::new("U")).with_position((4, 5)),
],
parameters: vec![
(
Identifier::new("x"),
Type::Argument(Identifier::new("T")).with_position((10, 11))
),
(
Identifier::new("y"),
Type::Argument(Identifier::new("U")).with_position((16, 17))
)
],
return_type: Type::Argument(Identifier::new("T")).with_position((19, 20)),
body: Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x")
),)
.with_position((23, 24))])
.with_position((0, 26)),
}),)
)
}

View File

@ -56,12 +56,14 @@ impl Value {
}
pub fn function(
type_arguments: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>,
body: WithPosition<Block>,
) -> Self {
Value(Arc::new(ValueInner::Function(Function::Parsed(
ParsedFunction {
type_arguments,
parameters,
return_type,
body,
@ -137,10 +139,25 @@ impl Display for Value {
ValueInner::Range(_) => todo!(),
ValueInner::String(string) => write!(f, "{string}"),
ValueInner::Function(Function::Parsed(ParsedFunction {
type_arguments,
parameters,
return_type,
body,
})) => {
if !type_arguments.is_empty() {
write!(f, "(")?;
for (index, r#type) in type_arguments.into_iter().enumerate() {
if index == type_arguments.len() - 1 {
write!(f, "{}", r#type.node)?;
} else {
write!(f, "{} ", r#type.node)?;
}
}
write!(f, ")")?;
}
write!(f, "(")?;
for (identifier, r#type) in parameters {
@ -326,6 +343,7 @@ impl Function {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ParsedFunction {
type_arguments: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>,
body: WithPosition<Block>,

View File

@ -1,19 +1,27 @@
use std::rc::Rc;
use dust_lang::*;
#[test]
fn logic() {
assert_eq!(interpret("1 == 1").unwrap(), Some(Value::boolean(true)));
assert_eq!(
interpret("('42' == '42') && (42 != 0)").unwrap(),
interpret(Rc::new("test".to_string()), "1 == 1").unwrap(),
Some(Value::boolean(true))
);
assert_eq!(
interpret(Rc::new("test".to_string()), "('42' == '42') && (42 != 0)").unwrap(),
Some(Value::boolean(true))
);
}
#[test]
fn math() {
assert_eq!(interpret("1 + 1").unwrap(), Some(Value::integer(2)));
assert_eq!(
interpret("2 * (21 + 19 + 1 * 2) / 2").unwrap(),
interpret(Rc::new("test".to_string()), "1 + 1").unwrap(),
Some(Value::integer(2))
);
assert_eq!(
interpret(Rc::new("test".to_string()), "2 * (21 + 19 + 1 * 2) / 2").unwrap(),
Some(Value::integer(42))
);
}
@ -21,16 +29,19 @@ fn math() {
#[test]
fn list_index() {
assert_eq!(
interpret("foo = [1, 2, 3]; foo[2]").unwrap(),
interpret(Rc::new("test".to_string()), "foo = [1, 2, 3]; foo[2]").unwrap(),
Some(Value::integer(3))
);
}
#[test]
fn map_index() {
assert_eq!(interpret("{ x = 3 }.x").unwrap(), Some(Value::integer(3)));
assert_eq!(
interpret("foo = { x = 3 }; foo.x").unwrap(),
interpret(Rc::new("test".to_string()), "{ x = 3 }.x").unwrap(),
Some(Value::integer(3))
);
assert_eq!(
interpret(Rc::new("test".to_string()), "foo = { x = 3 }; foo.x").unwrap(),
Some(Value::integer(3))
);
}

View File

@ -1,3 +1,5 @@
use std::rc::Rc;
use dust_lang::{
abstract_tree::Identifier,
error::{Error, ValidationError},
@ -8,8 +10,9 @@ use dust_lang::{
fn function_call() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
foobar = (message : str) : str { message }
foobar = (message : str) str { message }
foobar('Hiya')
",
),
@ -21,8 +24,9 @@ fn function_call() {
fn call_empty_function() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
foobar = (message : str) : none {}
foobar = (message : str) none {}
foobar('Hiya')
",
),
@ -34,11 +38,12 @@ fn call_empty_function() {
fn callback() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
foobar = (cb : () -> str) : str {
foobar = (cb: fn() -> str) str {
cb()
}
foobar(() : str { 'Hiya' })
foobar(() str { 'Hiya' })
",
),
Ok(Some(Value::string("Hiya".to_string())))
@ -47,30 +52,37 @@ fn callback() {
#[test]
fn built_in_function_call() {
assert_eq!(interpret("io.write_line('Hiya')"), Ok(None));
assert_eq!(
interpret(Rc::new("test".to_string()), "io.write_line('Hiya')"),
Ok(None)
);
}
#[test]
fn function_context_does_not_capture_values() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
x = 1
foo = () : any { x }
foo = () any { x }
"
),
Err(vec![Error::Validation {
)
.unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::VariableNotFound(Identifier::new("x")),
position: (32, 52).into()
}])
position: (32, 50).into()
}]
);
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
x = 1
foo = (x : int) : int { x }
foo = (x: int) int { x }
foo(2)
"
),
@ -82,9 +94,10 @@ fn function_context_does_not_capture_values() {
fn function_context_captures_functions() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
bar = () : int { 2 }
foo = () : int { bar() }
bar = () int { 2 }
foo = () int { bar() }
foo()
"
),
@ -96,8 +109,9 @@ fn function_context_captures_functions() {
fn recursion() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
fib = (i : int) : int {
fib = (i: int) int {
if i <= 1 {
1
} else {

View File

@ -1,9 +1,12 @@
use std::rc::Rc;
use dust_lang::*;
#[test]
fn async_block() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
x = 41
async {
@ -21,6 +24,7 @@ fn async_block() {
fn loops_and_breaks() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
i = 0
loop {
@ -37,6 +41,7 @@ fn loops_and_breaks() {
);
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
foobar = {
while true {
@ -55,7 +60,7 @@ fn loops_and_breaks() {
#[test]
fn r#if() {
assert_eq!(
interpret("if true { 'foobar' }"),
interpret(Rc::new("test".to_string()), "if true { 'foobar' }"),
Ok(Some(Value::string("foobar".to_string())))
)
}
@ -63,7 +68,10 @@ fn r#if() {
#[test]
fn if_else() {
assert_eq!(
interpret("if false { 'foo' } else { 'bar' }"),
interpret(
Rc::new("test".to_string()),
"if false { 'foo' } else { 'bar' }"
),
Ok(Some(Value::string("bar".to_string())))
)
}

View File

@ -1,3 +1,5 @@
use std::rc::Rc;
use dust_lang::{
abstract_tree::{Identifier, Type},
error::{Error, TypeConflict, ValidationError},
@ -7,6 +9,7 @@ use dust_lang::{
fn simple_structure() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
struct Foo {
bar : int,
@ -33,6 +36,7 @@ fn simple_structure() {
fn field_type_error() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
struct Foo {
bar : int,
@ -42,8 +46,10 @@ fn field_type_error() {
bar = 'hiya',
}
"
),
Err(vec![Error::Validation {
)
.unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::TypeCheck {
conflict: TypeConflict {
actual: Type::String,
@ -53,7 +59,7 @@ fn field_type_error() {
expected_position: (56, 59).into()
},
position: (96, 153).into()
}])
}]
)
}
@ -61,6 +67,7 @@ fn field_type_error() {
fn nested_structure() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
struct Bar {
baz : int
@ -93,15 +100,18 @@ fn nested_structure() {
fn undefined_struct() {
assert_eq!(
interpret(
Rc::new("test".to_string()),
"
Foo {
bar = 42
}
"
),
Err(vec![Error::Validation {
)
.unwrap_err()
.errors(),
&vec![Error::Validation {
error: error::ValidationError::VariableNotFound(Identifier::new("Foo")),
position: (17, 69).into()
}])
}]
)
}

View File

@ -1,4 +1,4 @@
use std::collections::BTreeMap;
use std::{collections::BTreeMap, rc::Rc};
use dust_lang::{
abstract_tree::{Identifier, Type},
@ -8,25 +8,37 @@ use dust_lang::{
#[test]
fn none() {
assert_eq!(interpret("x = 9"), Ok(None));
assert_eq!(interpret("x = 1 + 1"), Ok(None));
assert_eq!(interpret(Rc::new("test".to_string()), "x = 9"), Ok(None));
assert_eq!(
interpret(Rc::new("test".to_string()), "x = 1 + 1"),
Ok(None)
);
}
#[test]
fn integer() {
assert_eq!(interpret("1"), Ok(Some(Value::integer(1))));
assert_eq!(interpret("123"), Ok(Some(Value::integer(123))));
assert_eq!(interpret("-666"), Ok(Some(Value::integer(-666))));
assert_eq!(
interpret(Rc::new("test".to_string()), "1"),
Ok(Some(Value::integer(1)))
);
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]
fn integer_saturation() {
assert_eq!(
interpret("9223372036854775807 + 1"),
interpret(Rc::new("test".to_string()), "9223372036854775807 + 1"),
Ok(Some(Value::integer(i64::MAX)))
);
assert_eq!(
interpret("-9223372036854775808 - 1"),
interpret(Rc::new("test".to_string()), "-9223372036854775808 - 1"),
Ok(Some(Value::integer(i64::MIN)))
);
}
@ -34,11 +46,11 @@ fn integer_saturation() {
#[test]
fn float() {
assert_eq!(
interpret("1.7976931348623157e308"),
interpret(Rc::new("test".to_string()), "1.7976931348623157e308"),
Ok(Some(Value::float(f64::MAX)))
);
assert_eq!(
interpret("-1.7976931348623157e308"),
interpret(Rc::new("test".to_string()), "-1.7976931348623157e308"),
Ok(Some(Value::float(f64::MIN)))
);
}
@ -46,11 +58,11 @@ fn float() {
#[test]
fn float_saturation() {
assert_eq!(
interpret("1.7976931348623157e308 + 1"),
interpret(Rc::new("test".to_string()), "1.7976931348623157e308 + 1"),
Ok(Some(Value::float(f64::MAX)))
);
assert_eq!(
interpret("-1.7976931348623157e308 - 1"),
interpret(Rc::new("test".to_string()), "-1.7976931348623157e308 - 1"),
Ok(Some(Value::float(f64::MIN)))
);
}
@ -58,27 +70,27 @@ fn float_saturation() {
#[test]
fn string() {
assert_eq!(
interpret("\"one\""),
interpret(Rc::new("test".to_string()), "\"one\""),
Ok(Some(Value::string("one".to_string())))
);
assert_eq!(
interpret("'one'"),
interpret(Rc::new("test".to_string()), "'one'"),
Ok(Some(Value::string("one".to_string())))
);
assert_eq!(
interpret("`one`"),
interpret(Rc::new("test".to_string()), "`one`"),
Ok(Some(Value::string("one".to_string())))
);
assert_eq!(
interpret("`'one'`"),
interpret(Rc::new("test".to_string()), "`'one'`"),
Ok(Some(Value::string("'one'".to_string())))
);
assert_eq!(
interpret("'`one`'"),
interpret(Rc::new("test".to_string()), "'`one`'"),
Ok(Some(Value::string("`one`".to_string())))
);
assert_eq!(
interpret("\"'one'\""),
interpret(Rc::new("test".to_string()), "\"'one'\""),
Ok(Some(Value::string("'one'".to_string())))
);
}
@ -86,7 +98,7 @@ fn string() {
#[test]
fn list() {
assert_eq!(
interpret("[1, 2, 'foobar']"),
interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"),
Ok(Some(Value::list(vec![
Value::integer(1),
Value::integer(2),
@ -97,7 +109,10 @@ fn list() {
#[test]
fn empty_list() {
assert_eq!(interpret("[]"), Ok(Some(Value::list(Vec::new()))));
assert_eq!(
interpret(Rc::new("test".to_string()), "[]"),
Ok(Some(Value::list(Vec::new())))
);
}
#[test]
@ -108,14 +123,17 @@ fn map() {
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
assert_eq!(
interpret("{ x = 1, foo = 'bar' }"),
interpret(Rc::new("test".to_string()), "{ x = 1, foo = 'bar' }"),
Ok(Some(Value::map(map)))
);
}
#[test]
fn empty_map() {
assert_eq!(interpret("{}"), Ok(Some(Value::map(BTreeMap::new()))));
assert_eq!(
interpret(Rc::new("test".to_string()), "{}"),
Ok(Some(Value::map(BTreeMap::new())))
);
}
#[test]
@ -126,7 +144,10 @@ fn map_types() {
map.insert(Identifier::new("foo"), Value::string("bar".to_string()));
assert_eq!(
interpret("{ x : int = 1, foo : str = 'bar' }"),
interpret(
Rc::new("test".to_string()),
"{ x : int = 1, foo : str = 'bar' }"
),
Ok(Some(Value::map(map)))
);
}
@ -134,8 +155,10 @@ fn map_types() {
#[test]
fn map_type_errors() {
assert_eq!(
interpret("{ foo : bool = 'bar' }"),
Err(vec![Error::Validation {
interpret(Rc::new("test".to_string()), "{ foo : bool = 'bar' }")
.unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::TypeCheck {
conflict: TypeConflict {
actual: Type::String,
@ -145,11 +168,14 @@ fn map_type_errors() {
expected_position: (8, 12).into(),
},
position: (0, 22).into()
}])
}]
);
}
#[test]
fn range() {
assert_eq!(interpret("0..100"), Ok(Some(Value::range(0..100))));
assert_eq!(
interpret(Rc::new("test".to_string()), "0..100"),
Ok(Some(Value::range(0..100)))
);
}

View File

@ -1,3 +1,5 @@
use std::rc::Rc;
use dust_lang::{
abstract_tree::{AbstractNode, Block, Expression, Identifier, Statement, Type},
error::{Error, TypeConflict, ValidationError},
@ -7,7 +9,7 @@ use dust_lang::{
#[test]
fn set_and_get_variable() {
assert_eq!(
interpret("foobar = true; foobar"),
interpret(Rc::new("test".to_string()), "foobar = true; foobar"),
Ok(Some(Value::boolean(true)))
);
}
@ -15,7 +17,7 @@ fn set_and_get_variable() {
#[test]
fn set_variable_with_type() {
assert_eq!(
interpret("foobar: bool = true; foobar"),
interpret(Rc::new("test".to_string()), "foobar: bool = true; foobar"),
Ok(Some(Value::boolean(true)))
);
}
@ -23,8 +25,10 @@ fn set_variable_with_type() {
#[test]
fn set_variable_with_type_error() {
assert_eq!(
interpret("foobar: str = true"),
Err(vec![Error::Validation {
interpret(Rc::new("test".to_string()), "foobar: str = true")
.unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::TypeCheck {
conflict: TypeConflict {
actual: Type::Boolean,
@ -34,22 +38,26 @@ fn set_variable_with_type_error() {
expected_position: (8, 11).into()
},
position: (0, 18).into()
}])
}]
);
}
#[test]
fn function_variable() {
assert_eq!(
interpret("foobar = (x: int): int { x }; foobar"),
interpret(
Rc::new("test".to_string()),
"foobar = (x: int) int { x }; foobar"
),
Ok(Some(Value::function(
Vec::with_capacity(0),
vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))],
Type::Integer.with_position((19, 22)),
Type::Integer.with_position((18, 21)),
Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x")
))
.with_position((25, 26))])
.with_position((9, 28))
.with_position((24, 25))])
.with_position((9, 27))
)))
);
}

View File

@ -1,4 +1,10 @@
use std::{borrow::Cow, io::stderr, path::PathBuf, process::Command, rc::Rc};
use std::{
borrow::Cow,
io::{self, stderr},
path::PathBuf,
process::Command,
rc::Rc,
};
use ariadne::sources;
use dust_lang::{
@ -12,9 +18,7 @@ use reedline::{
SqliteBackedHistory, Suggestion,
};
use crate::error::Error;
pub fn run_shell(context: Context) -> Result<(), Error> {
pub fn run_shell(context: Context) -> Result<(), io::Error> {
let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings();
@ -76,18 +80,16 @@ pub fn run_shell(context: Context) -> Result<(), Error> {
continue;
}
let run_result = interpreter.run(&buffer);
let run_result = interpreter.run(Rc::new("input".to_string()), &buffer);
match run_result {
Ok(Some(value)) => {
println!("{value}")
}
Ok(None) => {}
Err(errors) => {
Err(error) => {
let source_id = Rc::new("input".to_string());
let reports = Error::Dust { errors }
.build_reports(source_id.clone())
.unwrap();
let reports = error.build_reports();
for report in reports {
report

View File

@ -1,263 +0,0 @@
use ariadne::{Color, Fmt, Label, Report, ReportKind};
use dust_lang::{
abstract_tree::Type,
error::{Error as DustError, RuntimeError, TypeConflict, ValidationError},
};
use std::{
fmt::{self, Display, Formatter},
io,
ops::Range,
rc::Rc,
};
#[derive(Debug)]
pub enum Error {
Dust {
errors: Vec<dust_lang::error::Error>,
},
Io(io::Error),
}
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::Io(error)
}
}
impl Error {
pub fn build_reports<'id>(
self,
source_id: Rc<String>,
) -> 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!(),
ValidationError::ExpectedIntegerFloatOrString { actual, position } => {
builder = builder.with_message(format!(
"Expected an {}, {} or {}.",
Type::Integer.fg(type_color),
Type::Float.fg(type_color),
Type::String.fg(type_color)
));
builder.add_labels([Label::new((
source_id.clone(),
position.0..position.1,
))
.with_message(format!("This has type {}.", actual.fg(type_color),))])
}
}
}
let report = builder.finish();
reports.push(report);
}
return Ok(reports);
} else {
return Ok(Vec::with_capacity(0));
};
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Error::Dust { errors } => {
for error in errors {
writeln!(f, "{error:?}")?;
}
Ok(())
}
Error::Io(io_error) => {
write!(f, "{io_error}")
}
}
}
}

View File

@ -1,12 +1,10 @@
//! 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,
@ -14,7 +12,7 @@ use std::{
rc::Rc,
};
use dust_lang::{context::Context, interpret};
use dust_lang::{context::Context, interpret, interpret_without_std};
/// Command-line arguments to be parsed.
#[derive(Parser, Debug)]
@ -24,6 +22,9 @@ struct Args {
#[arg(short, long)]
command: Option<String>,
#[arg(long)]
no_std: bool,
/// Location of the file to run.
path: Option<String>,
}
@ -54,7 +55,11 @@ fn main() {
return;
};
let eval_result = interpret(&source);
let eval_result = if args.no_std {
interpret_without_std(source_id.clone(), &source)
} else {
interpret(source_id.clone(), &source)
};
match eval_result {
Ok(value) => {
@ -62,27 +67,10 @@ fn main() {
println!("{value}")
}
}
Err(errors) => {
let reports = Error::Dust { errors }
.build_reports(source_id.clone())
.unwrap();
for report in reports {
Err(error) => {
for report in error.build_reports() {
report
.write_for_stdout(
sources([
(source_id.clone(), source.as_str()),
(
Rc::new("std/io.ds".to_string()),
include_str!("../../std/io.ds"),
),
(
Rc::new("std/thread.ds".to_string()),
include_str!("../../std/thread.ds"),
),
]),
stderr(),
)
.write_for_stdout(sources([(source_id.clone(), source.as_str())]), stderr())
.unwrap();
}
}

View File

@ -1,9 +1,9 @@
io = {
read_line = () : str {
read_line = () str {
__READ_LINE__()
}
write_line = (output: str) : none {
write_line = (T)(output: T) none {
__WRITE_LINE__(output)
}
}

View File

@ -1,5 +1,5 @@
thread = {
sleep = (milliseconds: int) : none {
sleep = (milliseconds: int) none {
__SLEEP__(milliseconds)
}
}