Start new rewrite of Dust

This commit is contained in:
Jeff 2024-09-06 19:27:16 -04:00
parent 5dcdfe83f9
commit 1ecaac0819
19 changed files with 209 additions and 10410 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,192 +0,0 @@
//! In-memory representation of a Dust program.
mod expression;
mod statement;
pub use expression::*;
pub use statement::*;
use std::{
collections::VecDeque,
fmt::{self, Debug, Display, Formatter},
num::TryFromIntError,
};
use serde::{Deserialize, Serialize};
use crate::{core_library, Context, ContextError};
pub type Span = (usize, usize);
/// In-memory representation of a Dust program.
#[derive(Clone, Serialize, Deserialize)]
pub struct AbstractSyntaxTree {
pub statements: VecDeque<Statement>,
#[serde(skip)]
pub context: Context,
}
impl Debug for AbstractSyntaxTree {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("AbstractSyntaxTree")
.field("statements", &self.statements)
.finish()
}
}
impl Eq for AbstractSyntaxTree {}
impl PartialEq for AbstractSyntaxTree {
fn eq(&self, other: &Self) -> bool {
self.statements == other.statements
}
}
impl PartialOrd for AbstractSyntaxTree {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for AbstractSyntaxTree {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.statements.cmp(&other.statements)
}
}
impl AbstractSyntaxTree {
pub fn new() -> Self {
Self {
statements: VecDeque::new(),
context: Context::new(),
}
}
pub fn with_statements<T: Into<VecDeque<Statement>>>(statements: T) -> Self {
Self {
statements: statements.into(),
context: Context::new(),
}
}
pub fn with_core_library() -> Self {
Self {
statements: VecDeque::new(),
context: core_library().create_child(),
}
}
}
impl Default for AbstractSyntaxTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Node<T> {
pub inner: T,
pub position: Span,
}
impl<T> Node<T> {
pub fn new(inner: T, position: Span) -> Self {
Self { inner, position }
}
}
impl<T: Display> Display for Node<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum AstError {
ContextError {
error: ContextError,
position: Span,
},
ExpectedFunctionOrConstructor {
position: Span,
},
ExpectedInteger {
position: Span,
},
ExpectedListType {
position: Span,
},
ExpectedNonEmptyEvaluation {
position: Span,
},
ExpectedNonEmptyList {
position: Span,
},
ExpectedRangeableType {
position: Span,
},
ExpectedStructFieldsType {
position: Span,
},
ExpectedTupleType {
position: Span,
},
FromIntError {
error: TryFromIntError,
position: Span,
},
}
impl AstError {
pub fn position(&self) -> Span {
match self {
AstError::ContextError { position, .. } => *position,
AstError::ExpectedFunctionOrConstructor { position } => *position,
AstError::ExpectedInteger { position } => *position,
AstError::ExpectedListType { position } => *position,
AstError::ExpectedNonEmptyEvaluation { position } => *position,
AstError::ExpectedNonEmptyList { position } => *position,
AstError::ExpectedRangeableType { position } => *position,
AstError::ExpectedStructFieldsType { position } => *position,
AstError::ExpectedTupleType { position } => *position,
AstError::FromIntError { position, .. } => *position,
}
}
}
impl Display for AstError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
AstError::ContextError { error, position } => {
write!(f, "Context error at {:?}: {}", position, error)
}
AstError::ExpectedFunctionOrConstructor { position } => {
write!(f, "Expected a function or constructor at {:?}", position)
}
AstError::ExpectedInteger { position } => {
write!(f, "Expected an integer at {:?}", position)
}
AstError::ExpectedListType { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedTupleType { position } => {
write!(f, "Expected a tuple type at {:?}", position)
}
AstError::ExpectedNonEmptyEvaluation { position } => {
write!(f, "Expected a type at {:?}", position)
}
AstError::ExpectedNonEmptyList { position } => {
write!(f, "Expected a non-empty list at {:?}", position)
}
AstError::ExpectedRangeableType { position } => {
write!(f, "Expected a rangeable type at {:?}", position)
}
AstError::ExpectedStructFieldsType { position } => {
write!(f, "Expected a struct type with fields at {:?}", position)
}
AstError::FromIntError { error, position } => {
write!(f, "Integer conversion error at {:?}: {}", position, error)
}
}
}
}

View File

