Implement runtime type checking
This commit is contained in:
parent
8826d08392
commit
3dc78a7066
@ -56,12 +56,6 @@ impl AbstractTree for Assignment {
|
||||
let statement_node = node.child_by_field_name("statement").unwrap();
|
||||
let statement = Statement::from_syntax_node(source, statement_node, context)?;
|
||||
|
||||
if let Some(type_defintion) = &type_definition {
|
||||
let statement_type = statement.expected_type(context)?;
|
||||
|
||||
type_defintion.check(&statement_type, context)?;
|
||||
}
|
||||
|
||||
Ok(Assignment {
|
||||
identifier,
|
||||
type_definition,
|
||||
@ -77,6 +71,16 @@ impl AbstractTree for Assignment {
|
||||
let new_value = match self.operator {
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Some(mut previous_value) = context.variables()?.get(key).cloned() {
|
||||
if let Ok(list) = previous_value.as_list() {
|
||||
let first_item_type = if let Some(first) = list.items().first() {
|
||||
first.r#type(context)?
|
||||
} else {
|
||||
TypeDefinition::new(Type::Any)
|
||||
};
|
||||
|
||||
first_item_type.check(&value.r#type(context)?, context)?;
|
||||
}
|
||||
|
||||
previous_value += value;
|
||||
previous_value
|
||||
} else {
|
||||
@ -91,15 +95,17 @@ impl AbstractTree for Assignment {
|
||||
return Err(Error::VariableIdentifierNotFound(key.clone()));
|
||||
}
|
||||
}
|
||||
AssignmentOperator::Equal => value,
|
||||
AssignmentOperator::Equal => {
|
||||
if let Some(type_definition) = &self.type_definition {
|
||||
let new_value_type = value.r#type(context)?;
|
||||
|
||||
type_definition.check(&new_value_type, context)?;
|
||||
}
|
||||
|
||||
value
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(type_definition) = &self.type_definition {
|
||||
let new_value_type = new_value.r#type(context)?;
|
||||
|
||||
type_definition.check(&new_value_type, context)?;
|
||||
}
|
||||
|
||||
context.variables_mut()?.insert(key.clone(), new_value);
|
||||
|
||||
Ok(Value::Empty)
|
||||
|
@ -59,7 +59,7 @@ impl AbstractTree for FunctionCall {
|
||||
arguments.push(value);
|
||||
}
|
||||
|
||||
return built_in_function.run(&arguments);
|
||||
return built_in_function.run(&arguments, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,14 @@ impl TypeDefinition {
|
||||
Self { r#type }
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> &Type {
|
||||
pub fn inner(&self) -> &Type {
|
||||
&self.r#type
|
||||
}
|
||||
|
||||
pub fn take_inner(self) -> Type {
|
||||
self.r#type
|
||||
}
|
||||
|
||||
pub fn check(&self, other: &TypeDefinition, context: &Map) -> Result<()> {
|
||||
match (&self.r#type, &other.r#type) {
|
||||
(Type::Any, _)
|
||||
|
@ -203,9 +203,9 @@ impl AbstractTree for ValueNode {
|
||||
}
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
previous
|
||||
TypeDefinition::new(Type::List(Box::new(previous.take_inner())))
|
||||
} else {
|
||||
TypeDefinition::new(Type::Any)
|
||||
TypeDefinition::new(Type::List(Box::new(Type::Any)))
|
||||
}
|
||||
}
|
||||
ValueNode::Empty => TypeDefinition::new(Type::Any),
|
||||
|
@ -13,7 +13,7 @@ impl BuiltInFunction for Read {
|
||||
"read"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value]) -> Result<Value> {
|
||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||
let path_string = arguments.first().unwrap_or_default().as_string()?;
|
||||
let path = PathBuf::from(path_string);
|
||||
|
||||
@ -55,7 +55,7 @@ impl BuiltInFunction for Write {
|
||||
"write"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value]) -> Result<Value> {
|
||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||
let file_content = arguments.first().unwrap_or_default().as_string()?;
|
||||
let path = arguments.get(1).unwrap_or(&Value::Empty).as_string()?;
|
||||
|
||||
@ -72,7 +72,7 @@ impl BuiltInFunction for Append {
|
||||
"append"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value]) -> Result<Value> {
|
||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||
let file_content = arguments.first().unwrap_or(&Value::Empty).as_string()?;
|
||||
let path = arguments.get(1).unwrap_or(&Value::Empty).as_string()?;
|
||||
|
||||
|
@ -1,12 +1,18 @@
|
||||
use crate::{Result, Value};
|
||||
use crate::{Map, Result, Value};
|
||||
|
||||
mod fs;
|
||||
mod output;
|
||||
mod r#type;
|
||||
|
||||
pub const BUILT_IN_FUNCTIONS: [&dyn BuiltInFunction; 4] =
|
||||
[&output::Output, &fs::Read, &fs::Write, &fs::Append];
|
||||
pub const BUILT_IN_FUNCTIONS: [&dyn BuiltInFunction; 5] = [
|
||||
&fs::Read,
|
||||
&fs::Write,
|
||||
&fs::Append,
|
||||
&output::Output,
|
||||
&r#type::Type,
|
||||
];
|
||||
|
||||
pub trait BuiltInFunction {
|
||||
fn name(&self) -> &'static str;
|
||||
fn run(&self, arguments: &[Value]) -> Result<Value>;
|
||||
fn run(&self, arguments: &[Value], context: &Map) -> Result<Value>;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{BuiltInFunction, Result, Value};
|
||||
use crate::{BuiltInFunction, Map, Result, Value};
|
||||
|
||||
pub struct Output;
|
||||
|
||||
@ -7,7 +7,7 @@ impl BuiltInFunction for Output {
|
||||
"output"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value]) -> Result<Value> {
|
||||
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||
for argument in arguments {
|
||||
println!("{argument}");
|
||||
}
|
||||
|
34
src/built_in_functions/type.rs
Normal file
34
src/built_in_functions/type.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::{BuiltInFunction, Error, List, Map, Result, Value};
|
||||
|
||||
pub struct Type;
|
||||
|
||||
impl BuiltInFunction for Type {
|
||||
fn name(&self) -> &'static str {
|
||||
"type"
|
||||
}
|
||||
|
||||
fn run(&self, arguments: &[Value], context: &Map) -> Result<Value> {
|
||||
Error::expect_function_argument_amount("type", 1, arguments.len())?;
|
||||
|
||||
if arguments.len() == 1 {
|
||||
let type_definition = arguments.first().unwrap().r#type(context)?;
|
||||
let type_text = type_definition.to_string();
|
||||
let text_without_brackets = &type_text[1..type_text.len() - 1];
|
||||
|
||||
Ok(Value::String(text_without_brackets.to_string()))
|
||||
} else {
|
||||
let mut answers = Vec::new();
|
||||
|
||||
for value in arguments {
|
||||
let type_definition = value.r#type(context)?;
|
||||
let type_text = type_definition.to_string();
|
||||
let text_without_brackets = &type_text[1..type_text.len() - 1];
|
||||
let text_as_value = Value::String(text_without_brackets.to_string());
|
||||
|
||||
answers.push(text_as_value);
|
||||
}
|
||||
|
||||
Ok(Value::List(List::with_items(answers)))
|
||||
}
|
||||
}
|
||||
}
|
@ -166,7 +166,7 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_tool_argument_amount(
|
||||
pub fn expect_function_argument_amount(
|
||||
tool_name: &'static str,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
@ -374,7 +374,7 @@ impl fmt::Display for Error {
|
||||
),
|
||||
RuntimeTypeCheck { expected, actual } => write!(
|
||||
f,
|
||||
"Type check error. Expected type {expected} but got value {actual}."
|
||||
"Type check error. Expected type {expected} but got value with type {actual}."
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -53,14 +53,18 @@ impl Value {
|
||||
|
||||
if let Some(previous) = &previous_type {
|
||||
if &value_type != previous {
|
||||
break;
|
||||
return Ok(TypeDefinition::new(Type::List(Box::new(Type::Any))));
|
||||
}
|
||||
}
|
||||
|
||||
previous_type = Some(value_type);
|
||||
}
|
||||
|
||||
Type::List(Box::new(Type::Any))
|
||||
if let Some(previous) = previous_type {
|
||||
Type::List(Box::new(previous.take_inner()))
|
||||
} else {
|
||||
Type::List(Box::new(Type::Any))
|
||||
}
|
||||
}
|
||||
Value::Map(_) => Type::Map,
|
||||
Value::Table(_) => Type::Table,
|
||||
|
Loading…
Reference in New Issue
Block a user