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_node = node.child_by_field_name("statement").unwrap();
|
||||||
let statement = Statement::from_syntax_node(source, statement_node, context)?;
|
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 {
|
Ok(Assignment {
|
||||||
identifier,
|
identifier,
|
||||||
type_definition,
|
type_definition,
|
||||||
@ -77,6 +71,16 @@ impl AbstractTree for Assignment {
|
|||||||
let new_value = match self.operator {
|
let new_value = match self.operator {
|
||||||
AssignmentOperator::PlusEqual => {
|
AssignmentOperator::PlusEqual => {
|
||||||
if let Some(mut previous_value) = context.variables()?.get(key).cloned() {
|
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 += value;
|
||||||
previous_value
|
previous_value
|
||||||
} else {
|
} else {
|
||||||
@ -91,15 +95,17 @@ impl AbstractTree for Assignment {
|
|||||||
return Err(Error::VariableIdentifierNotFound(key.clone()));
|
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);
|
context.variables_mut()?.insert(key.clone(), new_value);
|
||||||
|
|
||||||
Ok(Value::Empty)
|
Ok(Value::Empty)
|
||||||
|
@ -59,7 +59,7 @@ impl AbstractTree for FunctionCall {
|
|||||||
arguments.push(value);
|
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 }
|
Self { r#type }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn r#type(&self) -> &Type {
|
pub fn inner(&self) -> &Type {
|
||||||
&self.r#type
|
&self.r#type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn take_inner(self) -> Type {
|
||||||
|
self.r#type
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check(&self, other: &TypeDefinition, context: &Map) -> Result<()> {
|
pub fn check(&self, other: &TypeDefinition, context: &Map) -> Result<()> {
|
||||||
match (&self.r#type, &other.r#type) {
|
match (&self.r#type, &other.r#type) {
|
||||||
(Type::Any, _)
|
(Type::Any, _)
|
||||||
|
@ -203,9 +203,9 @@ impl AbstractTree for ValueNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(previous) = previous_type {
|
if let Some(previous) = previous_type {
|
||||||
previous
|
TypeDefinition::new(Type::List(Box::new(previous.take_inner())))
|
||||||
} else {
|
} else {
|
||||||
TypeDefinition::new(Type::Any)
|
TypeDefinition::new(Type::List(Box::new(Type::Any)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ValueNode::Empty => TypeDefinition::new(Type::Any),
|
ValueNode::Empty => TypeDefinition::new(Type::Any),
|
||||||
|
@ -13,7 +13,7 @@ impl BuiltInFunction for Read {
|
|||||||
"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_string = arguments.first().unwrap_or_default().as_string()?;
|
||||||
let path = PathBuf::from(path_string);
|
let path = PathBuf::from(path_string);
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ impl BuiltInFunction for Write {
|
|||||||
"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 file_content = arguments.first().unwrap_or_default().as_string()?;
|
||||||
let path = arguments.get(1).unwrap_or(&Value::Empty).as_string()?;
|
let path = arguments.get(1).unwrap_or(&Value::Empty).as_string()?;
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ impl BuiltInFunction for Append {
|
|||||||
"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 file_content = arguments.first().unwrap_or(&Value::Empty).as_string()?;
|
||||||
let path = arguments.get(1).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 fs;
|
||||||
mod output;
|
mod output;
|
||||||
|
mod r#type;
|
||||||
|
|
||||||
pub const BUILT_IN_FUNCTIONS: [&dyn BuiltInFunction; 4] =
|
pub const BUILT_IN_FUNCTIONS: [&dyn BuiltInFunction; 5] = [
|
||||||
[&output::Output, &fs::Read, &fs::Write, &fs::Append];
|
&fs::Read,
|
||||||
|
&fs::Write,
|
||||||
|
&fs::Append,
|
||||||
|
&output::Output,
|
||||||
|
&r#type::Type,
|
||||||
|
];
|
||||||
|
|
||||||
pub trait BuiltInFunction {
|
pub trait BuiltInFunction {
|
||||||
fn name(&self) -> &'static str;
|
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;
|
pub struct Output;
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ impl BuiltInFunction for Output {
|
|||||||
"output"
|
"output"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, arguments: &[Value]) -> Result<Value> {
|
fn run(&self, arguments: &[Value], _context: &Map) -> Result<Value> {
|
||||||
for argument in arguments {
|
for argument in arguments {
|
||||||
println!("{argument}");
|
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,
|
tool_name: &'static str,
|
||||||
expected: usize,
|
expected: usize,
|
||||||
actual: usize,
|
actual: usize,
|
||||||
@ -374,7 +374,7 @@ impl fmt::Display for Error {
|
|||||||
),
|
),
|
||||||
RuntimeTypeCheck { expected, actual } => write!(
|
RuntimeTypeCheck { expected, actual } => write!(
|
||||||
f,
|
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 let Some(previous) = &previous_type {
|
||||||
if &value_type != previous {
|
if &value_type != previous {
|
||||||
break;
|
return Ok(TypeDefinition::new(Type::List(Box::new(Type::Any))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previous_type = Some(value_type);
|
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::Map(_) => Type::Map,
|
||||||
Value::Table(_) => Type::Table,
|
Value::Table(_) => Type::Table,
|
||||||
|
Loading…
Reference in New Issue
Block a user