@ -1,187 +0,0 @@
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::{Context, Identifier, Type, TypeEvaluation};
use super::{AstError, Expression, Node, Span};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Statement {
Expression(Expression),
ExpressionNullified(Node<Expression>),
Let(Node<LetStatement>),
StructDefinition(Node<StructDefinition>),
}
impl Statement {
pub fn struct_definition(struct_definition: StructDefinition, position: Span) -> Self {
Statement::StructDefinition(Node::new(struct_definition, position))
}
pub fn position(&self) -> Span {
match self {
Statement::Expression(expression) => expression.position(),
Statement::ExpressionNullified(expression_node) => expression_node.position,
Statement::Let(r#let) => r#let.position,
Statement::StructDefinition(definition) => definition.position,
}
}
pub fn type_evaluation(&self, context: &Context) -> Result<TypeEvaluation, AstError> {
match self {
Statement::Expression(expression) => expression.type_evaluation(context),
Statement::ExpressionNullified(expression_node) => {
let type_evaluation = expression_node.inner.type_evaluation(context)?;
if let TypeEvaluation::Break(_) = type_evaluation {
Ok(type_evaluation)
} else {
Ok(TypeEvaluation::Return(None))
}
}
Statement::Let(_) => Ok(TypeEvaluation::Return(None)),
Statement::StructDefinition(_) => Ok(TypeEvaluation::Return(None)),
}
}
}
impl Display for Statement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Statement::Expression(expression) => write!(f, "{}", expression),
Statement::ExpressionNullified(expression) => write!(f, "{};", expression),
Statement::Let(r#let) => write!(f, "{};", r#let),
Statement::StructDefinition(struct_definition) => match &struct_definition.inner {
StructDefinition::Unit { name } => write!(f, "struct {};", name),
StructDefinition::Tuple { name, items } => {
write!(f, "struct {name} {{ ")?;
for (index, item) in items.iter().enumerate() {
write!(f, "{}: {}", item, index)?;
if index < items.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{ ")?;
for (index, (field, r#type)) in fields.iter().enumerate() {
write!(f, "{}: {}", field, r#type)?;
if index < fields.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, " }}")
}
},
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LetStatement {
Let {
identifier: Node<Identifier>,
value: Expression,
},
LetMut {
identifier: Node<Identifier>,
value: Expression,
},
LetType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
LetMutType {
identifier: Node<Identifier>,
r#type: Node<Type>,
value: Expression,
},
}
impl Display for LetStatement {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LetStatement::Let { identifier, value } => {
write!(f, "let {identifier} = {value}")
}
LetStatement::LetMut { identifier, value } => {
write!(f, "let mut {identifier} = {value}")
}
LetStatement::LetType {
identifier,
r#type,
value,
} => {
write!(f, "let {identifier}: {type} = {value}")
}
LetStatement::LetMutType {
identifier,
r#type,
value,
} => {
write!(f, "let mut {identifier}: {type} = {value}")
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum StructDefinition {
Unit {
name: Node<Identifier>,
},
Tuple {
name: Node<Identifier>,
items: Vec<Node<Type>>,
},
Fields {
name: Node<Identifier>,
fields: Vec<(Node<Identifier>, Node<Type>)>,
},
}
impl Display for StructDefinition {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
StructDefinition::Unit { name } => write!(f, "struct {name}"),
StructDefinition::Tuple {
name,
items: fields,
} => {
write!(f, "struct {name} {{")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field}")?;
}
write!(f, "}}")
}
StructDefinition::Fields { name, fields } => {
write!(f, "struct {name} {{")?;
for (i, (field_name, field_type)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{field_name}: {field_type}")?;
}
write!(f, "}}")
}
}
}
}

View File

@ -1,208 +0,0 @@
//! Integrated functions that can be called from Dust code.
use std::{
error::Error,
fmt::{self, Display, Formatter},
io::{self, stdin, stdout, Write},
};
use serde::{Deserialize, Serialize};
use crate::{FunctionType, Identifier, Type, Value, ValueData, ValueError};
/// Integrated function that can be called from Dust code.
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunction {
// String tools
ToString,
// Integer and float tools
IsEven,
IsOdd,
// I/O
ReadLine,
WriteLine,
}
impl BuiltInFunction {
pub fn name(&self) -> &'static str {
match self {
BuiltInFunction::IsEven => "is_even",
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line",
}
}
pub fn type_parameters(&self) -> Option<Vec<Identifier>> {
match self {
BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None,
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => None,
}
}
pub fn value_parameters(&self) -> Option<Vec<(Identifier, Type)>> {
match self {
BuiltInFunction::ToString { .. } => Some(vec![("value".into(), Type::Any)]),
BuiltInFunction::IsEven => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::IsOdd => Some(vec![("value".into(), Type::Number)]),
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("value".into(), Type::Any)]),
}
}
pub fn return_type(&self) -> Option<Type> {
match self {
BuiltInFunction::ToString { .. } => Some(Type::String { length: None }),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::ReadLine => Some(Type::String { length: None }),
BuiltInFunction::WriteLine => None,
}
}
pub fn r#type(&self) -> Type {
Type::Function(FunctionType {
name: Identifier::new(self.name()),
type_parameters: self.type_parameters(),
value_parameters: self.value_parameters(),
return_type: self.return_type().map(Box::new),
})
}
pub fn call(
&self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
) -> Result<Option<Value>, BuiltInFunctionError> {
match (self.value_parameters(), &value_arguments) {
(Some(value_parameters), Some(value_arguments)) => {
if value_parameters.len() != value_arguments.len() {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
}
(Some(_), None) | (None, Some(_)) => {
return Err(BuiltInFunctionError::WrongNumberOfValueArguments);
}
(None, None) => {}
}
match self {
BuiltInFunction::ToString => {
Ok(Some(Value::string(value_arguments.unwrap()[0].to_string())))
}
BuiltInFunction::IsEven => {
let is_even = value_arguments.unwrap()[0].is_even()?;
Ok(Some(is_even))
}
BuiltInFunction::IsOdd => {
let is_odd = value_arguments.unwrap()[0].is_odd()?;
Ok(Some(is_odd))
}
BuiltInFunction::ReadLine => {
let mut input = String::new();
stdin().read_line(&mut input)?;
Ok(Some(Value::string(input.trim_end_matches('\n'))))
}
BuiltInFunction::WriteLine => {
let first_argument = &value_arguments.unwrap()[0];
match first_argument {
Value::Raw(ValueData::String(string)) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
Value::Reference(reference) => match reference.as_ref() {
ValueData::String(string) => {
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
},
Value::Mutable(locked) => {
let value_data = &*locked.read().unwrap();
let string = match value_data {
ValueData::String(string) => string,
_ => return Err(BuiltInFunctionError::ExpectedString),
};
let mut stdout = stdout();
stdout.write_all(string.as_bytes())?;
stdout.write_all(b"\n")?;
Ok(None)
}
_ => Err(BuiltInFunctionError::ExpectedString),
}
}
}
}
}
impl Display for BuiltInFunction {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum BuiltInFunctionError {
Io(io::ErrorKind),
ValueError(ValueError),
ExpectedString,
ExpectedList,
ExpectedInteger,
WrongNumberOfValueArguments,
}
impl From<ValueError> for BuiltInFunctionError {
fn from(v: ValueError) -> Self {
Self::ValueError(v)
}
}
impl From<io::Error> for BuiltInFunctionError {
fn from(error: io::Error) -> Self {
Self::Io(error.kind())
}
}
impl Error for BuiltInFunctionError {}
impl Display for BuiltInFunctionError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
BuiltInFunctionError::Io(error_kind) => write!(f, "I/O error: {}", error_kind),
BuiltInFunctionError::ValueError(value_error) => {
write!(f, "Value error: {}", value_error)
}
BuiltInFunctionError::ExpectedInteger => write!(f, "Expected an integer"),
BuiltInFunctionError::ExpectedString => write!(f, "Expected a string"),
BuiltInFunctionError::ExpectedList => write!(f, "Expected a list"),
BuiltInFunctionError::WrongNumberOfValueArguments => {
write!(f, "Wrong number of value arguments")
}
}
}
}

186
dust-lang/src/bytecode.rs Normal file
View File

