1
0

Add a parser test; Pass VM test

This commit is contained in:
Jeff 2024-08-20 07:20:44 -04:00
parent 8b5eb9977c
commit cf9a9837c8
9 changed files with 173 additions and 188 deletions

View File

@ -6,7 +6,7 @@ use std::{
use serde::{Deserialize, Serialize};
use crate::{Context, FunctionType, Identifier, RangeableType, StructType, Type};
use crate::{BuiltInFunction, Context, FunctionType, Identifier, RangeableType, StructType, Type};
use super::{Node, Span, Statement};
@ -338,6 +338,9 @@ impl Expression {
}
}
Expression::Literal(literal_expression) => match literal_expression.inner.as_ref() {
LiteralExpression::BuiltInFunction(built_in_function) => {
built_in_function.return_type()
}
LiteralExpression::Primitive(primitive_value) => match primitive_value {
PrimitiveValueExpression::Boolean(_) => Some(Type::Boolean),
PrimitiveValueExpression::Character(_) => Some(Type::Character),
@ -682,6 +685,7 @@ impl From<char> for LiteralExpression {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LiteralExpression {
BuiltInFunction(BuiltInFunction),
Primitive(PrimitiveValueExpression),
String(String),
}
@ -689,6 +693,9 @@ pub enum LiteralExpression {
impl Display for LiteralExpression {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
LiteralExpression::BuiltInFunction(built_in_function) => {
write!(f, "{built_in_function}")
}
LiteralExpression::Primitive(primitive) => {
write!(f, "{primitive}")
}
@ -708,6 +715,11 @@ impl PartialOrd for LiteralExpression {
impl Ord for LiteralExpression {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(
LiteralExpression::BuiltInFunction(left),
LiteralExpression::BuiltInFunction(right),
) => left.cmp(right),
(LiteralExpression::BuiltInFunction(_), _) => Ordering::Greater,
(LiteralExpression::Primitive(left), LiteralExpression::Primitive(right)) => {
left.cmp(right)
}

View File

@ -13,7 +13,7 @@ use crate::{Identifier, Type, Value};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunction {
// String tools
ToString,
ToString { argument: Box<Value> },
// Integer and float tools
IsEven,
@ -34,26 +34,46 @@ impl BuiltInFunction {
BuiltInFunction::IsOdd => "is_odd",
BuiltInFunction::Length => "length",
BuiltInFunction::ReadLine => "read_line",
BuiltInFunction::ToString => "to_string",
BuiltInFunction::ToString { .. } => "to_string",
BuiltInFunction::WriteLine => "write_line",
}
}
pub fn value_parameters(&self) -> Vec<(Identifier, Type)> {
pub fn type_parameters(&self) -> Option<Vec<Identifier>> {
match self {
BuiltInFunction::ToString => vec![("value".into(), Type::Any)],
BuiltInFunction::IsEven => vec![("value".into(), Type::Number)],
BuiltInFunction::IsOdd => vec![("value".into(), Type::Number)],
BuiltInFunction::Length => {
vec![(
"value".into(),
Type::ListOf {
item_type: Box::new(Type::Any),
},
)]
}
BuiltInFunction::ReadLine => vec![],
BuiltInFunction::WriteLine => vec![("output".into(), Type::Any)],
BuiltInFunction::ToString { .. } => None,
BuiltInFunction::IsEven => None,
BuiltInFunction::IsOdd => None,
BuiltInFunction::Length => 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::Length => Some(vec![(
"value".into(),
Type::ListOf {
item_type: Box::new(Type::Any),
},
)]),
BuiltInFunction::ReadLine => None,
BuiltInFunction::WriteLine => Some(vec![("output".into(), Type::Any)]),
}
}
pub fn return_type(&self) -> Option<Type> {
match self {
BuiltInFunction::ToString { .. } => Some(Type::String),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::Length => Some(Type::Number),
BuiltInFunction::ReadLine => Some(Type::String),
BuiltInFunction::WriteLine => None,
}
}
@ -63,17 +83,7 @@ impl BuiltInFunction {
value_arguments: Option<Vec<Value>>,
) -> Result<Option<Value>, BuiltInFunctionError> {
match self {
BuiltInFunction::ToString => {
if let Some(value_arguments) = value_arguments {
if value_arguments.len() == 1 {
Ok(Some(Value::string(value_arguments[0].to_string())))
} else {
Err(BuiltInFunctionError::WrongNumberOfValueArguments)
}
} else {
Err(BuiltInFunctionError::WrongNumberOfValueArguments)
}
}
BuiltInFunction::ToString { argument } => Ok(Some(Value::string(argument))),
BuiltInFunction::IsEven => {
if let Some(value_arguments) = value_arguments {
if value_arguments.len() == 1 {
@ -145,17 +155,6 @@ impl BuiltInFunction {
}
}
}
pub fn expected_return_type(&self) -> Option<Type> {
match self {
BuiltInFunction::ToString => Some(Type::String),
BuiltInFunction::IsEven => Some(Type::Boolean),
BuiltInFunction::IsOdd => Some(Type::Boolean),
BuiltInFunction::Length => Some(Type::Integer),
BuiltInFunction::ReadLine => Some(Type::String),
BuiltInFunction::WriteLine => None,
}
}
}
impl Display for BuiltInFunction {

View File

@ -1,8 +1,8 @@
//! Top-level error handling for the Dust language.
use annotate_snippets::{Level, Renderer, Snippet};
use annotate_snippets::{Level, Message, Renderer, Snippet};
use std::fmt::Display;
use crate::{AnalysisError, LexError, ParseError, RuntimeError};
use crate::{ast::Span, AnalysisError, LexError, ParseError, RuntimeError};
/// An error that occurred during the execution of the Dust language and its
/// corresponding source code.
@ -61,12 +61,12 @@ impl<'src> DustError<'src> {
}
}
pub fn position(&self) -> (usize, usize) {
pub fn position(&self) -> Option<Span> {
match self {
DustError::Runtime { runtime_error, .. } => runtime_error.position(),
DustError::Analysis { analysis_error, .. } => analysis_error.position(),
DustError::Parse { parse_error, .. } => parse_error.position(),
DustError::Lex { lex_error, .. } => lex_error.position(),
DustError::Analysis { analysis_error, .. } => Some(analysis_error.position()),
DustError::Parse { parse_error, .. } => Some(parse_error.position()),
DustError::Lex { lex_error, .. } => Some(lex_error.position()),
}
}
@ -83,10 +83,18 @@ impl<'src> DustError<'src> {
let title = self.title();
let span = self.position();
let label = self.to_string();
let message = Level::Error.title(title).snippet(
Snippet::source(self.source())
.annotation(Level::Info.span(span.0..span.1).label(&label)),
);
let message = if let Some(span) = span {
Level::Error.title(title).snippet(
Snippet::source(self.source())
.annotation(Level::Info.span(span.0..span.1).label(&label)),
)
} else {
Level::Error
.title(title)
.snippet(Snippet::source(self.source()))
.footer(Level::Info.title("No position information available"))
};
let renderer = Renderer::styled();
format!("{}", renderer.render(message))

View File

@ -437,7 +437,6 @@ impl<'src> Lexer<'src> {
"mut" => Token::Mut,
"read_line" => Token::ReadLine,
"struct" => Token::Struct,
"to_string" => Token::ToString,
"true" => Token::Boolean("true"),
"while" => Token::While,
"write_line" => Token::WriteLine,

View File

@ -822,7 +822,7 @@ impl<'src> Parser<'src> {
Expression::tuple_access(left, index_node, position)
} else {
let field = self.parse_identifier()?;
let position = (left.position().0, self.current_position.1);
let position = (left.position().0, field.position.1);
Expression::field_access(left, field, position)
}
@ -1145,6 +1145,26 @@ mod tests {
use super::*;
#[test]
fn built_in_function() {
let source = "42.to_string()";
assert_eq!(
parse(source),
Ok(AbstractSyntaxTree::with_statements([
Statement::Expression(Expression::call(
Expression::field_access(
Expression::literal(42, (0, 2)),
Node::new(Identifier::new("to_string"), (3, 12)),
(0, 12)
),
vec![],
(0, 14)
))
]))
);
}
#[test]
fn map_expression() {
let source = "map { x = '1', y = 2, z = 3.0 }";

View File

@ -36,7 +36,6 @@ pub enum Token<'src> {
ReadLine,
Str,
Struct,
ToString,
While,
WriteLine,
@ -123,7 +122,6 @@ impl<'src> Token<'src> {
Token::String(text) => TokenOwned::String(text.to_string()),
Token::Str => TokenOwned::Str,
Token::Struct => TokenOwned::Struct,
Token::ToString => TokenOwned::ToString,
Token::While => TokenOwned::While,
Token::WriteLine => TokenOwned::WriteLine,
}
@ -181,7 +179,6 @@ impl<'src> Token<'src> {
Token::Slash => "/",
Token::Str => "str",
Token::Struct => "struct",
Token::ToString => "to_string",
Token::While => "while",
Token::WriteLine => "write_line",
}
@ -238,7 +235,6 @@ impl<'src> Token<'src> {
Token::Str => TokenKind::Str,
Token::String(_) => TokenKind::String,
Token::Struct => TokenKind::Struct,
Token::ToString => TokenKind::ToString,
Token::While => TokenKind::While,
Token::WriteLine => TokenKind::WriteLine,
}
@ -332,7 +328,6 @@ pub enum TokenOwned {
Mut,
ReadLine,
Str,
ToString,
While,
WriteLine,
@ -421,7 +416,6 @@ impl Display for TokenOwned {
TokenOwned::Str => Token::Str.fmt(f),
TokenOwned::String(string) => write!(f, "{string}"),
TokenOwned::Struct => Token::Struct.fmt(f),
TokenOwned::ToString => Token::ToString.fmt(f),
TokenOwned::While => Token::While.fmt(f),
TokenOwned::WriteLine => Token::WriteLine.fmt(f),
}
@ -455,7 +449,6 @@ pub enum TokenKind {
Map,
ReadLine,
Str,
ToString,
While,
WriteLine,
@ -544,7 +537,6 @@ impl Display for TokenKind {
TokenKind::Slash => Token::Slash.fmt(f),
TokenKind::String => write!(f, "string value"),
TokenKind::Struct => Token::Struct.fmt(f),
TokenKind::ToString => Token::ToString.fmt(f),
TokenKind::While => Token::While.fmt(f),
TokenKind::WriteLine => Token::WriteLine.fmt(f),
}
@ -555,7 +547,7 @@ impl Display for TokenKind {
pub(crate) mod tests {
use super::*;
pub fn all_tokens<'src>() -> [Token<'src>; 52] {
pub fn all_tokens<'src>() -> [Token<'src>; 51] {
[
Token::Identifier("foobar"),
Token::Boolean("true"),
@ -606,7 +598,6 @@ pub(crate) mod tests {
Token::Star,
Token::Str,
Token::Struct,
Token::ToString,
Token::While,
Token::WriteLine,
]

View File

@ -371,7 +371,7 @@ impl Ord for Type {
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct FunctionType {
pub name: Identifier,
pub type_parameters: Option<Vec<Type>>,
pub type_parameters: Option<Vec<Identifier>>,
pub value_parameters: Option<Vec<(Identifier, Type)>>,
pub return_type: Option<Box<Type>>,
}

View File

@ -16,8 +16,8 @@ use serde::{
};
use crate::{
AbstractSyntaxTree, Context, EnumType, FunctionType, Identifier, RangeableType, RuntimeError,
StructType, Type, Vm,
AbstractSyntaxTree, BuiltInFunction, Context, EnumType, FunctionType, Identifier,
RangeableType, RuntimeError, StructType, Type, Vm,
};
/// Dust value representation
@ -194,7 +194,15 @@ impl Value {
Value::Character(_) => Type::Character,
Value::Enum(Enum { r#type, .. }) => Type::Enum(r#type.clone()),
Value::Float(_) => Type::Float,
Value::Function(function) => Type::Function(function.r#type.clone()),
Value::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),
}),
Value::Function(Function::Parsed { name, r#type, body }) => {
Type::Function(r#type.clone())
}
Value::Integer(_) => Type::Integer,
Value::List(values) => {
let item_type = values.first().unwrap().r#type();
@ -255,6 +263,14 @@ impl Value {
}
pub fn get_field(&self, field: &Identifier) -> Option<Value> {
if let "to_string" = field.as_str() {
return Some(Value::Function(Function::BuiltIn(
BuiltInFunction::ToString {
argument: Box::new(self.clone()),
},
)));
}
match self {
Value::Mutable(inner) => inner.read().unwrap().get_field(field),
Value::Struct(Struct::Fields { fields, .. }) => fields.get(field).cloned(),
@ -1059,151 +1075,88 @@ impl<'de> Deserialize<'de> for Value {
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Function {
pub name: Identifier,
pub r#type: FunctionType,
pub body: Arc<AbstractSyntaxTree>,
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Function {
BuiltIn(BuiltInFunction),
Parsed {
name: Identifier,
r#type: FunctionType,
body: AbstractSyntaxTree,
},
}
impl Function {
pub fn call(
&self,
self,
_type_arguments: Option<Vec<Type>>,
value_arguments: Option<Vec<Value>>,
context: &Context,
) -> Result<Option<Value>, RuntimeError> {
let new_context = Context::with_data_from(context);
match self {
Function::BuiltIn(built_in_function) => built_in_function
.call(_type_arguments, value_arguments)
.map_err(|error| RuntimeError::BuiltInFunctionError { error }),
Function::Parsed { r#type, body, .. } => {
let new_context = Context::with_data_from(context);
if let (Some(value_parameters), Some(value_arguments)) =
(&self.r#type.value_parameters, value_arguments)
{
for ((identifier, _), value) in value_parameters.iter().zip(value_arguments) {
new_context.set_variable_value(identifier.clone(), value);
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);
}
}
let mut vm = Vm::new(body, new_context);
vm.run()
}
}
let mut vm = Vm::new(self.body.as_ref().clone(), new_context);
vm.run()
}
}
impl Display for Function {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "fn {}", self.name)?;
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) = &self.r#type.type_parameters {
write!(f, "<")?;
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, ", ")?;
for (index, type_parameter) in type_parameters.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
write!(f, "{}", type_parameter)?;
}
write!(f, ">")?;
}
write!(f, "{}", type_parameter)?;
}
write!(f, "(")?;
write!(f, ">")?;
}
write!(f, "(")?;
if let Some(value_paramers) = &self.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 &self.body.statements {
write!(f, "{}", statement)?;
}
write!(f, "}}")
}
}
impl Serialize for Function {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut ser = serializer.serialize_struct("Function", 3)?;
ser.serialize_field("name", &self.name)?;
ser.serialize_field("type", &self.r#type)?;
ser.serialize_field("body", self.body.as_ref())?;
ser.end()
}
}
impl<'de> Deserialize<'de> for Function {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FunctionVisitor;
impl<'de> Visitor<'de> for FunctionVisitor {
type Value = Function;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a function")
}
fn visit_map<A>(self, mut map: A) -> Result<Function, A::Error>
where
A: MapAccess<'de>,
{
let mut name = None;
let mut r#type = None;
let mut body = None;
while let Some(key) = map.next_key()? {
match key {
"name" => {
if name.is_some() {
return Err(de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
if let Some(value_paramers) = &r#type.value_parameters {
for (index, (identifier, r#type)) in value_paramers.iter().enumerate() {
if index > 0 {
write!(f, ", ")?;
}
"type" => {
if r#type.is_some() {
return Err(de::Error::duplicate_field("type"));
}
r#type = Some(map.next_value()?);
}
"body" => {
if body.is_some() {
return Err(de::Error::duplicate_field("body"));
}
body = Some(map.next_value().map(Arc::new)?);
}
_ => {
return Err(de::Error::unknown_field(key, &["name", "type", "body"]));
}
write!(f, "{identifier}: {type}")?;
}
}
let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
let r#type = r#type.ok_or_else(|| de::Error::missing_field("type"))?;
let body = body.ok_or_else(|| de::Error::missing_field("body"))?;
write!(f, ") {{")?;
Ok(Function { name, r#type, body })
for statement in &body.statements {
write!(f, "{}", statement)?;
}
write!(f, "}}")
}
}
deserializer.deserialize_struct("Function", &["name", "type", "body"], FunctionVisitor)
}
}

View File

@ -20,9 +20,8 @@ use crate::{
OperatorExpression, PrimitiveValueExpression, RangeExpression, Span, Statement,
StructDefinition, StructExpression,
},
constructor::FieldsConstructor,
parse, Analyzer, BuiltInFunctionError, Constructor, Context, ContextData, DustError,
Expression, Identifier, ParseError, Struct, StructType, Type, Value, ValueError,
Expression, Function, Identifier, ParseError, StructType, Type, Value, ValueError,
};
/// Run the source code and return the result.
@ -609,6 +608,9 @@ impl Vm {
fn run_literal(&self, literal: LiteralExpression) -> Result<Evaluation, RuntimeError> {
let value = match literal {
LiteralExpression::BuiltInFunction(built_in_function) => {
Value::Function(Function::BuiltIn(built_in_function))
}
LiteralExpression::String(string) => Value::String(string),
LiteralExpression::Primitive(primitive_expression) => match primitive_expression {
PrimitiveValueExpression::Boolean(boolean) => Value::Boolean(boolean),
@ -934,7 +936,6 @@ pub enum RuntimeError {
// These should be prevented by running the analyzer before the VM
BuiltInFunctionError {
error: BuiltInFunctionError,
position: Span,
},
EnumVariantNotFound {
identifier: Identifier,
@ -1006,8 +1007,9 @@ pub enum RuntimeError {
}
impl RuntimeError {
pub fn position(&self) -> Span {
match self {
pub fn position(&self) -> Option<Span> {
let position = match self {
Self::BuiltInFunctionError { .. } => return None,
Self::ParseError(parse_error) => parse_error.position(),
Self::Expression { position, .. } => *position,
Self::Statement { position, .. } => *position,
@ -1016,7 +1018,6 @@ impl RuntimeError {
right_position,
..
} => (left_position.0, right_position.1),
Self::BuiltInFunctionError { position, .. } => *position,
Self::EnumVariantNotFound { position, .. } => *position,
Self::ExpectedBoolean { position } => *position,
Self::ExpectedConstructor { position, .. } => *position,
@ -1042,7 +1043,9 @@ impl RuntimeError {
Self::UndefinedProperty {
property_position, ..
} => *property_position,
}
};
Some(position)
}
}