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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29" checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29"
dependencies = [ dependencies = [
"concolor",
"unicode-width", "unicode-width",
"yansi", "yansi",
] ]
@ -262,26 +261,6 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.6" version = "0.8.6"
@ -537,12 +516,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.9" version = "0.5.9"
@ -591,17 +564,6 @@ dependencies = [
"hashbrown", "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]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.1" version = "0.12.1"
@ -1456,15 +1418,6 @@ dependencies = [
"windows-targets 0.52.4", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@ -1483,21 +1436,6 @@ dependencies = [
"windows-targets 0.52.4", "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]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.5" version = "0.48.5"
@ -1528,12 +1466,6 @@ dependencies = [
"windows_x86_64_msvc 0.52.4", "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]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.5" version = "0.48.5"
@ -1546,12 +1478,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@ -1564,12 +1490,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@ -1582,12 +1502,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@ -1600,12 +1514,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 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]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@ -1618,12 +1526,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 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]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
@ -1636,12 +1538,6 @@ version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 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]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,18 +6,256 @@ pub mod lexer;
pub mod parser; pub mod parser;
pub mod value; pub mod value;
use std::{ops::Range, rc::Rc};
use abstract_tree::Type;
use ariadne::{Color, Fmt, Label, Report, ReportKind};
use context::Context; use context::Context;
use error::Error; use error::{Error, RuntimeError, TypeConflict, ValidationError};
use lexer::lex; use lexer::lex;
use parser::parse; use parser::parse;
pub use value::Value; 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()); let mut interpreter = Interpreter::new(Context::new());
interpreter.run(include_str!("../../std/io.ds"))?; interpreter.load_std()?;
interpreter.run(include_str!("../../std/thread.ds"))?; interpreter.run(source_id, source)
interpreter.run(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 { pub struct Interpreter {
@ -29,11 +267,36 @@ impl Interpreter {
Interpreter { context } Interpreter { context }
} }
pub fn run(&mut self, source: &str) -> Result<Option<Value>, Vec<Error>> { pub fn run(
let tokens = lex(source)?; &mut self,
let abstract_tree = parse(&tokens)?; source_id: Rc<String>,
let value_option = abstract_tree.run(&self.context)?; 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) 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())); .map_with(|value, state| Expression::Value(value).with_position(state.span()));
let r#type = recursive(|r#type| { let r#type = recursive(|r#type| {
let function_type = r#type let function_type = just(Token::Keyword(Keyword::Fn))
.clone() .ignore_then(
.separated_by(just(Token::Control(Control::Comma))) r#type
.collect() .clone()
.delimited_by( .separated_by(just(Token::Control(Control::Comma)))
just(Token::Control(Control::ParenOpen)), .collect()
just(Token::Control(Control::ParenClose)), .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()) .then(r#type.clone())
.map(|(parameter_types, return_type)| Type::Function { .map(|(parameter_types, return_type)| Type::Function {
parameter_types, parameter_types,
@ -110,20 +113,21 @@ pub fn parser<'src>() -> impl Parser<
just(Token::Keyword(Keyword::Range)).to(Type::Range), just(Token::Keyword(Keyword::Range)).to(Type::Range),
just(Token::Keyword(Keyword::Str)).to(Type::String), just(Token::Keyword(Keyword::Str)).to(Type::String),
just(Token::Keyword(Keyword::List)).to(Type::List), just(Token::Keyword(Keyword::List)).to(Type::List),
identifier.clone().try_map(move |identifier, span| { identifier.clone().map(move |identifier| {
custom_types if let Some(r#type) = custom_types.0.borrow().get(&identifier) {
.0 r#type.clone()
.borrow() } else {
.get(&identifier) Type::Argument(identifier)
.cloned() }
.ok_or_else(|| {
Rich::custom(span, format!("There is no type named {identifier}."))
})
}), }),
)) ))
}) })
.map_with(|r#type, state| r#type.with_position(state.span())); .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 type_specification = just(Token::Control(Control::Colon)).ignore_then(r#type.clone());
let structure_field_definition = identifier.clone().then(type_specification.clone()); let structure_field_definition = identifier.clone().then(type_specification.clone());
@ -213,25 +217,42 @@ pub fn parser<'src>() -> impl Parser<
.with_position(state.span()) .with_position(state.span())
}); });
let parsed_function = identifier let type_arguments = type_argument
.clone() .clone()
.then(type_specification.clone())
.separated_by(just(Token::Control(Control::Comma))) .separated_by(just(Token::Control(Control::Comma)))
.at_least(1)
.collect() .collect()
.delimited_by( .delimited_by(
just(Token::Control(Control::ParenOpen)), just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)), 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)))
.collect()
.delimited_by(
just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)),
)
.then(r#type.clone())
.then(block.clone()),
) )
.then(type_specification.clone()) .map_with(
.then(block.clone()) |(type_arguments, ((parameters, return_type), body)), state| {
.map_with(|((parameters, return_type), body), state| { Expression::Value(ValueNode::ParsedFunction {
Expression::Value(ValueNode::ParsedFunction { type_arguments: type_arguments.unwrap_or_else(|| Vec::with_capacity(0)),
parameters, parameters,
return_type, return_type,
body: body.with_position(state.span()), body: body.with_position(state.span()),
}) })
.with_position(state.span()) .with_position(state.span())
}); },
);
let built_in_function = { let built_in_function = {
select! { select! {
@ -246,21 +267,45 @@ pub fn parser<'src>() -> impl Parser<
.with_position(state.span()) .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(( let atom = choice((
built_in_function.clone(), range.clone(),
structure_instance.clone(),
parsed_function.clone(), parsed_function.clone(),
identifier_expression.clone(), built_in_function.clone(),
basic_value.clone(),
list.clone(), list.clone(),
map.clone(), map.clone(),
basic_value.clone(),
identifier_expression.clone(),
positioned_expression.clone().delimited_by( positioned_expression.clone().delimited_by(
just(Token::Control(Control::ParenOpen)), just(Token::Control(Control::ParenOpen)),
just(Token::Control(Control::ParenClose)), just(Token::Control(Control::ParenClose)),
), ),
)); ));
use Operator::*;
let logic_math_indexes_and_function_calls = atom.pratt(( let logic_math_indexes_and_function_calls = atom.pratt((
prefix(2, just(Token::Operator(Not)), |_, expression, span| { prefix(2, just(Token::Operator(Not)), |_, expression, span| {
Expression::Logic(Box::new(Logic::Not(expression))).with_position(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(( choice((
structure_instance,
range,
logic_math_indexes_and_function_calls, logic_math_indexes_and_function_calls,
range,
structure_instance,
parsed_function, parsed_function,
built_in_function, built_in_function,
list, list,
@ -742,7 +765,7 @@ mod tests {
#[test] #[test]
fn function_type() { fn function_type() {
assert_eq!( 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( Statement::Assignment(Assignment::new(
Identifier::new("foobar").with_position((0, 6)), Identifier::new("foobar").with_position((0, 6)),
Some( Some(
@ -750,11 +773,11 @@ mod tests {
parameter_types: vec![], parameter_types: vec![],
return_type: Box::new(Type::Any) return_type: Box::new(Type::Any)
} }
.with_position((9, 18)) .with_position((9, 20))
), ),
AssignmentOperator::Assign, AssignmentOperator::Assign,
Statement::Expression(Expression::Identifier(Identifier::new("some_function"))) Statement::Expression(Expression::Identifier(Identifier::new("some_function")))
.with_position((21, 34)) .with_position((23, 36))
),) ),)
); );
} }
@ -762,9 +785,13 @@ mod tests {
#[test] #[test]
fn function_call() { fn function_call() {
assert_eq!( assert_eq!(
parse(&lex("output()").unwrap()).unwrap()[0].node, parse(&lex("io.write_line()").unwrap()).unwrap()[0].node,
Statement::Expression(Expression::FunctionCall(FunctionCall::new( 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), Vec::with_capacity(0),
))) )))
) )
@ -781,15 +808,45 @@ mod tests {
#[test] #[test]
fn function() { fn function() {
assert_eq!( 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 { Statement::Expression(Expression::Value(ValueNode::ParsedFunction {
type_arguments: Vec::with_capacity(0),
parameters: vec![(Identifier::new("x"), Type::Integer.with_position((4, 7)))], 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( body: Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x") Identifier::new("x")
),) ),)
.with_position((17, 18))]) .with_position((15, 16))])
.with_position((0, 20)) .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( pub fn function(
type_arguments: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>, parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>, return_type: WithPosition<Type>,
body: WithPosition<Block>, body: WithPosition<Block>,
) -> Self { ) -> Self {
Value(Arc::new(ValueInner::Function(Function::Parsed( Value(Arc::new(ValueInner::Function(Function::Parsed(
ParsedFunction { ParsedFunction {
type_arguments,
parameters, parameters,
return_type, return_type,
body, body,
@ -137,10 +139,25 @@ impl Display for Value {
ValueInner::Range(_) => todo!(), ValueInner::Range(_) => todo!(),
ValueInner::String(string) => write!(f, "{string}"), ValueInner::String(string) => write!(f, "{string}"),
ValueInner::Function(Function::Parsed(ParsedFunction { ValueInner::Function(Function::Parsed(ParsedFunction {
type_arguments,
parameters, parameters,
return_type, return_type,
body, 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, "(")?; write!(f, "(")?;
for (identifier, r#type) in parameters { for (identifier, r#type) in parameters {
@ -326,6 +343,7 @@ impl Function {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct ParsedFunction { pub struct ParsedFunction {
type_arguments: Vec<WithPosition<Type>>,
parameters: Vec<(Identifier, WithPosition<Type>)>, parameters: Vec<(Identifier, WithPosition<Type>)>,
return_type: WithPosition<Type>, return_type: WithPosition<Type>,
body: WithPosition<Block>, body: WithPosition<Block>,

View File

@ -1,19 +1,27 @@
use std::rc::Rc;
use dust_lang::*; use dust_lang::*;
#[test] #[test]
fn logic() { fn logic() {
assert_eq!(interpret("1 == 1").unwrap(), Some(Value::boolean(true)));
assert_eq!( 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)) Some(Value::boolean(true))
); );
} }
#[test] #[test]
fn math() { fn math() {
assert_eq!(interpret("1 + 1").unwrap(), Some(Value::integer(2)));
assert_eq!( 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)) Some(Value::integer(42))
); );
} }
@ -21,16 +29,19 @@ fn math() {
#[test] #[test]
fn list_index() { fn list_index() {
assert_eq!( 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)) Some(Value::integer(3))
); );
} }
#[test] #[test]
fn map_index() { fn map_index() {
assert_eq!(interpret("{ x = 3 }.x").unwrap(), Some(Value::integer(3)));
assert_eq!( 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)) Some(Value::integer(3))
); );
} }

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use std::collections::BTreeMap; use std::{collections::BTreeMap, rc::Rc};
use dust_lang::{ use dust_lang::{
abstract_tree::{Identifier, Type}, abstract_tree::{Identifier, Type},
@ -8,25 +8,37 @@ use dust_lang::{
#[test] #[test]
fn none() { fn none() {
assert_eq!(interpret("x = 9"), Ok(None)); assert_eq!(interpret(Rc::new("test".to_string()), "x = 9"), Ok(None));
assert_eq!(interpret("x = 1 + 1"), Ok(None)); assert_eq!(
interpret(Rc::new("test".to_string()), "x = 1 + 1"),
Ok(None)
);
} }
#[test] #[test]
fn integer() { fn integer() {
assert_eq!(interpret("1"), Ok(Some(Value::integer(1)))); assert_eq!(
assert_eq!(interpret("123"), Ok(Some(Value::integer(123)))); interpret(Rc::new("test".to_string()), "1"),
assert_eq!(interpret("-666"), Ok(Some(Value::integer(-666)))); 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] #[test]
fn integer_saturation() { fn integer_saturation() {
assert_eq!( assert_eq!(
interpret("9223372036854775807 + 1"), interpret(Rc::new("test".to_string()), "9223372036854775807 + 1"),
Ok(Some(Value::integer(i64::MAX))) Ok(Some(Value::integer(i64::MAX)))
); );
assert_eq!( assert_eq!(
interpret("-9223372036854775808 - 1"), interpret(Rc::new("test".to_string()), "-9223372036854775808 - 1"),
Ok(Some(Value::integer(i64::MIN))) Ok(Some(Value::integer(i64::MIN)))
); );
} }
@ -34,11 +46,11 @@ fn integer_saturation() {
#[test] #[test]
fn float() { fn float() {
assert_eq!( assert_eq!(
interpret("1.7976931348623157e308"), interpret(Rc::new("test".to_string()), "1.7976931348623157e308"),
Ok(Some(Value::float(f64::MAX))) Ok(Some(Value::float(f64::MAX)))
); );
assert_eq!( assert_eq!(
interpret("-1.7976931348623157e308"), interpret(Rc::new("test".to_string()), "-1.7976931348623157e308"),
Ok(Some(Value::float(f64::MIN))) Ok(Some(Value::float(f64::MIN)))
); );
} }
@ -46,11 +58,11 @@ fn float() {
#[test] #[test]
fn float_saturation() { fn float_saturation() {
assert_eq!( assert_eq!(
interpret("1.7976931348623157e308 + 1"), interpret(Rc::new("test".to_string()), "1.7976931348623157e308 + 1"),
Ok(Some(Value::float(f64::MAX))) Ok(Some(Value::float(f64::MAX)))
); );
assert_eq!( assert_eq!(
interpret("-1.7976931348623157e308 - 1"), interpret(Rc::new("test".to_string()), "-1.7976931348623157e308 - 1"),
Ok(Some(Value::float(f64::MIN))) Ok(Some(Value::float(f64::MIN)))
); );
} }
@ -58,27 +70,27 @@ fn float_saturation() {
#[test] #[test]
fn string() { fn string() {
assert_eq!( assert_eq!(
interpret("\"one\""), interpret(Rc::new("test".to_string()), "\"one\""),
Ok(Some(Value::string("one".to_string()))) Ok(Some(Value::string("one".to_string())))
); );
assert_eq!( assert_eq!(
interpret("'one'"), interpret(Rc::new("test".to_string()), "'one'"),
Ok(Some(Value::string("one".to_string()))) Ok(Some(Value::string("one".to_string())))
); );
assert_eq!( assert_eq!(
interpret("`one`"), interpret(Rc::new("test".to_string()), "`one`"),
Ok(Some(Value::string("one".to_string()))) Ok(Some(Value::string("one".to_string())))
); );
assert_eq!( assert_eq!(
interpret("`'one'`"), interpret(Rc::new("test".to_string()), "`'one'`"),
Ok(Some(Value::string("'one'".to_string()))) Ok(Some(Value::string("'one'".to_string())))
); );
assert_eq!( assert_eq!(
interpret("'`one`'"), interpret(Rc::new("test".to_string()), "'`one`'"),
Ok(Some(Value::string("`one`".to_string()))) Ok(Some(Value::string("`one`".to_string())))
); );
assert_eq!( assert_eq!(
interpret("\"'one'\""), interpret(Rc::new("test".to_string()), "\"'one'\""),
Ok(Some(Value::string("'one'".to_string()))) Ok(Some(Value::string("'one'".to_string())))
); );
} }
@ -86,7 +98,7 @@ fn string() {
#[test] #[test]
fn list() { fn list() {
assert_eq!( assert_eq!(
interpret("[1, 2, 'foobar']"), interpret(Rc::new("test".to_string()), "[1, 2, 'foobar']"),
Ok(Some(Value::list(vec![ Ok(Some(Value::list(vec![
Value::integer(1), Value::integer(1),
Value::integer(2), Value::integer(2),
@ -97,7 +109,10 @@ fn list() {
#[test] #[test]
fn empty_list() { 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] #[test]
@ -108,14 +123,17 @@ 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("{ x = 1, foo = 'bar' }"), interpret(Rc::new("test".to_string()), "{ x = 1, foo = 'bar' }"),
Ok(Some(Value::map(map))) Ok(Some(Value::map(map)))
); );
} }
#[test] #[test]
fn empty_map() { 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] #[test]
@ -126,7 +144,10 @@ 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("{ x : int = 1, foo : str = 'bar' }"), interpret(
Rc::new("test".to_string()),
"{ x : int = 1, foo : str = 'bar' }"
),
Ok(Some(Value::map(map))) Ok(Some(Value::map(map)))
); );
} }
@ -134,8 +155,10 @@ fn map_types() {
#[test] #[test]
fn map_type_errors() { fn map_type_errors() {
assert_eq!( assert_eq!(
interpret("{ foo : bool = 'bar' }"), interpret(Rc::new("test".to_string()), "{ foo : bool = 'bar' }")
Err(vec![Error::Validation { .unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::TypeCheck { error: ValidationError::TypeCheck {
conflict: TypeConflict { conflict: TypeConflict {
actual: Type::String, actual: Type::String,
@ -145,11 +168,14 @@ fn map_type_errors() {
expected_position: (8, 12).into(), expected_position: (8, 12).into(),
}, },
position: (0, 22).into() position: (0, 22).into()
}]) }]
); );
} }
#[test] #[test]
fn range() { 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::{ use dust_lang::{
abstract_tree::{AbstractNode, Block, Expression, Identifier, Statement, Type}, abstract_tree::{AbstractNode, Block, Expression, Identifier, Statement, Type},
error::{Error, TypeConflict, ValidationError}, error::{Error, TypeConflict, ValidationError},
@ -7,7 +9,7 @@ use dust_lang::{
#[test] #[test]
fn set_and_get_variable() { fn set_and_get_variable() {
assert_eq!( assert_eq!(
interpret("foobar = true; foobar"), interpret(Rc::new("test".to_string()), "foobar = true; foobar"),
Ok(Some(Value::boolean(true))) Ok(Some(Value::boolean(true)))
); );
} }
@ -15,7 +17,7 @@ fn set_and_get_variable() {
#[test] #[test]
fn set_variable_with_type() { fn set_variable_with_type() {
assert_eq!( assert_eq!(
interpret("foobar: bool = true; foobar"), interpret(Rc::new("test".to_string()), "foobar: bool = true; foobar"),
Ok(Some(Value::boolean(true))) Ok(Some(Value::boolean(true)))
); );
} }
@ -23,8 +25,10 @@ fn set_variable_with_type() {
#[test] #[test]
fn set_variable_with_type_error() { fn set_variable_with_type_error() {
assert_eq!( assert_eq!(
interpret("foobar: str = true"), interpret(Rc::new("test".to_string()), "foobar: str = true")
Err(vec![Error::Validation { .unwrap_err()
.errors(),
&vec![Error::Validation {
error: ValidationError::TypeCheck { error: ValidationError::TypeCheck {
conflict: TypeConflict { conflict: TypeConflict {
actual: Type::Boolean, actual: Type::Boolean,
@ -34,22 +38,26 @@ fn set_variable_with_type_error() {
expected_position: (8, 11).into() expected_position: (8, 11).into()
}, },
position: (0, 18).into() position: (0, 18).into()
}]) }]
); );
} }
#[test] #[test]
fn function_variable() { fn function_variable() {
assert_eq!( 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( Ok(Some(Value::function(
Vec::with_capacity(0),
vec![(Identifier::new("x"), Type::Integer.with_position((13, 16)))], 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( Block::new(vec![Statement::Expression(Expression::Identifier(
Identifier::new("x") Identifier::new("x")
)) ))
.with_position((25, 26))]) .with_position((24, 25))])
.with_position((9, 28)) .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 ariadne::sources;
use dust_lang::{ use dust_lang::{
@ -12,9 +18,7 @@ use reedline::{
SqliteBackedHistory, Suggestion, SqliteBackedHistory, Suggestion,
}; };
use crate::error::Error; pub fn run_shell(context: Context) -> Result<(), io::Error> {
pub fn run_shell(context: Context) -> Result<(), Error> {
let mut interpreter = Interpreter::new(context.clone()); let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings(); let mut keybindings = default_emacs_keybindings();
@ -76,18 +80,16 @@ pub fn run_shell(context: Context) -> Result<(), Error> {
continue; continue;
} }
let run_result = interpreter.run(&buffer); let run_result = interpreter.run(Rc::new("input".to_string()), &buffer);
match run_result { match run_result {
Ok(Some(value)) => { Ok(Some(value)) => {
println!("{value}") println!("{value}")
} }
Ok(None) => {} Ok(None) => {}
Err(errors) => { Err(error) => {
let source_id = Rc::new("input".to_string()); let source_id = Rc::new("input".to_string());
let reports = Error::Dust { errors } let reports = error.build_reports();
.build_reports(source_id.clone())
.unwrap();
for report in reports { for report in reports {
report 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. //! Command line interface for the dust programming language.
mod cli; mod cli;
mod error;
use ariadne::sources; use ariadne::sources;
use clap::Parser; use clap::Parser;
use cli::run_shell; use cli::run_shell;
use colored::Colorize; use colored::Colorize;
use error::Error;
use std::{ use std::{
fs::read_to_string, fs::read_to_string,
@ -14,7 +12,7 @@ use std::{
rc::Rc, rc::Rc,
}; };
use dust_lang::{context::Context, interpret}; use dust_lang::{context::Context, interpret, interpret_without_std};
/// Command-line arguments to be parsed. /// Command-line arguments to be parsed.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -24,6 +22,9 @@ struct Args {
#[arg(short, long)] #[arg(short, long)]
command: Option<String>, command: Option<String>,
#[arg(long)]
no_std: bool,
/// Location of the file to run. /// Location of the file to run.
path: Option<String>, path: Option<String>,
} }
@ -54,7 +55,11 @@ fn main() {
return; 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 { match eval_result {
Ok(value) => { Ok(value) => {
@ -62,27 +67,10 @@ fn main() {
println!("{value}") println!("{value}")
} }
} }
Err(errors) => { Err(error) => {
let reports = Error::Dust { errors } for report in error.build_reports() {
.build_reports(source_id.clone())
.unwrap();
for report in reports {
report report
.write_for_stdout( .write_for_stdout(sources([(source_id.clone(), source.as_str())]), stderr())
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(),
)
.unwrap(); .unwrap();
} }
} }

View File

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

View File

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