@ -0,0 +1,186 @@
use serde::{Deserialize, Serialize};
use crate::{Span, Value, ValueError};
const STACK_SIZE: usize = 256;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Vm {
chunk: Chunk,
ip: usize,
stack: Vec<Value>,
}
impl Vm {
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
ip: 0,
stack: Vec::with_capacity(STACK_SIZE),
}
}
pub fn interpret(&mut self) -> Result<Option<Value>, VmError> {
loop {
let instruction = self.read_instruction();
match instruction {
Instruction::Constant(index) => {
let value = self.read_constant(*index);
self.stack.push(value.clone());
}
Instruction::Negate => {
let negated = self.pop()?.negate()?;
self.stack.push(negated);
}
Instruction::Return => {
let value = self.pop()?;
return Ok(Some(value));
}
}
self.ip += 1;
}
}
pub fn push(&mut self, value: Value) -> Result<(), VmError> {
if self.stack.len() == STACK_SIZE {
Err(VmError::StackOverflow)
} else {
self.stack.push(value);
Ok(())
}
}
pub fn pop(&mut self) -> Result<Value, VmError> {
if let Some(value) = self.stack.pop() {
Ok(value)
} else {
Err(VmError::StackUnderflow)
}
}
pub fn read_instruction(&self) -> &Instruction {
let (instruction, _) = &self.chunk.code[self.ip];
instruction
}
pub fn read_constant(&self, index: usize) -> Value {
self.chunk.constants[index].clone()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VmError {
StackUnderflow,
StackOverflow,
Value(ValueError),
}
impl From<ValueError> for VmError {
fn from(error: ValueError) -> Self {
Self::Value(error)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum Instruction {
Constant(usize),
Negate,
Return,
}
impl Instruction {
pub fn disassemble(&self, chunk: &Chunk, offset: usize) -> String {
match self {
Instruction::Constant(index) => {
let value = &chunk.constants[*index];
format!("{:04} CONSTANT {} {}", offset, index, value)
}
Instruction::Negate => format!("{:04} NEGATE", offset),
Instruction::Return => format!("{:04} RETURN", offset),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Chunk {
code: Vec<(Instruction, Span)>,
constants: Vec<Value>,
}
impl Chunk {
pub fn new() -> Self {
Self {
code: Vec::new(),
constants: Vec::new(),
}
}
pub fn len(&self) -> usize {
self.code.len()
}
pub fn is_empty(&self) -> bool {
self.code.is_empty()
}
pub fn capacity(&self) -> usize {
self.code.capacity()
}
pub fn write(&mut self, instruction: Instruction, position: Span) {
self.code.push((instruction, position));
}
pub fn push_constant(&mut self, value: Value) -> usize {
self.constants.push(value);
self.constants.len() - 1
}
pub fn clear(&mut self) {
self.code.clear();
self.constants.clear();
}
pub fn disassemble(&self, name: &str) {
println!("== {} ==", name);
for (offset, (instruction, position)) in self.code.iter().enumerate() {
println!("{} {}", position, instruction.disassemble(self, offset));
}
}
}
impl Default for Chunk {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn negation() {
let mut chunk = Chunk::new();
let constant = chunk.push_constant(Value::integer(42));
chunk.write(Instruction::Constant(constant), Span(0, 1));
chunk.write(Instruction::Negate, Span(4, 5));
chunk.write(Instruction::Return, Span(2, 3));
let mut vm = Vm::new(chunk);
let result = vm.interpret();
assert_eq!(result, Ok(Some(Value::integer(-42))));
}
}

View File

@ -1,651 +0,0 @@
//! Garbage-collecting context for variables.
use std::{
collections::HashMap,
fmt::{self, Display, Formatter},
sync::{
atomic::{AtomicUsize, Ordering},
Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
},
};
use crate::{Constructor, Identifier, StructType, Type, Value};
pub type Associations = HashMap<Identifier, (ContextData, usize)>;
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
fn next_id() -> usize {
ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
/// Garbage-collecting context for variables.
#[derive(Debug, Clone)]
pub struct Context {
inner: Arc<ContextInner>,
}
impl Context {
pub fn new() -> Self {
Self::with_data(HashMap::new())
}
pub fn with_data(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: false,
id: next_id(),
}),
}
}
pub fn with_data_immutable(data: Associations) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(data),
parent: None,
is_immutable: true,
id: next_id(),
}),
}
}
/// Creates a deep copy of another context.
pub fn with_data_from(other: &Self) -> Result<Self, ContextError> {
let mut associations = HashMap::new();
for (identifier, (context_data, position)) in other.inner.associations.read()?.iter() {
associations.insert(identifier.clone(), (context_data.clone(), *position));
}
Ok(Self::with_data(associations))
}
pub fn create_child(&self) -> Self {
Self {
inner: Arc::new(ContextInner {
associations: RwLock::new(HashMap::new()),
parent: Some(Arc::downgrade(&self.inner)),
is_immutable: false,
id: next_id(),
}),
}
}
pub fn id(&self) -> usize {
self.inner.id
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
self.inner.association_count()
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
self.inner.contains(identifier)
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
self.inner.get(identifier)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
self.inner.get_type(identifier)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
self.inner.get_data(identifier)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
self.inner.get_variable_value(identifier)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
self.inner.get_constructor(identifier)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
self.inner.get_constructor_type(identifier)
}
/// Associates an identifier with a variable type, with a position given for garbage collection.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
self.inner.set_variable_type(identifier, r#type)
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
self.inner.set_variable_value(identifier, value)
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
self.inner.set_constructor(identifier, constructor)
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
self.inner.set_constructor_type(identifier, struct_type)
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
self.inner.collect_garbage(position)
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
self.inner.set_position(identifier, position)
}
/// Recovers the context from a poisoned state by recovering data from an error.
///
/// This method is not used.
pub fn _recover_from_poison(&mut self, recovered: &RwLockReadGuard<Associations>) {
log::debug!("Context is recovering from poison error");
let mut new_associations = HashMap::new();
for (identifier, (context_data, position)) in recovered.iter() {
new_associations.insert(identifier.clone(), (context_data.clone(), *position));
}
self.inner = Arc::new(ContextInner {
associations: RwLock::new(new_associations),
parent: None,
is_immutable: false,
id: next_id(),
});
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct ContextInner {
id: usize,
associations: RwLock<Associations>,
parent: Option<Weak<ContextInner>>,
is_immutable: bool,
}
impl ContextInner {
fn parent(&self) -> Option<Arc<ContextInner>> {
self.parent.as_ref().and_then(|parent| parent.upgrade())
}
/// Returns the number of associated identifiers in the context.
pub fn association_count(&self) -> Result<usize, ContextError> {
Ok(self.associations.read()?.len())
}
/// Returns a boolean indicating whether the identifier is in the context.
pub fn contains(&self, identifier: &Identifier) -> Result<bool, ContextError> {
if self.associations.read()?.contains_key(identifier) {
Ok(true)
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
parent.contains(identifier)
} else {
Ok(false)
}
} else {
Ok(false)
}
}
/// Returns the full ContextData and Span if the context contains the given identifier.
pub fn get(
&self,
identifier: &Identifier,
) -> Result<Option<(ContextData, usize)>, ContextError> {
if let Some((variable_data, position)) = self.associations.read()?.get(identifier) {
return Ok(Some((variable_data.clone(), *position)));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get(identifier);
}
}
Ok(None)
}
/// Returns the type associated with the given identifier.
pub fn get_type(&self, identifier: &Identifier) -> Result<Option<Type>, ContextError> {
match self.associations.read()?.get(identifier) {
Some((ContextData::VariableType(r#type), _)) => return Ok(Some(r#type.clone())),
Some((ContextData::VariableValue(value), _)) => return Ok(Some(value.r#type())),
Some((ContextData::ConstructorType(struct_type), _)) => {
return Ok(Some(Type::Struct(struct_type.clone())))
}
_ => {}
}
if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_type(identifier);
}
}
Ok(None)
}
/// Returns the ContextData associated with the identifier.
pub fn get_data(&self, identifier: &Identifier) -> Result<Option<ContextData>, ContextError> {
if let Some((variable_data, _)) = self.associations.read()?.get(identifier) {
return Ok(Some(variable_data.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_data(identifier);
}
}
Ok(None)
}
/// Returns the value associated with the identifier.
pub fn get_variable_value(
&self,
identifier: &Identifier,
) -> Result<Option<Value>, ContextError> {
if let Some((ContextData::VariableValue(value), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(value.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_variable_value(identifier);
}
}
Ok(None)
}
/// Returns the constructor associated with the identifier.
pub fn get_constructor(
&self,
identifier: &Identifier,
) -> Result<Option<Constructor>, ContextError> {
if let Some((ContextData::Constructor(constructor), _)) =
self.associations.read()?.get(identifier)
{
return Ok(Some(constructor.clone()));
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor(identifier);
}
}
Ok(None)
}
/// Returns the constructor type associated with the identifier.
pub fn get_constructor_type(
&self,
identifier: &Identifier,
) -> Result<Option<StructType>, ContextError> {
let read_associations = self.associations.read()?;
if let Some((context_data, _)) = read_associations.get(identifier) {
return match context_data {
ContextData::Constructor(constructor) => Ok(Some(constructor.struct_type.clone())),
ContextData::ConstructorType(struct_type) => Ok(Some(struct_type.clone())),
_ => Ok(None),
};
} else if let Some(parent) = &self.parent {
if let Some(parent) = parent.upgrade() {
return parent.get_constructor_type(identifier);
}
}
Ok(None)
}
/// Associates an identifier with a variable type.
pub fn set_variable_type(
&self,
identifier: Identifier,
r#type: Type,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Setting {identifier} to type {type} in context {}", self.id);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableType(r#type), last_position),
);
Ok(())
}
/// Associates an identifier with a variable value.
pub fn set_variable_value(
&self,
identifier: Identifier,
value: Value,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to value {value} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::VariableValue(value), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor.
pub fn set_constructor(
&self,
identifier: Identifier,
constructor: Constructor,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor {constructor:?} in context {}",
self.id
);
let mut associations = self.associations.write()?;
let last_position = associations
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
associations.insert(
identifier,
(ContextData::Constructor(constructor), last_position),
);
Ok(())
}
/// Associates an identifier with a constructor type, with a position given for garbage
/// collection.
pub fn set_constructor_type(
&self,
identifier: Identifier,
struct_type: StructType,
) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!(
"Setting {identifier} to constructor of type {struct_type} in context {}",
self.id
);
let mut variables = self.associations.write()?;
let last_position = variables
.get(&identifier)
.map(|(_, last_position)| *last_position)
.unwrap_or_default();
variables.insert(
identifier,
(ContextData::ConstructorType(struct_type), last_position),
);
Ok(())
}
/// Collects garbage up to the given position, removing all variables with lesser positions.
pub fn collect_garbage(&self, position: usize) -> Result<(), ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
log::trace!("Collecting garbage up to {position} in context {}", self.id);
let mut variables = self.associations.write()?;
variables.retain(|identifier, (_, last_used)| {
let should_drop = position >= *last_used;
if should_drop {
log::trace!("Removing {identifier} from context {}", self.id);
}
!should_drop
});
variables.shrink_to_fit();
Ok(())
}
/// Updates an associated identifier's last known position, allowing it to live longer in the
/// program. Returns a boolean indicating whether the identifier was found. If the identifier is
/// not found in the current context, the parent context is searched but parent context's
/// position is not updated.
pub fn set_position(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
let found = self.update_position_if_found(identifier, position)?;
if found {
return Ok(true);
}
let found_in_ancestor = if let Some(parent) = &self.parent() {
if parent.is_immutable {
false
} else {
parent.update_position_if_found(identifier, position)?
}
} else {
false
};
if found_in_ancestor {
return Ok(true);
}
let mut associations = self.associations.write()?;
log::trace!(
"Reserving {identifier} at position {position:?} in context {}",
self.id
);
associations.insert(identifier.clone(), (ContextData::Reserved, position));
Ok(false)
}
fn update_position_if_found(
&self,
identifier: &Identifier,
position: usize,
) -> Result<bool, ContextError> {
if self.is_immutable {
return Err(ContextError::CannotMutateImmutableContext);
}
let mut associations = self.associations.write()?;
if let Some((_, last_position)) = associations.get_mut(identifier) {
log::trace!(
"Updating {identifier}'s last position to {position:?} in context {}",
self.id
);
*last_position = position;
Ok(true)
} else {
Ok(false)
}
}
}
#[derive(Debug, Clone)]
pub enum ContextData {
Constructor(Constructor),
ConstructorType(StructType),
VariableValue(Value),
VariableType(Type),
Reserved,
}
#[derive(Debug, Clone)]
pub enum ContextError {
CannotMutateImmutableContext,
PoisonErrorRecovered(Arc<Associations>),
}
impl From<PoisonError<RwLockWriteGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockWriteGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl From<PoisonError<RwLockReadGuard<'_, Associations>>> for ContextError {
fn from(error: PoisonError<RwLockReadGuard<'_, Associations>>) -> Self {
let associations = error.into_inner().clone();
Self::PoisonErrorRecovered(Arc::new(associations))
}
}
impl PartialEq for ContextError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::CannotMutateImmutableContext, Self::CannotMutateImmutableContext) => true,
(Self::PoisonErrorRecovered(left), Self::PoisonErrorRecovered(right)) => {
Arc::ptr_eq(left, right)
}
_ => false,
}
}
}
impl Display for ContextError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::CannotMutateImmutableContext => write!(f, "Cannot mutate immutable context"),
Self::PoisonErrorRecovered(associations) => {
write!(
f,
"Context poisoned with {} associations recovered",
associations.len()
)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{parse, Vm};
use super::*;
#[test]
fn context_removes_variables() {
let source = "
let x = 5;
let y = 10;
let z = x + y;
z
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(Some(Value::integer(15))));
assert_eq!(context.association_count().unwrap(), 0);
}
#[test]
fn garbage_collector_does_not_break_loops() {
let source = "
let mut z = 0;
while z < 10 {
z += 1;
}
";
let ast = parse(source).unwrap();
let context = ast.context.clone();
assert_eq!(Vm.run(ast), Ok(None));
assert_eq!(context.association_count().unwrap(), 0);
}
}

View File

@ -1,39 +0,0 @@
use std::{collections::HashMap, sync::OnceLock};
use crate::{BuiltInFunction, Context, ContextData, Function, Identifier, Value};
static CORE_LIBRARY: OnceLock<Context> = OnceLock::new();
pub fn core_library<'a>() -> &'a Context {
CORE_LIBRARY.get_or_init(|| {
Context::with_data_immutable(HashMap::from([
(
Identifier::new("to_string"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ToString,
))),
0,
),
),
(
Identifier::new("read_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::ReadLine,
))),
0,
),
),
(
Identifier::new("write_line"),
(
ContextData::VariableValue(Value::function(Function::BuiltIn(
BuiltInFunction::WriteLine,
))),
0,
),
),
]))
})
}

View File

@ -1,193 +0,0 @@
//! Top-level error handling for the Dust language.
use annotate_snippets::{Level, Renderer, Snippet};
use std::fmt::Display;
use crate::{AnalysisError, ContextError, LexError, ParseError, RuntimeError};
/// An error that occurred during the execution of the Dust language and its
/// corresponding source code.
#[derive(Debug, Clone, PartialEq)]
pub enum DustError<'src> {
ContextError(ContextError),
Runtime {
runtime_error: RuntimeError,
source: &'src str,
},
Analysis {
analysis_errors: Vec<AnalysisError>,
source: &'src str,
},
Parse {
parse_error: ParseError,
source: &'src str,
},
Lex {
lex_error: LexError,
source: &'src str,
},
}
impl<'src> From<ContextError> for DustError<'src> {
fn from(error: ContextError) -> Self {
Self::ContextError(error)
}
}
impl<'src> DustError<'src> {
pub fn runtime(runtime_error: RuntimeError, source: &'src str) -> Self {
DustError::Runtime {
runtime_error,
source,
}
}
pub fn analysis<T: Into<Vec<AnalysisError>>>(analysis_errors: T, source: &'src str) -> Self {
DustError::Analysis {
analysis_errors: analysis_errors.into(),
source,
}
}
pub fn parse(parse_error: ParseError, source: &'src str) -> Self {
DustError::Parse {
parse_error,
source,
}
}
pub fn lex(lex_error: LexError, source: &'src str) -> Self {
DustError::Lex { lex_error, source }
}
pub fn title(&self) -> &'static str {
match self {
DustError::ContextError(_) => "Context error",
DustError::Runtime { .. } => "Runtime error",
DustError::Analysis { .. } => "Analysis error",
DustError::Parse { .. } => "Parse error",
DustError::Lex { .. } => "Lex error",
}
}
pub fn source(&self) -> &'src str {
match self {
DustError::ContextError(_) => "",
DustError::Runtime { source, .. } => source,
DustError::Analysis { source, .. } => source,
DustError::Parse { source, .. } => source,
DustError::Lex { source, .. } => source,
}
}
pub fn report(&self) -> String {
let mut report = String::new();
let renderer = Renderer::styled();
match self {
DustError::ContextError(_) => {
let message = Level::Error.title("Context error");
report.push_str(&renderer.render(message).to_string());
}
DustError::Runtime {
runtime_error,
source,
} => {
let error = runtime_error.root_error();
let position = error.position();
let label = error.to_string();
let message = Level::Error
.title("Runtime error")
.snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&label)),
)
.footer(
Level::Error
.title("This error occured during the execution of the Dust program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
DustError::Analysis {
analysis_errors,
source,
} => {
for error in analysis_errors {
let position = error.position();
let label = error.to_string();
let message =
Level::Warning
.title("Analysis error")
.snippet(Snippet::source(source).fold(true).annotation(
Level::Warning.span(position.0..position.1).label(&label),
))
.footer(
Level::Warning
.title("This error was found without running the program."),
);
report.push_str(&renderer.render(message).to_string());
report.push_str("\n\n");
}
}
DustError::Parse {
parse_error,
source,
} => {
if let ParseError::Lex(lex_error) = parse_error {
let lex_error_report = DustError::lex(lex_error.clone(), source).report();
report.push_str(&lex_error_report);
return report;
}
let position = parse_error.position();
let label = parse_error.to_string();
let message = Level::Error.title("Parse error").snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&label)),
);
report.push_str(&renderer.render(message).to_string());
}
DustError::Lex { lex_error, source } => {
let position = lex_error.position();
let label = lex_error.to_string();
let message = Level::Error.title("Lex error").snippet(
Snippet::source(source)
.fold(true)
.annotation(Level::Error.span(position.0..position.1).label(&label)),
);
report.push_str(&renderer.render(message).to_string());
}
}
report
}
}
impl Display for DustError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DustError::ContextError(context_error) => write!(f, "{context_error}"),
DustError::Runtime { runtime_error, .. } => write!(f, "{runtime_error}"),
DustError::Analysis {
analysis_errors, ..
} => {
for error in analysis_errors {
write!(f, "{error} ")?;
}
Ok(())
}
DustError::Parse { parse_error, .. } => write!(f, "{parse_error}"),
DustError::Lex { lex_error, .. } => write!(f, "{lex_error}"),
}
}
}

View File

@ -1,41 +0,0 @@
use crate::{Constructor, RuntimeError, Span, StructType, Type, Value};
#[derive(Debug, Clone, PartialEq)]
pub enum Evaluation {
Break(Option<Value>),
Constructor(Constructor),
Return(Option<Value>),
}
impl Evaluation {
pub fn value(self) -> Option<Value> {
match self {
Evaluation::Return(value_option) => value_option,
_ => None,
}
}
pub fn expect_value(self, position: Span) -> Result<Value, RuntimeError> {
if let Evaluation::Return(Some(value)) = self {
Ok(value)
} else {
Err(RuntimeError::ExpectedValue { position })
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TypeEvaluation {
Break(Option<Type>),
Constructor(StructType),
Return(Option<Type>),
}
impl TypeEvaluation {
pub fn r#type(self) -> Option<Type> {
match self {
TypeEvaluation::Return(type_option) => type_option,
_ => None,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -15,34 +15,27 @@
//!
//! assert_eq!(the_answer, Some(Value::integer(42)));
//! ```
pub mod analyzer;
pub mod ast;
pub mod built_in_function;
pub mod bytecode;
pub mod constructor;
pub mod context;
pub mod core_library;
pub mod dust_error;
pub mod evaluation;
pub mod identifier;
pub mod lexer;
pub mod parser;
pub mod token;
pub mod r#type;
pub mod value;
pub mod vm;
pub use analyzer::{analyze, AnalysisError, Analyzer};
pub use ast::{AbstractSyntaxTree, AstError, Expression, Node, Span, Statement};
pub use built_in_function::{BuiltInFunction, BuiltInFunctionError};
pub use constructor::{ConstructError, Constructor};
pub use context::{Context, ContextData, ContextError};
pub use core_library::core_library;
pub use dust_error::DustError;
pub use evaluation::{Evaluation, TypeEvaluation};
pub use identifier::Identifier;
pub use lexer::{lex, LexError, Lexer};
pub use parser::{parse, ParseError, Parser};
pub use bytecode::*;
pub use constructor::*;
pub use identifier::*;
pub use r#type::*;
pub use token::{Token, TokenKind, TokenOwned};
pub use value::*;
pub use vm::{run, RuntimeError, Vm};
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Span(usize, usize);
impl Display for Span {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "({}, {})", self.0, self.1)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,651 +0,0 @@
//! Token and TokenOwned types.
use std::fmt::{self, Display, Formatter};
use serde::{Deserialize, Serialize};
/// Source code token.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum Token<'src> {
// End of file
Eof,
// Hard-coded values
Boolean(&'src str),
Character(char),
Float(&'src str),
Identifier(&'src str),
Integer(&'src str),
String(&'src str),
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Mut,
Str,
Struct,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessEqual,
Minus,
MinusEqual,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Slash,
Star,
}
impl<'src> Token<'src> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
match self {
Token::Eof => 0,
Token::Boolean(text) => text.len(),
Token::Character(_) => 3,
Token::Float(text) => text.len(),
Token::Identifier(text) => text.len(),
Token::Integer(text) => text.len(),
Token::String(text) => text.len() + 2,
Token::Async => 5,
Token::Bool => 4,
Token::Break => 5,
Token::Else => 4,
Token::FloatKeyword => 5,
Token::If => 2,
Token::Int => 3,
Token::Let => 3,
Token::Loop => 4,
Token::Map => 3,
Token::Mut => 3,
Token::Str => 3,
Token::Struct => 6,
Token::While => 5,
Token::BangEqual => 2,
Token::Bang => 1,
Token::Colon => 1,
Token::Comma => 1,
Token::Dot => 1,
Token::DoubleAmpersand => 2,
Token::DoubleDot => 2,
Token::DoubleEqual => 2,
Token::DoublePipe => 2,
Token::Equal => 1,
Token::Greater => 1,
Token::GreaterEqual => 2,
Token::LeftCurlyBrace => 1,
Token::LeftParenthesis => 1,
Token::LeftSquareBrace => 1,
Token::Less => 1,
Token::LessEqual => 2,
Token::Minus => 1,
Token::MinusEqual => 2,
Token::Percent => 1,
Token::Plus => 1,
Token::PlusEqual => 2,
Token::RightCurlyBrace => 1,
Token::RightParenthesis => 1,
Token::RightSquareBrace => 1,
Token::Semicolon => 1,
Token::Slash => 1,
Token::Star => 1,
}
}
pub fn to_owned(&self) -> TokenOwned {
match self {
Token::Async => TokenOwned::Async,
Token::BangEqual => TokenOwned::BangEqual,
Token::Bang => TokenOwned::Bang,
Token::Bool => TokenOwned::Bool,
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
Token::Break => TokenOwned::Break,
Token::Character(character) => TokenOwned::Character(*character),
Token::Colon => TokenOwned::Colon,
Token::Comma => TokenOwned::Comma,
Token::Dot => TokenOwned::Dot,
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
Token::DoubleDot => TokenOwned::DoubleDot,
Token::DoubleEqual => TokenOwned::DoubleEqual,
Token::DoublePipe => TokenOwned::DoublePipe,
Token::Else => TokenOwned::Else,
Token::Eof => TokenOwned::Eof,
Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::FloatKeyword => TokenOwned::FloatKeyword,
Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
Token::If => TokenOwned::If,
Token::Int => TokenOwned::Int,
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
Token::LeftParenthesis => TokenOwned::LeftParenthesis,
Token::LeftSquareBrace => TokenOwned::LeftSquareBrace,
Token::Let => TokenOwned::Let,
Token::Less => TokenOwned::Less,
Token::LessEqual => TokenOwned::LessOrEqual,
Token::Loop => TokenOwned::Loop,
Token::Map => TokenOwned::Map,
Token::Minus => TokenOwned::Minus,
Token::MinusEqual => TokenOwned::MinusEqual,
Token::Mut => TokenOwned::Mut,
Token::Percent => TokenOwned::Percent,
Token::Plus => TokenOwned::Plus,
Token::PlusEqual => TokenOwned::PlusEqual,
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
Token::RightParenthesis => TokenOwned::RightParenthesis,
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
Token::Semicolon => TokenOwned::Semicolon,
Token::Star => TokenOwned::Star,
Token::Slash => TokenOwned::Slash,
Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::While => TokenOwned::While,
}
}
pub fn kind(&self) -> TokenKind {
match self {
Token::Async => TokenKind::Async,
Token::BangEqual => TokenKind::BangEqual,
Token::Bang => TokenKind::Bang,
Token::Bool => TokenKind::Bool,
Token::Boolean(_) => TokenKind::Boolean,
Token::Break => TokenKind::Break,
Token::Character(_) => TokenKind::Character,
Token::Colon => TokenKind::Colon,
Token::Comma => TokenKind::Comma,
Token::Dot => TokenKind::Dot,
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
Token::DoubleDot => TokenKind::DoubleDot,
Token::DoubleEqual => TokenKind::DoubleEqual,
Token::DoublePipe => TokenKind::DoublePipe,
Token::Else => TokenKind::Else,
Token::Eof => TokenKind::Eof,
Token::Equal => TokenKind::Equal,
Token::Float(_) => TokenKind::Float,
Token::FloatKeyword => TokenKind::FloatKeyword,
Token::Greater => TokenKind::Greater,
Token::GreaterEqual => TokenKind::GreaterOrEqual,
Token::Identifier(_) => TokenKind::Identifier,
Token::If => TokenKind::If,
Token::Int => TokenKind::Int,
Token::Integer(_) => TokenKind::Integer,
Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace,
Token::LeftParenthesis => TokenKind::LeftParenthesis,
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
Token::Let => TokenKind::Let,
Token::Less => TokenKind::Less,
Token::LessEqual => TokenKind::LessOrEqual,
Token::Loop => TokenKind::Loop,
Token::Map => TokenKind::Map,
Token::Minus => TokenKind::Minus,
Token::MinusEqual => TokenKind::MinusEqual,
Token::Mut => TokenKind::Mut,
Token::Percent => TokenKind::Percent,
Token::Plus => TokenKind::Plus,
Token::PlusEqual => TokenKind::PlusEqual,
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
Token::RightParenthesis => TokenKind::RightParenthesis,
Token::RightSquareBrace => TokenKind::RightSquareBrace,
Token::Semicolon => TokenKind::Semicolon,
Token::Star => TokenKind::Star,
Token::Slash => TokenKind::Slash,
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::While => TokenKind::While,
}
}
pub fn is_eof(&self) -> bool {
matches!(self, Token::Eof)
}
pub fn precedence(&self) -> u8 {
match self {
Token::Dot => 9,
Token::LeftParenthesis | Token::LeftSquareBrace => 8,
Token::Star | Token::Slash | Token::Percent => 7,
Token::Minus | Token::Plus => 6,
Token::DoubleEqual
| Token::Less
| Token::LessEqual
| Token::Greater
| Token::GreaterEqual => 5,
Token::DoubleAmpersand => 4,
Token::DoublePipe => 3,
Token::DoubleDot => 2,
Token::Equal | Token::MinusEqual | Token::PlusEqual => 1,
_ => 0,
}
}
pub fn is_left_associative(&self) -> bool {
matches!(
self,
Token::Dot
| Token::DoubleAmpersand
| Token::DoublePipe
| Token::Plus
| Token::Minus
| Token::Star
| Token::Slash
| Token::Percent
)
}
pub fn is_right_associative(&self) -> bool {
matches!(self, Token::Equal | Token::MinusEqual | Token::PlusEqual)
}
pub fn is_prefix(&self) -> bool {
matches!(self, Token::Bang | Token::Minus | Token::Star)
}
pub fn is_postfix(&self) -> bool {
matches!(
self,
Token::Dot | Token::LeftCurlyBrace | Token::LeftParenthesis | Token::LeftSquareBrace
)
}
}
impl<'src> Display for Token<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Token::Async => write!(f, "async"),
Token::BangEqual => write!(f, "!="),
Token::Bang => write!(f, "!"),
Token::Bool => write!(f, "bool"),
Token::Boolean(value) => write!(f, "{}", value),
Token::Break => write!(f, "break"),
Token::Character(value) => write!(f, "'{}'", value),
Token::Colon => write!(f, ":"),
Token::Comma => write!(f, ","),
Token::Dot => write!(f, "."),
Token::DoubleAmpersand => write!(f, "&&"),
Token::DoubleDot => write!(f, ".."),
Token::DoubleEqual => write!(f, "=="),
Token::DoublePipe => write!(f, "||"),
Token::Else => write!(f, "else"),
Token::Eof => write!(f, "EOF"),
Token::Equal => write!(f, "="),
Token::Float(value) => write!(f, "{}", value),
Token::FloatKeyword => write!(f, "float"),
Token::Greater => write!(f, ">"),
Token::GreaterEqual => write!(f, ">="),
Token::Identifier(value) => write!(f, "{}", value),
Token::If => write!(f, "if"),
Token::Int => write!(f, "int"),
Token::Integer(value) => write!(f, "{}", value),
Token::LeftCurlyBrace => write!(f, "{{"),
Token::LeftParenthesis => write!(f, "("),
Token::LeftSquareBrace => write!(f, "["),
Token::Let => write!(f, "let"),
Token::Less => write!(f, "<"),
Token::LessEqual => write!(f, "<="),
Token::Loop => write!(f, "loop"),
Token::Map => write!(f, "map"),
Token::Minus => write!(f, "-"),
Token::MinusEqual => write!(f, "-="),
Token::Mut => write!(f, "mut"),
Token::Percent => write!(f, "%"),
Token::Plus => write!(f, "+"),
Token::PlusEqual => write!(f, "+="),
Token::RightCurlyBrace => write!(f, "}}"),
Token::RightParenthesis => write!(f, ")"),
Token::RightSquareBrace => write!(f, "]"),
Token::Semicolon => write!(f, ";"),
Token::Slash => write!(f, "/"),
Token::Star => write!(f, "*"),
Token::Str => write!(f, "str"),
Token::String(value) => write!(f, "\"{}\"", value),
Token::Struct => write!(f, "struct"),
Token::While => write!(f, "while"),
}
}
}
/// Owned version of `Token`, which owns all the strings.
///
/// This is used for errors.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum TokenOwned {
Eof,
Identifier(String),
// Hard-coded values
Boolean(String),
Character(char),
Float(String),
Integer(String),
String(String),
// Keywords
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Mut,
Str,
While,
// Symbols
Async,
Bang,
BangEqual,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenOwned {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenOwned::Async => Token::Async.fmt(f),
TokenOwned::Bang => Token::Bang.fmt(f),
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
TokenOwned::Bool => Token::Bool.fmt(f),
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
TokenOwned::Break => Token::Break.fmt(f),
TokenOwned::Character(character) => Token::Character(*character).fmt(f),
TokenOwned::Colon => Token::Colon.fmt(f),
TokenOwned::Comma => Token::Comma.fmt(f),
TokenOwned::Dot => Token::Dot.fmt(f),
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenOwned::DoubleDot => Token::DoubleDot.fmt(f),
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
TokenOwned::Else => Token::Else.fmt(f),
TokenOwned::Eof => Token::Eof.fmt(f),
TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => Token::Float(float).fmt(f),
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
TokenOwned::If => Token::If.fmt(f),
TokenOwned::Int => Token::Int.fmt(f),
TokenOwned::Integer(integer) => Token::Integer(integer).fmt(f),
TokenOwned::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenOwned::Let => Token::Let.fmt(f),
TokenOwned::Less => Token::Less.fmt(f),
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
TokenOwned::Loop => Token::Loop.fmt(f),
TokenOwned::Map => Token::Map.fmt(f),
TokenOwned::Minus => Token::Minus.fmt(f),
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
TokenOwned::Mut => Token::Mut.fmt(f),
TokenOwned::Percent => Token::Percent.fmt(f),
TokenOwned::Plus => Token::Plus.fmt(f),
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
TokenOwned::Star => Token::Star.fmt(f),
TokenOwned::Slash => Token::Slash.fmt(f),
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => Token::String(string).fmt(f),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::While => Token::While.fmt(f),
}
}
}
/// Token representation that holds no data.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum TokenKind {
Eof,
Identifier,
// Hard-coded values
Boolean,
Character,
Float,
Integer,
String,
// Keywords
Async,
Bool,
Break,
Else,
FloatKeyword,
If,
Int,
Let,
Loop,
Map,
Str,
While,
// Symbols
BangEqual,
Bang,
Colon,
Comma,
Dot,
DoubleAmpersand,
DoubleDot,
DoubleEqual,
DoublePipe,
Equal,
Greater,
GreaterOrEqual,
LeftCurlyBrace,
LeftParenthesis,
LeftSquareBrace,
Less,
LessOrEqual,
Minus,
MinusEqual,
Mut,
Percent,
Plus,
PlusEqual,
RightCurlyBrace,
RightParenthesis,
RightSquareBrace,
Semicolon,
Star,
Struct,
Slash,
}
impl Display for TokenKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
TokenKind::Async => Token::Async.fmt(f),
TokenKind::Bang => Token::Bang.fmt(f),
TokenKind::BangEqual => Token::BangEqual.fmt(f),
TokenKind::Bool => Token::Bool.fmt(f),
TokenKind::Boolean => write!(f, "boolean value"),
TokenKind::Break => Token::Break.fmt(f),
TokenKind::Character => write!(f, "character value"),
TokenKind::Colon => Token::Colon.fmt(f),
TokenKind::Comma => Token::Comma.fmt(f),
TokenKind::Dot => Token::Dot.fmt(f),
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
TokenKind::Else => Token::Else.fmt(f),
TokenKind::Eof => Token::Eof.fmt(f),
TokenKind::Equal => Token::Equal.fmt(f),
TokenKind::Float => write!(f, "float value"),
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
TokenKind::Greater => Token::Greater.fmt(f),
TokenKind::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenKind::Identifier => write!(f, "identifier"),
TokenKind::If => Token::If.fmt(f),
TokenKind::Int => Token::Int.fmt(f),
TokenKind::Integer => write!(f, "integer value"),
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
TokenKind::Let => Token::Let.fmt(f),
TokenKind::Less => Token::Less.fmt(f),
TokenKind::LessOrEqual => Token::LessEqual.fmt(f),
TokenKind::Loop => Token::Loop.fmt(f),
TokenKind::Map => Token::Map.fmt(f),
TokenKind::Minus => Token::Minus.fmt(f),
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
TokenKind::Mut => Token::Mut.fmt(f),
TokenKind::Percent => Token::Percent.fmt(f),
TokenKind::Plus => Token::Plus.fmt(f),
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
TokenKind::Semicolon => Token::Semicolon.fmt(f),
TokenKind::Star => Token::Star.fmt(f),
TokenKind::Str => Token::Str.fmt(f),
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::While => Token::While.fmt(f),
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub fn all_tokens<'src>() -> [Token<'src>; 47] {
[
Token::Async,
Token::Bang,
Token::BangEqual,
Token::Bool,
Token::Break,
Token::Colon,
Token::Comma,
Token::Dot,
Token::DoubleAmpersand,
Token::DoubleDot,
Token::DoubleEqual,
Token::DoublePipe,
Token::Else,
Token::Eof,
Token::Equal,
Token::FloatKeyword,
Token::Greater,
Token::GreaterEqual,
Token::If,
Token::Int,
Token::LeftCurlyBrace,
Token::LeftParenthesis,
Token::LeftSquareBrace,
Token::Let,
Token::Less,
Token::LessEqual,
Token::Map,
Token::Minus,
Token::MinusEqual,
Token::Mut,
Token::Percent,
Token::Plus,
Token::PlusEqual,
Token::RightCurlyBrace,
Token::RightParenthesis,
Token::RightSquareBrace,
Token::Semicolon,
Token::Star,
Token::Str,
Token::Slash,
Token::Boolean("true"),
Token::Float("0.0"),
Token::Integer("0"),
Token::String("string"),
Token::Identifier("foobar"),
Token::Struct,
Token::While,
]
}
#[test]
fn token_displays() {
for token in all_tokens().iter() {
let display = token.to_string();
assert_eq!(display, token.to_owned().to_string());
if let Token::Boolean(_)
| Token::Float(_)
| Token::Identifier(_)
| Token::Integer(_)
| Token::String(_) = token
{
continue;
} else {
assert_eq!(display, token.kind().to_string());
}
}
}
}

View File

@ -17,7 +17,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{Constructor, BuiltInFunction, Identifier};
use crate::{Constructor, Identifier};
/// Description of a kind of value.
///
@ -258,7 +258,6 @@ impl Type {
pub fn get_field_type(&self, field: &Identifier) -> Option<Type> {
match field.as_str() {
"to_string" => Some(BuiltInFunction::ToString.r#type()),
"length" => match self {
Type::List { .. } => Some(Type::Integer),
Type::ListOf { .. } => Some(Type::Integer),

View File

@ -14,10 +14,7 @@ use serde::{
Deserialize, Deserializer, Serialize, Serializer,
};
use crate::{
AbstractSyntaxTree, BuiltInFunction, BuiltInFunctionError, Context, ContextError, EnumType,
FunctionType, Identifier, RangeableType, RuntimeError, StructType, Type, Vm,
};
use crate::{EnumType, FunctionType, Identifier, RangeableType, StructType, Type};
/// Dust value representation
///
@ -98,10 +95,6 @@ impl Value {
}
}
pub fn function(value: Function) -> Self {
Value::Raw(ValueData::Function(value))
}
pub fn range<T: Into<RangeValue>>(range: T) -> Self {
Value::Raw(ValueData::Range(range.into()))
}
@ -359,9 +352,6 @@ impl Value {
ValueData::Float(float) => Some(Value::boolean(float % 2.0 != 0.0)),
_ => None,
},
"to_string" => Some(Value::function(Function::BuiltIn(
BuiltInFunction::ToString,
))),
"length" => match data {
ValueData::List(values) => Some(Value::integer(values.len() as i64)),
ValueData::String(string) => Some(Value::integer(string.len() as i64)),
@ -934,7 +924,6 @@ pub enum ValueData {
Character(char),
Enum(Enum),
Float(f64),
Function(Function),
Integer(i64),
List(Vec<Value>),
Map(HashMap<Identifier, Value>),
@ -952,15 +941,6 @@ impl ValueData {
ValueData::Character(_) => Type::Character,
ValueData::Enum(Enum { r#type, .. }) => Type::Enum(r#type.clone()),
ValueData::Float(_) => Type::Float,
ValueData::Function(Function::BuiltIn(built_in_function)) => {
Type::Function(FunctionType {
name: Identifier::new(built_in_function.name()),
type_parameters: built_in_function.type_parameters(),
value_parameters: built_in_function.value_parameters(),
return_type: built_in_function.return_type().map(Box::new),
})
}
ValueData::Function(Function::Parsed { r#type, .. }) => Type::Function(r#type.clone()),
ValueData::Integer(_) => Type::Integer,
ValueData::List(values) => {
let item_type = values.first().unwrap().r#type();
@ -1202,7 +1182,6 @@ impl Display for ValueData {
Ok(())
}
ValueData::Function(function) => write!(f, "{function}"),
ValueData::Integer(integer) => write!(f, "{integer}"),
ValueData::Map(pairs) => {
write!(f, "{{ ")?;
@ -1261,7 +1240,6 @@ impl PartialEq for ValueData {
(ValueData::Byte(left), ValueData::Byte(right)) => left == right,
(ValueData::Character(left), ValueData::Character(right)) => left == right,
(ValueData::Float(left), ValueData::Float(right)) => left == right,
(ValueData::Function(left), ValueData::Function(right)) => left == right,
(ValueData::Integer(left), ValueData::Integer(right)) => left == right,
(ValueData::List(left), ValueData::List(right)) => left == right,
(ValueData::Map(left), ValueData::Map(right)) => left == right,
@ -1291,8 +1269,6 @@ impl Ord for ValueData {
(ValueData::Character(_), _) => Ordering::Greater,
(ValueData::Float(left), ValueData::Float(right)) => left.partial_cmp(right).unwrap(),
(ValueData::Float(_), _) => Ordering::Greater,
(ValueData::Function(left), ValueData::Function(right)) => left.cmp(right),
(ValueData::Function(_), _) => Ordering::Greater,
(ValueData::Integer(left), ValueData::Integer(right)) => left.cmp(right),
(ValueData::Integer(_), _) => Ordering::Greater,
(ValueData::List(left), ValueData::List(right)) => left.cmp(right),
@ -1320,7 +1296,6 @@ impl Serialize for ValueData {
ValueData::Character(character) => serializer.serialize_char(*character),
ValueData::Enum(r#emum) => r#emum.serialize(serializer),
ValueData::Float(float) => serializer.serialize_f64(*float),
ValueData::Function(function) => function.serialize(serializer),
ValueData::Integer(integer) => serializer.serialize_i64(*integer),
ValueData::List(list) => list.serialize(serializer),
ValueData::Map(pairs) => {
@ -1385,11 +1360,9 @@ impl<'de> Deserialize<'de> for ValueData {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Function {
BuiltIn(BuiltInFunction),
Parsed {
name: Identifier,
r#type: FunctionType,
body: AbstractSyntaxTree,
},
}
@ -1398,92 +1371,8 @@ impl Function {
self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
context: &Context,
) -> Result<Option<Value>, FunctionCallError> {
match self {
Function::BuiltIn(built_in_function) => built_in_function
.call(_type_arguments, value_arguments)
.map_err(FunctionCallError::BuiltInFunction),
Function::Parsed { r#type, body, .. } => {
let new_context =
Context::with_data_from(context).map_err(FunctionCallError::Context)?;
if let (Some(value_parameters), Some(value_arguments)) =
(&r#type.value_parameters, value_arguments)
{
for ((identifier, _), value) in value_parameters.iter().zip(value_arguments) {
new_context
.set_variable_value(identifier.clone(), value)
.map_err(FunctionCallError::Context)?;
}
}
Vm.run(body)
.map_err(|error| FunctionCallError::Runtime(Box::new(error)))
}
}
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Function::BuiltIn(built_in_function) => write!(f, "{}", built_in_function),
Function::Parsed { name, r#type, body } => {
write!(f, "fn {}", name)?;
if let Some(type_parameters) = &r#type.type_parameters {
write!(f, "<")?;
for (index, type_parameter) in type_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{}", type_parameter)?;
}
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_paramers) = &r#type.value_parameters {
for (index, (identifier, r#type)) in value_paramers.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{identifier}: {type}")?;
}
}
write!(f, ") {{")?;
for statement in &body.statements {
write!(f, "{}", statement)?;
}
write!(f, "}}")
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum FunctionCallError {
BuiltInFunction(BuiltInFunctionError),
Context(ContextError),
Runtime(Box<RuntimeError>),
}
impl Display for FunctionCallError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
FunctionCallError::BuiltInFunction(error) => write!(f, "{}", error),
FunctionCallError::Context(error) => write!(f, "{}", error),
FunctionCallError::Runtime(error) => write!(f, "{}", error),
}
) -> Result<Option<Value>, ()> {
todo!()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
use dust_lang::*;
#[test]
fn block_scope_captures_parent() {
let source = "let x = 42; { x }";
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}
#[test]
fn block_scope_does_not_capture_child() {
env_logger::builder().is_test(true).try_init().unwrap();
let source = "{ let x = 42; } x";
assert_eq!(
run(source),
Err(DustError::analysis(
[AnalysisError::UndefinedVariable {
identifier: Node::new(Identifier::new("x"), (16, 17))
}],
source
))
);
}
#[test]
fn block_scope_does_not_capture_sibling() {
let source = "{ let x = 42; } { x }";
assert_eq!(
run(source),
Err(DustError::analysis(
[AnalysisError::UndefinedVariable {
identifier: Node::new(Identifier::new("x"), (18, 19))
}],
source
))
);
}
#[test]
fn block_scope_does_not_pollute_parent() {
let source = "let x = 42; { let x = \"foo\"; let x = \"bar\"; } x";
assert_eq!(run(source), Ok(Some(Value::integer(42))));
}

View File

@ -1,7 +1,6 @@
use std::fs::read_to_string;
use clap::Parser;
use dust_lang::run;
#[derive(Parser)]
struct Cli {
@ -37,19 +36,9 @@ fn main() {
}
fn parse_and_display_errors(source: &str) {
match dust_lang::parse(source) {
Ok(ast) => println!("{:#?}", ast),
Err(error) => eprintln!("{}", error.report()),
}
todo!()
}
fn run_and_display_errors(source: &str) {
match run(source) {
Ok(return_value) => {
if let Some(value) = return_value {
println!("{}", value);
}
}
Err(error) => eprintln!("{}", error.report()),
}
todo!()
}