From a908ececfc4abc19e13574e42259d18e25a1346e Mon Sep 17 00:00:00 2001 From: BadCatSet Date: Thu, 13 Jun 2024 14:15:35 +0300 Subject: [PATCH] refactor: switch to stable toolchain feat: implement scope manipulation features refactor: make import an expression test: add tests for scope manipulation --- obsidian/FIXME.canvas | 13 +- obsidian/TODO.canvas | 27 +- rust-toolchain.toml | 2 - src/interpreter/builtins/operators.rs | 67 +-- src/interpreter/expression.rs | 94 ++-- src/interpreter/identifier.rs | 7 +- src/interpreter/runner.rs | 5 +- src/interpreter/scope.rs | 30 +- src/interpreter/statement.rs | 62 ++- src/interpreter/tree_sitter_parser.rs | 405 ++++++++---------- src/interpreter/value/fru_object.rs | 10 +- src/interpreter/value/fru_value.rs | 44 +- src/interpreter/value/native/object.rs | 11 +- src/main.rs | 2 - src/stdlib/scope/fru_scope.rs | 19 +- tests/expression/block_expression_tests.rs | 2 +- .../expression/curry_call_expression_tests.rs | 8 +- tests/expression/function_expression_tests.rs | 22 +- .../instantiation_expression_tests.rs | 4 +- tests/expression/variable_expression_tests.rs | 2 +- tests/lib.rs | 6 +- .../literal_expression/literal_bool_tests.rs | 2 +- tests/oop/class_tests.rs | 10 +- tests/oop/data_tests.rs | 4 +- tests/oop/general_tests.rs | 16 +- tests/oop/property_tests.rs | 90 ++-- tests/oop/static_tests.rs | 20 +- tests/oop/struct_tests.rs | 10 +- tests/scope_manipulation/basics.rs | 79 ++++ tests/scope_manipulation/mod.rs | 1 + 30 files changed, 593 insertions(+), 481 deletions(-) delete mode 100644 rust-toolchain.toml create mode 100644 tests/scope_manipulation/basics.rs create mode 100644 tests/scope_manipulation/mod.rs diff --git a/obsidian/FIXME.canvas b/obsidian/FIXME.canvas index 9af50a6..5a85a2e 100644 --- a/obsidian/FIXME.canvas +++ b/obsidian/FIXME.canvas @@ -1,13 +1,12 @@ { "nodes":[ - {"id":"813b32f2728a4a94","x":-800,"y":-440,"width":640,"height":820,"type":"group","label":"ordinary"}, - {"id":"e6b12e213f195739","x":-1420,"y":-440,"width":520,"height":500,"type":"group","label":"critical"}, - {"id":"02fe9a5b84622932","x":-1400,"y":-414,"width":250,"height":79,"type":"text","text":"`fru_clone()` horror"}, + {"id":"813b32f2728a4a94","type":"group","x":-800,"y":-440,"width":640,"height":820,"label":"ordinary"}, + {"id":"e6b12e213f195739","type":"group","x":-1420,"y":-440,"width":520,"height":500,"label":"critical"}, + {"id":"02fe9a5b84622932","type":"text","text":"`fru_clone()` horror","x":-1400,"y":-414,"width":250,"height":79}, {"id":"cd9a0d2eaf046fee","type":"text","text":"implement Display for FruValue and fix for FruObject","x":-740,"y":-409,"width":250,"height":110}, - {"id":"2dc5ff956d956862","type":"text","text":"introduce a new error type for \"method/field not found\"","x":-740,"y":-275,"width":250,"height":130}, - {"id":"b18a8755b912d870","type":"text","text":"remove or fix BACKWARDS_MAP in Identifier (fails testing with some probability) !! probably already fixed","x":-740,"y":-100,"width":305,"height":170}, - {"id":"8b8b22b9de04c759","type":"text","text":"automate wasm module for book","x":-740,"y":120,"width":250,"height":60}, - {"id":"4cbdbc839acac840","type":"text","text":"make set expression, not statement","x":-740,"y":240,"width":250,"height":60} + {"id":"b18a8755b912d870","type":"text","text":"remove or fix BACKWARDS_MAP in Identifier (fails testing with some probability) !! probably already fixed","x":-740,"y":-260,"width":305,"height":170}, + {"id":"8b8b22b9de04c759","type":"text","text":"automate wasm module for book","x":-740,"y":-60,"width":250,"height":60}, + {"id":"4cbdbc839acac840","type":"text","text":"make set expression, not statement","x":-740,"y":40,"width":250,"height":60} ], "edges":[] } \ No newline at end of file diff --git a/obsidian/TODO.canvas b/obsidian/TODO.canvas index 5473ecf..2e12458 100644 --- a/obsidian/TODO.canvas +++ b/obsidian/TODO.canvas @@ -1,18 +1,27 @@ { "nodes":[ - {"id":"feb6594a9d261a54","type":"text","text":"implement modules and imports","x":340,"y":-20,"width":250,"height":60}, - {"id":"4ab66adbfddd4d15","type":"text","text":"implement scope manipulation features","x":-40,"y":-100,"width":250,"height":60}, - {"id":"41499d9f6eafdeec","type":"text","text":"add trait polymorphism for native types","x":-480,"y":-50,"width":250,"height":60,"color":"4"}, - {"id":"1c18b7117da43e6e","type":"text","text":"implement \"evil\" features","x":-460,"y":-167,"width":250,"height":67,"color":"5"}, - {"id":"18df0d00841f02bd","type":"text","text":"add collections\n- [ ] list\n- [ ] set\n- [ ] map\n- [ ] tuple?","x":-60,"y":120,"width":250,"height":182,"color":"3"}, - {"id":"550b9c0e5c7cd03a","type":"text","text":"add derivation and implicit derivation, and make them overridable (the main reason is equality of objects)","x":680,"y":249,"width":250,"height":160}, - {"id":"b9e932828d9a3c13","type":"text","text":"make cd for windows and linux releases","x":680,"y":140,"width":250,"height":60}, - {"id":"5d4e5bd937436926","type":"text","text":"add computed properties","x":-700,"y":140,"width":250,"height":60,"color":"4"} + {"id":"0c274527d795ce38","x":-80,"y":600,"width":400,"height":400,"type":"group","label":"OPTIMIZATIONS"}, + {"id":"0e8289b07d6ca556","type":"group","x":-600,"y":600,"width":400,"height":380,"label":"CI/CD"}, + {"id":"b9c4b54397d2bf2d","x":-43,"y":666,"width":250,"height":87,"type":"text","text":"macro for computing hash of ident in compile time"}, + {"id":"b9e932828d9a3c13","type":"text","text":"make cd for windows and linux releases","x":-580,"y":640,"width":250,"height":50}, + {"id":"070a7b543247f7a2","type":"text","text":"simplified syntax for read-only properties","x":-290,"y":360,"width":250,"height":60}, + {"id":"5d4e5bd937436926","type":"text","text":"computed properties","x":-640,"y":360,"width":250,"height":60,"color":"4"}, + {"id":"18df0d00841f02bd","type":"text","text":"collections\n- [ ] list\n- [ ] set\n- [ ] map\n- [ ] tuple?","x":-80,"y":10,"width":250,"height":182,"color":"3"}, + {"id":"97a68fbd25b52ede","x":260,"y":-280,"width":250,"height":60,"type":"text","text":"destructuring let statements"}, + {"id":"1c18b7117da43e6e","type":"text","text":"\"evil\" features","x":-460,"y":-180,"width":250,"height":67,"color":"5"}, + {"id":"41499d9f6eafdeec","type":"text","text":"trait polymorphism for native types","x":-460,"y":-40,"width":250,"height":60,"color":"4"}, + {"id":"4ab66adbfddd4d15","type":"text","text":"scope manipulation features","x":-80,"y":-180,"width":250,"height":60}, + {"id":"feb6594a9d261a54","type":"text","text":"modules and imports","x":260,"y":-180,"width":250,"height":60}, + {"id":"545169f683d1ebef","x":620,"y":-180,"width":250,"height":60,"type":"text","text":"import as expression"}, + {"id":"550b9c0e5c7cd03a","type":"text","text":"derivation and implicit derivation, and make them overridable (the main reason is equality of objects)","x":620,"y":300,"width":250,"height":160} ], "edges":[ {"id":"4f43eea514ca8881","fromNode":"41499d9f6eafdeec","fromSide":"right","toNode":"4ab66adbfddd4d15","toSide":"left"}, {"id":"94361d23f182cbe8","fromNode":"4ab66adbfddd4d15","fromSide":"right","toNode":"feb6594a9d261a54","toSide":"left"}, {"id":"e3e0be30be175453","fromNode":"1c18b7117da43e6e","fromSide":"right","toNode":"4ab66adbfddd4d15","toSide":"left"}, - {"id":"8706d0e0442108f4","fromNode":"41499d9f6eafdeec","fromSide":"right","toNode":"18df0d00841f02bd","toSide":"left"} + {"id":"8706d0e0442108f4","fromNode":"41499d9f6eafdeec","fromSide":"right","toNode":"18df0d00841f02bd","toSide":"left"}, + {"id":"10698732105d19f9","fromNode":"5d4e5bd937436926","fromSide":"right","toNode":"070a7b543247f7a2","toSide":"left"}, + {"id":"889e945801477a2c","fromNode":"97a68fbd25b52ede","fromSide":"right","toNode":"545169f683d1ebef","toSide":"left"}, + {"id":"3f046cb411b32fae","fromNode":"feb6594a9d261a54","fromSide":"right","toNode":"545169f683d1ebef","toSide":"left"} ] } \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 271800c..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" \ No newline at end of file diff --git a/src/interpreter/builtins/operators.rs b/src/interpreter/builtins/operators.rs index 0cad6cc..69a1439 100644 --- a/src/interpreter/builtins/operators.rs +++ b/src/interpreter/builtins/operators.rs @@ -33,35 +33,44 @@ macro_rules! operator_group { } pub fn builtin_operators() -> HashMap { - let mut res = HashMap::from(operator_group!(Id::for_number(), Id::for_number(), [ - (for_plus, num_plus_num), - (for_minus, num_minus_num), - (for_multiply, num_mul_num), - (for_divide, num_div_num), - (for_mod, num_mod_num), - (for_pow, num_pow_num), - (for_less, num_less_num), - (for_less_eq, num_less_eq_num), - (for_greater, num_greater_num), - (for_greater_eq, num_greater_eq_num), - (for_eq, num_eq_num), - (for_not_eq, num_not_eq_num) - ])); - - res.extend(operator_group!(Id::for_bool(), Id::for_bool(), [ - (for_and, bool_and_bool), - (for_or, bool_or_bool) - ])); - - res.extend(operator_group!(Id::for_string(), Id::for_string(), [ - (for_combine, string_concat), - (for_less, string_less_string), - (for_less_eq, string_less_eq_string), - (for_greater, string_greater_string), - (for_greater_eq, string_greater_eq_string), - (for_eq, string_eq_string), - (for_not_eq, string_not_eq_string) - ])); + let mut res = HashMap::from(operator_group!( + Id::for_number(), + Id::for_number(), + [ + (for_plus, num_plus_num), + (for_minus, num_minus_num), + (for_multiply, num_mul_num), + (for_divide, num_div_num), + (for_mod, num_mod_num), + (for_pow, num_pow_num), + (for_less, num_less_num), + (for_less_eq, num_less_eq_num), + (for_greater, num_greater_num), + (for_greater_eq, num_greater_eq_num), + (for_eq, num_eq_num), + (for_not_eq, num_not_eq_num) + ] + )); + + res.extend(operator_group!( + Id::for_bool(), + Id::for_bool(), + [(for_and, bool_and_bool), (for_or, bool_or_bool)] + )); + + res.extend(operator_group!( + Id::for_string(), + Id::for_string(), + [ + (for_combine, string_concat), + (for_less, string_less_string), + (for_less_eq, string_less_eq_string), + (for_greater, string_greater_string), + (for_greater_eq, string_greater_eq_string), + (for_eq, string_eq_string), + (for_not_eq, string_not_eq_string) + ] + )); res.extend([ ( diff --git a/src/interpreter/expression.rs b/src/interpreter/expression.rs index caa2af9..a783c8c 100644 --- a/src/interpreter/expression.rs +++ b/src/interpreter/expression.rs @@ -1,13 +1,15 @@ -use std::rc::Rc; +use std::{path::PathBuf, rc::Rc}; use crate::interpreter::{ control::Control, identifier::{Identifier, OperatorIdentifier}, + runner, scope::Scope, statement::FruStatement, value::fru_value::FruValue, value::function::{ArgumentList, EvaluatedArgumentList, FormalParameters, FruFunction}, }; +use crate::stdlib::scope::fru_scope::{extract_scope_from_value, FruScope}; #[derive(Debug, Clone)] pub enum FruExpression { @@ -17,6 +19,7 @@ pub enum FruExpression { Variable { ident: Identifier, }, + ScopeAccessor, Function { args: FormalParameters, body: Rc, @@ -25,6 +28,11 @@ pub enum FruExpression { body: Vec, expr: Box, }, + ScopeModifier { + what: Box, + body: Vec, + expr: Box, + }, Call { what: Box, args: ArgumentList, @@ -51,6 +59,9 @@ pub enum FruExpression { then_body: Box, else_body: Box, }, + Import { + path: Box, + }, } fn eval_args(args: &ArgumentList, scope: Rc) -> Result { @@ -61,7 +72,7 @@ fn eval_args(args: &ArgumentList, scope: Rc) -> Result Result<_, Control> { Ok((*ident, arg.evaluate(scope.clone())?)) }) - .try_collect()?, + .collect::>()?, }) } @@ -72,19 +83,17 @@ impl FruExpression { FruExpression::Variable { ident } => Ok(scope.get_variable(*ident)?), - FruExpression::Function { args, body } => { - Ok(FruFunction { - argument_idents: args.clone(), - body: body.clone(), - scope: scope.clone(), - } - .into()) + FruExpression::ScopeAccessor => Ok(FruScope::new_value(scope)), + + FruExpression::Function { args, body } => Ok(FruFunction { + argument_idents: args.clone(), + body: body.clone(), + scope: scope.clone(), } + .into()), FruExpression::Block { body, expr } => { - if !body.is_empty() { - scope = Scope::new_with_parent(scope.clone()) - }; + scope = Scope::new_with_parent(scope.clone()); for statement in body { statement.execute(scope.clone())?; @@ -92,6 +101,24 @@ impl FruExpression { expr.evaluate(scope) } + FruExpression::ScopeModifier { what, body, expr } => { + let what = what.evaluate(scope)?; + let new_scope = match extract_scope_from_value(&what) { + Some(x) => x, + None => { + return Control::new_err(format!( + "Expected `Scope` in scope modifier expression, got `{}`", + what.get_type_identifier() + )) + } + }; + + for statement in body { + statement.execute(new_scope.clone())?; + } + + expr.evaluate(new_scope) + } FruExpression::Call { what, args } => { let callee = what.evaluate(scope.clone())?; @@ -143,23 +170,40 @@ impl FruExpression { condition, then_body, else_body, - } => { - match condition.evaluate(scope.clone())? { - FruValue::Bool(b) => { - if b { - then_body.evaluate(scope.clone()) - } else { - else_body.evaluate(scope.clone()) - } + } => match condition.evaluate(scope.clone())? { + FruValue::Bool(b) => { + if b { + then_body.evaluate(scope.clone()) + } else { + else_body.evaluate(scope.clone()) } + } + + unexpected => Control::new_err(format!( + "Expected `Bool` in if condition, got `{}`", + unexpected.get_type_identifier() + )), + }, + + FruExpression::Import { path } => { + let path = path.evaluate(scope.clone())?; - unexpected => { - Control::new_err(format!( - "Expected `Bool` in if condition, got `{}`", - unexpected.get_type_identifier() + let path = match path { + FruValue::String(path) => path, + + _ => { + return Control::new_err(format!( + "Expected `String` in import path, got `{}`", + path.get_type_identifier() )) } - } + }; + + let path = PathBuf::from(path); + + let result_scope = runner::execute_file(&path)?; + + Ok(FruScope::new_value(result_scope)) } } } diff --git a/src/interpreter/identifier.rs b/src/interpreter/identifier.rs index 514342f..a6f225c 100644 --- a/src/interpreter/identifier.rs +++ b/src/interpreter/identifier.rs @@ -1,10 +1,5 @@ use std::{ - collections::HashMap, - fmt::Debug, - fmt::Display, - hash::DefaultHasher, - hash::Hash, - hash::Hasher, + collections::HashMap, fmt::Debug, fmt::Display, hash::DefaultHasher, hash::Hash, hash::Hasher, sync::Mutex, }; diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index cc682b0..520ae1f 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -1,9 +1,10 @@ -use std::{path::Path, rc::Rc}; +use std::{fs::read_to_string, path::Path, rc::Rc}; use crate::interpreter::{control::Control, error::FruError, scope::Scope, tree_sitter_parser}; pub fn execute_file(path: &Path) -> Result, FruError> { - let source_code = std::fs::read_to_string(path).unwrap(); + let source_code = read_to_string(path) + .map_err(|err| FruError::new(format!("Error reading file {path:?} {err}")))?; let ast = match tree_sitter_parser::parse(source_code) { Ok(ast) => ast, diff --git a/src/interpreter/scope.rs b/src/interpreter/scope.rs index 6c75a8f..6354707 100644 --- a/src/interpreter/scope.rs +++ b/src/interpreter/scope.rs @@ -98,12 +98,10 @@ impl Scope { Ok(op.clone()) } else { match &self.parent { - ScopeAncestor::None => { - Err(FruError::new(format!( - "operator `{:?}` does not exist", - ident - ))) - } + ScopeAncestor::None => Err(FruError::new(format!( + "operator `{:?}` does not exist", + ident + ))), ScopeAncestor::Parent(parent) | ScopeAncestor::Object { parent, .. } | ScopeAncestor::Type { parent, .. } => parent.get_operator(ident), @@ -118,6 +116,10 @@ impl Scope { pub fn has_variable(&self, ident: Identifier) -> bool { self.variables.borrow().contains_key(&ident) } + + pub fn let_set_variable(&self, ident: Identifier, value: FruValue) { + self.variables.borrow_mut().insert(ident, value); + } } impl ScopeAncestor { @@ -144,17 +146,13 @@ impl ScopeAncestor { ScopeAncestor::Parent(parent) => parent.set_variable(ident, value), - ScopeAncestor::Object { object, parent } => { - object - .set_prop(ident, value.clone()) - .or_else(|_| parent.set_variable(ident, value)) - } + ScopeAncestor::Object { object, parent } => object + .set_prop(ident, value.clone()) + .or_else(|_| parent.set_variable(ident, value)), - ScopeAncestor::Type { type_, parent } => { - type_ - .set_prop(ident, value.clone()) - .or_else(|_| parent.set_variable(ident, value)) - } + ScopeAncestor::Type { type_, parent } => type_ + .set_prop(ident, value.clone()) + .or_else(|_| parent.set_variable(ident, value)), } } } diff --git a/src/interpreter/statement.rs b/src/interpreter/statement.rs index 5802303..01e3b48 100644 --- a/src/interpreter/statement.rs +++ b/src/interpreter/statement.rs @@ -1,19 +1,18 @@ -use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::interpreter::{ control::Control, - error::FruError, expression::FruExpression, identifier::{Identifier, OperatorIdentifier}, - runner, scope::Scope, value::fru_type::{FruField, FruType, Property, TypeType}, value::fru_value::FruValue, value::function::{FormalParameters, FruFunction}, value::operator::AnyOperator, }; -use crate::stdlib::scope::fru_scope::FruScope; +use crate::stdlib::scope::fru_scope::extract_scope_from_value; +// TODO: make normal struct pub type RawMethods = Vec<(bool, Identifier, FormalParameters, Rc)>; #[derive(Debug, Clone)] @@ -24,6 +23,10 @@ pub enum FruStatement { Block { body: Vec, }, + ScopeModifier { + what: Box, + body: Vec, + }, Expression { value: Box, }, @@ -72,9 +75,6 @@ pub enum FruStatement { static_properties: HashMap, methods: RawMethods, }, - Import { - path: Box, - }, } impl FruStatement { @@ -94,6 +94,23 @@ impl FruStatement { } } + FruStatement::ScopeModifier { what, body } => { + let what = what.evaluate(scope)?; + let new_scope = match extract_scope_from_value(&what) { + Some(x) => x, + None => { + return Control::new_err(format!( + "Expected `Scope` in scope modifier statement, got `{}`", + what.get_type_identifier() + )) + } + }; + + for statement in body { + statement.execute(new_scope.clone())?; + } + } + FruStatement::Expression { value } => { value.evaluate(scope.clone())?; } @@ -258,37 +275,6 @@ impl FruStatement { ), )?; } - - FruStatement::Import { path } => { - let path = path.evaluate(scope.clone())?; - - let path = match path { - FruValue::String(path) => path, - - _ => { - return Control::new_err(format!( - "Expected `String` in import path, got `{}`", - path.get_type_identifier() - )); - } - }; - - let path = PathBuf::from(path); - - let name = path - .file_stem() - .ok_or_else(|| FruError::new(format!("File path \"{:?}\" is not valid", path)))? - .to_str() - .unwrap(); - - let result_scope = match runner::execute_file(&path) { - Ok(x) => x, - - Err(err) => return Err(Control::Error(err)), - }; - - scope.let_variable(Identifier::new(name), FruScope::new_value(result_scope))?; - } } Ok(()) diff --git a/src/interpreter/tree_sitter_parser.rs b/src/interpreter/tree_sitter_parser.rs index bad405e..7debee4 100644 --- a/src/interpreter/tree_sitter_parser.rs +++ b/src/interpreter/tree_sitter_parser.rs @@ -103,11 +103,9 @@ impl<'a> NodeWrapper<'a> { } fn text(&self) -> Result<&'a str, ParseError> { - self.node.utf8_text(self.source).map_err(|x| { - ParseError::Utf8Error { - position: self.node.range(), - error: x, - } + self.node.utf8_text(self.source).map_err(|x| ParseError::Utf8Error { + position: self.node.range(), + error: x, }) } @@ -119,12 +117,10 @@ impl<'a> NodeWrapper<'a> { match self.node.child_by_field_name(name) { Some(x) => Ok(Self::new(x, self.source)), - None => { - Err(ParseError::MissingAst { - position: self.node.range(), - name: name.to_string(), - }) - } + None => Err(ParseError::MissingAst { + position: self.node.range(), + name: name.to_string(), + }), } } @@ -160,7 +156,7 @@ impl<'a> NodeWrapper<'a> { self.node .children_by_field_name(name, &mut self.node.walk()) .map(|x| parser(Self::new(x, self.source))) - .try_collect() + .collect() } fn parse_optional_child( @@ -216,66 +212,53 @@ fn search_for_errors(ast: Node) -> ParseError { fn parse_statement(ast: NodeWrapper) -> Result { let result_statement = match ast.grammar_name() { - "source_file" => { - FruStatement::SourceCode { - body: ast.parse_children("body", parse_statement)?, - } - } - - "block_statement" => { - FruStatement::Block { - body: ast.parse_children("body", parse_statement)?, - } - } - - "expression_statement" => { - FruStatement::Expression { - value: ast.parse_child_expression("value")?.wrap_box(), - } - } - - "let_statement" => { - FruStatement::Let { - ident: ast.get_child_ident("ident")?, - value: ast.parse_child_expression("value")?.wrap_box(), - } - } - - "set_statement" => { - FruStatement::Set { - ident: ast.get_child_ident("ident")?, - value: ast.parse_child_expression("value")?.wrap_box(), - } - } - - "set_prop_statement" => { - FruStatement::SetProp { - what: ast.parse_child_expression("what")?.wrap_box(), - ident: ast.get_child_ident("ident")?, - value: ast.parse_child_expression("value")?.wrap_box(), - } - } - - "if_statement" => { - FruStatement::If { - condition: ast.parse_child_expression("condition")?.wrap_box(), - then_body: ast.parse_child_statement("then_body")?.wrap_box(), - else_body: ast.parse_optional_child("else_body", parse_statement)?.map(Box::new), - } - } - - "while_statement" => { - FruStatement::While { - condition: ast.parse_child_expression("condition")?.wrap_box(), - body: ast.parse_child_statement("body")?.wrap_box(), - } - } - - "return_statement" => { - FruStatement::Return { - value: ast.parse_optional_child("value", parse_expression)?.map(Box::new), - } - } + "source_file" => FruStatement::SourceCode { + body: ast.parse_children("body", parse_statement)?, + }, + + "block_statement" => FruStatement::Block { + body: ast.parse_children("body", parse_statement)?, + }, + + "scope_modifier_statement" => FruStatement::ScopeModifier { + what: ast.parse_child_expression("what")?.wrap_box(), + body: ast.parse_children("body", parse_statement)?, + }, + + "expression_statement" => FruStatement::Expression { + value: ast.parse_child_expression("value")?.wrap_box(), + }, + + "let_statement" => FruStatement::Let { + ident: ast.get_child_ident("ident")?, + value: ast.parse_child_expression("value")?.wrap_box(), + }, + + "set_statement" => FruStatement::Set { + ident: ast.get_child_ident("ident")?, + value: ast.parse_child_expression("value")?.wrap_box(), + }, + + "set_prop_statement" => FruStatement::SetProp { + what: ast.parse_child_expression("what")?.wrap_box(), + ident: ast.get_child_ident("ident")?, + value: ast.parse_child_expression("value")?.wrap_box(), + }, + + "if_statement" => FruStatement::If { + condition: ast.parse_child_expression("condition")?.wrap_box(), + then_body: ast.parse_child_statement("then_body")?.wrap_box(), + else_body: ast.parse_optional_child("else_body", parse_statement)?.map(Box::new), + }, + + "while_statement" => FruStatement::While { + condition: ast.parse_child_expression("condition")?.wrap_box(), + body: ast.parse_child_statement("body")?.wrap_box(), + }, + + "return_statement" => FruStatement::Return { + value: ast.parse_optional_child("value", parse_expression)?.map(Box::new), + }, "break_statement" => FruStatement::Break, @@ -334,35 +317,31 @@ fn parse_statement(ast: NodeWrapper) -> Result { TypeMember::StaticField(f) => static_fields.push(f), - TypeMember::Property(p) => { - match properties.entry(p.ident) { - Entry::Occupied(_) => { - return Err(ParseError::Error { - position: ast.get_child("members")?.range(), - error: format!("Duplicate property: `{}`", p.ident), - }); - } - - Entry::Vacant(entry) => { - entry.insert(p); - } + TypeMember::Property(p) => match properties.entry(p.ident) { + Entry::Occupied(_) => { + return Err(ParseError::Error { + position: ast.get_child("members")?.range(), + error: format!("Duplicate property: `{}`", p.ident), + }); } - } - - TypeMember::StaticProperty(p) => { - match static_properties.entry(p.ident) { - Entry::Occupied(_) => { - return Err(ParseError::Error { - position: ast.get_child("members")?.range(), - error: format!("Duplicate static property: `{}`", p.ident), - }); - } - - Entry::Vacant(entry) => { - entry.insert(p); - } + + Entry::Vacant(entry) => { + entry.insert(p); + } + }, + + TypeMember::StaticProperty(p) => match static_properties.entry(p.ident) { + Entry::Occupied(_) => { + return Err(ParseError::Error { + position: ast.get_child("members")?.range(), + error: format!("Duplicate static property: `{}`", p.ident), + }); } - } + + Entry::Vacant(entry) => { + entry.insert(p); + } + }, } } @@ -379,12 +358,6 @@ fn parse_statement(ast: NodeWrapper) -> Result { } } - "import_statement" => { - FruStatement::Import { - path: ast.parse_child_expression("path")?.wrap_box(), - } - } - unexpected => { return Err(ParseError::InvalidAst { position: ast.range(), @@ -398,106 +371,90 @@ fn parse_statement(ast: NodeWrapper) -> Result { fn parse_expression(ast: NodeWrapper) -> Result { let result_expression = match ast.grammar_name() { - "nah_literal" => { - FruExpression::Literal { - value: FruValue::Nah, - } - } + "nah_literal" => FruExpression::Literal { + value: FruValue::Nah, + }, - "number_literal" => { - FruExpression::Literal { - value: FruValue::Number(ast.text()?.parse().unwrap()), - } - } + "number_literal" => FruExpression::Literal { + value: FruValue::Number(ast.text()?.parse().unwrap()), + }, - "bool_literal" => { - FruExpression::Literal { - value: FruValue::Bool(ast.text()?.parse().unwrap()), - } - } + "bool_literal" => FruExpression::Literal { + value: FruValue::Bool(ast.text()?.parse().unwrap()), + }, - "string_literal" => { - match unescape(&ast.text()?.replace("\\\n", "\n")) { - Ok(s) => { - FruExpression::Literal { - value: FruValue::String(s), - } - } + "string_literal" => match unescape(&ast.text()?.replace("\\\n", "\n")) { + Ok(s) => FruExpression::Literal { + value: FruValue::String(s), + }, - Err(err) => { - return Err(ParseError::InvalidAst { - position: ast.range(), - error: err.to_string(), - }); - } - } - } - - "variable" => { - FruExpression::Variable { - ident: ast.get_child_ident("ident")?, - } - } - - "function_expression" => { - FruExpression::Function { - args: ast.parse_child("parameters", parse_formal_parameters)?, - body: ast.parse_child("body", parse_function_body)?.wrap_rc(), - } - } - - "parenthesized_expression" => ast.parse_child_expression("expr")?, - - "block_expression" => { - FruExpression::Block { - body: ast.parse_children("body", parse_statement)?, - expr: ast.parse_child_expression("expr")?.wrap_box(), - } - } - - "call_expression" => { - FruExpression::Call { - what: ast.parse_child_expression("what")?.wrap_box(), - args: ast.parse_child("args", parse_argument_list)?, + Err(err) => { + return Err(ParseError::InvalidAst { + position: ast.range(), + error: err.to_string(), + }); } - } + }, - "curry_call_expression" => { - FruExpression::CurryCall { - what: ast.parse_child_expression("what")?.wrap_box(), - args: ast.parse_child("args", parse_argument_list)?, - } - } + "variable" => FruExpression::Variable { + ident: ast.get_child_ident("ident")?, + }, - "instantiation_expression" => { - FruExpression::Instantiation { - what: ast.parse_child_expression("what")?.wrap_box(), - args: ast.parse_child("args", parse_argument_list_instantiation)?, - } - } + "scope_expression" => FruExpression::ScopeAccessor, - "prop_access_expression" => { - FruExpression::PropAccess { - what: ast.parse_child_expression("what")?.wrap_box(), - ident: ast.get_child_ident("ident")?, - } - } + "function_expression" => FruExpression::Function { + args: ast.parse_child("parameters", parse_formal_parameters)?, + body: ast.parse_child("body", parse_function_body)?.wrap_rc(), + }, - "binary_expression" => { - FruExpression::Binary { - operator: ast.get_child_ident("operator")?, - left: ast.parse_child_expression("left")?.wrap_box(), - right: ast.parse_child_expression("right")?.wrap_box(), - } - } + "parenthesized_expression" => ast.parse_child_expression("expr")?, - "if_expression" => { - FruExpression::If { - condition: ast.parse_child_expression("condition")?.wrap_box(), - then_body: ast.parse_child_expression("then_body")?.wrap_box(), - else_body: ast.parse_child_expression("else_body")?.wrap_box(), - } - } + "block_expression" => FruExpression::Block { + body: ast.parse_children("body", parse_statement)?, + expr: ast.parse_child_expression("expr")?.wrap_box(), + }, + + "scope_modifier_expression" => FruExpression::ScopeModifier { + what: ast.parse_child_expression("what")?.wrap_box(), + body: ast.parse_children("body", parse_statement)?, + expr: ast.parse_child_expression("expr")?.wrap_box(), + }, + + "call_expression" => FruExpression::Call { + what: ast.parse_child_expression("what")?.wrap_box(), + args: ast.parse_child("args", parse_argument_list)?, + }, + + "curry_call_expression" => FruExpression::CurryCall { + what: ast.parse_child_expression("what")?.wrap_box(), + args: ast.parse_child("args", parse_argument_list)?, + }, + + "instantiation_expression" => FruExpression::Instantiation { + what: ast.parse_child_expression("what")?.wrap_box(), + args: ast.parse_child("args", parse_argument_list_instantiation)?, + }, + + "prop_access_expression" => FruExpression::PropAccess { + what: ast.parse_child_expression("what")?.wrap_box(), + ident: ast.get_child_ident("ident")?, + }, + + "binary_expression" => FruExpression::Binary { + operator: ast.get_child_ident("operator")?, + left: ast.parse_child_expression("left")?.wrap_box(), + right: ast.parse_child_expression("right")?.wrap_box(), + }, + + "if_expression" => FruExpression::If { + condition: ast.parse_child_expression("condition")?.wrap_box(), + then_body: ast.parse_child_expression("then_body")?.wrap_box(), + else_body: ast.parse_child_expression("else_body")?.wrap_box(), + }, + + "import_expression" => FruExpression::Import { + path: ast.parse_child_expression("path")?.wrap_box(), + }, unexpected => { return Err(ParseError::InvalidAst { @@ -525,11 +482,9 @@ fn parse_function_body(ast: NodeWrapper) -> Result { Ok(match ast.grammar_name() { "block_statement" => parse_statement(ast)?, - "block_expression" => { - FruStatement::Return { - value: Some(parse_expression(ast)?.wrap_box()), - } - } + "block_expression" => FruStatement::Return { + value: Some(parse_expression(ast)?.wrap_box()), + }, unexpected => { return Err(ParseError::InvalidAst { @@ -546,12 +501,10 @@ fn parse_type_member(ast: NodeWrapper) -> Result { "type_property" => parse_property(ast), - unexpected => { - Err(ParseError::InvalidAst { - position: ast.range(), - error: format!("Not a type member: {}", unexpected), - }) - } + unexpected => Err(ParseError::InvalidAst { + position: ast.range(), + error: format!("Not a type member: {}", unexpected), + }), } } @@ -702,18 +655,14 @@ fn parse_formal_parameter( match x.grammar_name() { "positional_parameter" => Ok((x.get_child_ident("ident")?, None)), - "default_parameter" => { - Ok(( - x.get_child_ident("ident")?, - Some(x.parse_child_expression("value")?), - )) - } - unexpected => { - Err(ParseError::InvalidAst { - position: x.range(), - error: format!("Not a formal parameter: {}", unexpected), - }) - } + "default_parameter" => Ok(( + x.get_child_ident("ident")?, + Some(x.parse_child_expression("value")?), + )), + unexpected => Err(ParseError::InvalidAst { + position: x.range(), + error: format!("Not a formal parameter: {}", unexpected), + }), } } @@ -765,18 +714,14 @@ fn parse_argument_item( match ast.grammar_name() { "positional_argument" => Ok((None, ast.parse_child_expression("value")?)), - "named_argument" => { - Ok(( - Some(ast.get_child_ident("ident")?), - ast.parse_child_expression("value")?, - )) - } + "named_argument" => Ok(( + Some(ast.get_child_ident("ident")?), + ast.parse_child_expression("value")?, + )), - unexpected => { - Err(ParseError::InvalidAst { - position: ast.range(), - error: format!("Not an argument: {}", unexpected), - }) - } + unexpected => Err(ParseError::InvalidAst { + position: ast.range(), + error: format!("Not an argument: {}", unexpected), + }), } } diff --git a/src/interpreter/value/fru_object.rs b/src/interpreter/value/fru_object.rs index 1bbfe50..c0e667f 100644 --- a/src/interpreter/value/fru_object.rs +++ b/src/interpreter/value/fru_object.rs @@ -124,12 +124,10 @@ impl FruObject { let tt = self.get_type().get_type_type(); match tt { - TypeType::Struct => { - FruObject::new_object( - self.get_type(), - self.internal.fields.borrow().iter().map(FruValue::fru_clone).collect(), - ) - } + TypeType::Struct => FruObject::new_object( + self.get_type(), + self.internal.fields.borrow().iter().map(FruValue::fru_clone).collect(), + ), TypeType::Class | TypeType::Data => FruValue::Object(self.clone()), } diff --git a/src/interpreter/value/fru_value.rs b/src/interpreter/value/fru_value.rs index c67aa68..962f69e 100644 --- a/src/interpreter/value/fru_value.rs +++ b/src/interpreter/value/fru_value.rs @@ -69,14 +69,12 @@ impl FruValue { )))) } - normal => { - Ok(FruValue::Function(AnyFunction::CurriedFunction(Rc::new( - CurriedFunction { - saved_args: args, - function: Rc::new(normal.clone()), - }, - )))) - } + normal => Ok(FruValue::Function(AnyFunction::CurriedFunction(Rc::new( + CurriedFunction { + saved_args: args, + function: Rc::new(normal.clone()), + }, + )))), } } @@ -92,12 +90,10 @@ impl FruValue { FruValue::NativeObject(obj) => obj.instantiate(args), - _ => { - FruError::new_res(format!( - "`{}` is not instantiatable", - self.get_type_identifier() - )) - } + _ => FruError::new_res(format!( + "`{}` is not instantiatable", + self.get_type_identifier() + )), } } @@ -109,12 +105,10 @@ impl FruValue { FruValue::NativeObject(obj) => obj.get_prop(ident), - _ => { - FruError::new_res(format!( - "cannot access prop of `{}`", - self.get_type_identifier() - )) - } + _ => FruError::new_res(format!( + "cannot access prop of `{}`", + self.get_type_identifier() + )), } } @@ -126,12 +120,10 @@ impl FruValue { FruValue::NativeObject(obj) => obj.set_prop(ident, value), - _ => { - FruError::new_res(format!( - "cannot set prop of `{}`", - self.get_type_identifier() - )) - } + _ => FruError::new_res(format!( + "cannot set prop of `{}`", + self.get_type_identifier() + )), } } diff --git a/src/interpreter/value/native/object.rs b/src/interpreter/value/native/object.rs index 2a5d5b6..5ff4559 100644 --- a/src/interpreter/value/native/object.rs +++ b/src/interpreter/value/native/object.rs @@ -1,13 +1,14 @@ +use std::any::Any; use std::rc::Rc; use crate::interpreter::{ - error::FruError, - identifier::Identifier, - value::fru_value::FruValue, + error::FruError, identifier::Identifier, value::fru_value::FruValue, value::function::EvaluatedArgumentList, }; pub trait INativeObject { + fn as_any(&self) -> &dyn Any; + fn get_type_identifier(&self) -> Identifier { Identifier::for_native_object() } @@ -86,4 +87,8 @@ impl NativeObject { pub fn fru_clone(&self) -> FruValue { FruValue::NativeObject(self.internal.fru_clone(self)) } + + pub fn downcast(&self) -> Option<&T> { + self.internal.as_any().downcast_ref::() + } } diff --git a/src/main.rs b/src/main.rs index c3fc662..ea224d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![feature(iterator_try_collect)] - use std::{path::PathBuf, time::Instant}; use crate::interpreter::runner::execute_file; diff --git a/src/stdlib/scope/fru_scope.rs b/src/stdlib/scope/fru_scope.rs index d57deab..376739c 100644 --- a/src/stdlib/scope/fru_scope.rs +++ b/src/stdlib/scope/fru_scope.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{any::Any, rc::Rc}; use crate::interpreter::{ error::FruError, @@ -19,6 +19,10 @@ impl FruScope { } impl INativeObject for FruScope { + fn as_any(&self) -> &dyn Any { + self + } + fn get_type_identifier(&self) -> Identifier { Identifier::new("Scope") } @@ -28,8 +32,15 @@ impl INativeObject for FruScope { } fn set_prop(&self, ident: Identifier, value: FruValue) -> Result<(), FruError> { - self.scope - .set_variable(ident, value.clone()) - .or_else(|_| self.scope.let_variable(ident, value)) + self.scope.let_set_variable(ident, value); + Ok(()) + } +} + +pub fn extract_scope_from_value(v: &FruValue) -> Option> { + if let FruValue::NativeObject(o) = v { + o.downcast::().map(|x| x.scope.clone()) + } else { + None } } diff --git a/tests/expression/block_expression_tests.rs b/tests/expression/block_expression_tests.rs index de28acb..939009d 100644 --- a/tests/expression/block_expression_tests.rs +++ b/tests/expression/block_expression_tests.rs @@ -8,7 +8,7 @@ fn test_basics() { let x = x + 7; x * x }; - + assert_eq(y, 64); "#) } diff --git a/tests/expression/curry_call_expression_tests.rs b/tests/expression/curry_call_expression_tests.rs index c042804..5df1d17 100644 --- a/tests/expression/curry_call_expression_tests.rs +++ b/tests/expression/curry_call_expression_tests.rs @@ -4,13 +4,13 @@ use crate::run; fn test_curry_1() { run(r#" let f = fn (a, b, c) {a + b + c}; - + let g = f$(1); - + assert_eq(g(2, 3), 6); - + assert_eq(f(1, 2, 3), 6); - + assert_eq(g$(2)(5), 8); print(f); diff --git a/tests/expression/function_expression_tests.rs b/tests/expression/function_expression_tests.rs index fe03b78..379b11f 100644 --- a/tests/expression/function_expression_tests.rs +++ b/tests/expression/function_expression_tests.rs @@ -6,11 +6,11 @@ fn test_function() { let func = fn (x, y) { return x + y + {x * y}; }; - + let func_same = fn (x, y) { x + y + {x * y} }; - + assert_eq(func(1, 2), 5); assert_eq(func_same(5, 5), 35); "#) @@ -22,15 +22,15 @@ fn test_function_decorator() { let func = fn (x, y) { return x + y + {x * y}; }; - + let decorator = fn (func) { return fn (x, y) { return func(x - 1, y + 1) + 1; }; }; - + func = decorator(func); - + assert_eq(func(2, 1), 6); assert_eq(func(6, 4), 36); "#) @@ -41,12 +41,12 @@ fn test_function_nested() { run(r#" let comp = fn(x) { let tr1 = fn(y) { x + y }; - + let tr2 = fn(y) { x * y }; - + tr2(tr1(1)) }; - + assert_eq(comp(6), 42); "#) } @@ -81,13 +81,13 @@ fn test_eval_3() { fn test_overall() { run(r#" let f = fn (a, b) {a + b}; - + let dec = fn (func) { fn (w) { func$(w) } }; - + let g = dec(f); - + assert_eq(g(1)(2), 3); "#) } diff --git a/tests/expression/instantiation_expression_tests.rs b/tests/expression/instantiation_expression_tests.rs index 1e6322d..3e958c1 100644 --- a/tests/expression/instantiation_expression_tests.rs +++ b/tests/expression/instantiation_expression_tests.rs @@ -15,7 +15,7 @@ fn test_error_propagation_2() { struct Box { f; } - + let b = Box :{ 1 / 0 }; "#) } @@ -27,7 +27,7 @@ fn test_error_propagation_3() { struct Box { f; } - + Box :{ 1, 2 }; "#) } diff --git a/tests/expression/variable_expression_tests.rs b/tests/expression/variable_expression_tests.rs index 6ff2663..23f0b90 100644 --- a/tests/expression/variable_expression_tests.rs +++ b/tests/expression/variable_expression_tests.rs @@ -48,7 +48,7 @@ fn test_while() { let a = a - 4; s = s + a; } - + assert_eq(s, -6); "#) } diff --git a/tests/lib.rs b/tests/lib.rs index f33b611..2f3f73f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,5 +1,3 @@ -#![feature(iterator_try_collect)] - use std::io::Write; use tempfile::NamedTempFile; @@ -9,10 +7,14 @@ use crate::interpreter::{identifier::reset_poison, runner::execute_file}; #[path = "../src/interpreter/mod.rs"] mod interpreter; +#[path = "../src/stdlib/mod.rs"] +mod stdlib; + mod builtin; mod expression; mod literal_expression; mod oop; +mod scope_manipulation; mod statement; pub fn run(code: &str) { diff --git a/tests/literal_expression/literal_bool_tests.rs b/tests/literal_expression/literal_bool_tests.rs index 8945cbc..6c05383 100644 --- a/tests/literal_expression/literal_bool_tests.rs +++ b/tests/literal_expression/literal_bool_tests.rs @@ -7,7 +7,7 @@ fn test_basics() { let b = false; assert_eq(a, true); assert_eq(b, false); - + print(true); "#); } diff --git a/tests/oop/class_tests.rs b/tests/oop/class_tests.rs index c2052ce..b87aa17 100644 --- a/tests/oop/class_tests.rs +++ b/tests/oop/class_tests.rs @@ -6,17 +6,17 @@ fn test_class() { class Box { f; } - + let b = Box :{ f: 5 }; assert_eq(b.f, 5); - + b.f = 10; assert_eq(b.f, 10); - + let b2 = b; - + assert_eq(b2.f, 10); - + b2.f = 20; assert_eq(b.f, 20); assert_eq(b2.f, 20); diff --git a/tests/oop/data_tests.rs b/tests/oop/data_tests.rs index 0828dc7..da5b671 100644 --- a/tests/oop/data_tests.rs +++ b/tests/oop/data_tests.rs @@ -7,10 +7,10 @@ fn test_data() { data Box { f; } - + let b = Box :{ 5 }; assert_eq(b.f, 5); - + b.f = 10; "#) } diff --git a/tests/oop/general_tests.rs b/tests/oop/general_tests.rs index 0b4a7e2..62911e7 100644 --- a/tests/oop/general_tests.rs +++ b/tests/oop/general_tests.rs @@ -36,29 +36,29 @@ fn test_operators() { x; y; } - + operator + (a : Vec2, b : Vec2) { Vec2 :{ a.x + b.x, a.y + b.y } } - + commutative operator * (a : Vec2, k : Number) { Vec2 :{ a.x * k, a.y * k } } - + operator +-*/%=<>&|^!? (a : Number, k : Number) { a * k } - + let v1 = Vec2 :{ 1, 2 }; let v2 = Vec2 :{ 3, 4 }; - + assert_eq(v1 + v2, Vec2 :{ 4, 6 }); - + assert_eq(v1 * 2, Vec2 :{ 2, 4 }); assert_eq(5 * v1 * 2, Vec2 :{ 10, 20 }); - + assert_eq(6 +-*/%=<>&|^!? 9, 54); - + print(Vec2); "#) } diff --git a/tests/oop/property_tests.rs b/tests/oop/property_tests.rs index e0d7a07..85d3655 100644 --- a/tests/oop/property_tests.rs +++ b/tests/oop/property_tests.rs @@ -6,7 +6,7 @@ fn test_basics() { struct Vec { x; y; - + Length { get { (x * x + y * y) ** 0.5 } set(value) { @@ -16,12 +16,12 @@ fn test_basics() { } } } - + let v = Vec :{ x: 3, y: 4 }; assert_eq(v.Length, 5); - + v.Length = 1; - + assert_eq(v.x + v.y, 1.4); "#) } @@ -31,14 +31,14 @@ fn test_getter_arrow() { run(r#" struct Thing { x; - + Foo { get => x + 1; } } - + let t = Thing :{ x: 3 }; - + assert_eq(t.Foo, 4); "#) } @@ -48,23 +48,23 @@ fn test_other() { run(r#" struct Thing { x; - + Foo { set(val) { if val == 3 { return; } - + x = val * 2; } } } - + let t = Thing :{ x: 3 }; t.Foo = 5; assert_eq(t.x, 10); - + t.Foo = 3; assert_eq(t.x, 10); "#) @@ -77,9 +77,9 @@ fn test_no_getter() { struct Thing { X {} } - + let t = Thing :{}; - + t.X; "#) } @@ -91,13 +91,37 @@ fn test_no_setter() { struct Thing { X {} } - + let t = Thing :{}; - + t.X = 3; "#) } +#[test] +#[should_panic(expected = "static property `X` has no getter")] +fn test_static_no_getter() { + run(r#" + struct Thing { + static X {} + } + + Thing.X; + "#) +} + +#[test] +#[should_panic(expected = "static property `X` has no setter")] +fn test_static_no_setter() { + run(r#" + struct Thing { + static X {} + } + + Thing.X = 3; + "#) +} + #[test] #[should_panic(expected = "unexpected signal Continue")] fn test_unexpected_signal() { @@ -109,9 +133,27 @@ fn test_unexpected_signal() { } } } - + let t = Thing :{}; - + + t.X = 3; + "#) +} + +#[test] +#[should_panic(expected = "division by zero")] +fn test_error_propagation() { + run(r#" + struct Thing { + X { + set { + 1 / 0; + } + } + } + + let t = Thing :{}; + t.X = 3; "#) } @@ -121,7 +163,7 @@ fn test_static_basics() { run(r#" let T = { let inner = 5; - + struct Thing { static Foo { get => inner + 5; @@ -130,18 +172,18 @@ fn test_static_basics() { } } } - + Thing }; - + assert_eq(T.Foo, 10); - + T.Foo = 3; - + assert_eq(T.Foo, 3); - + let t = T :{}; - + assert_eq(t.Foo, 3); "#) } diff --git a/tests/oop/static_tests.rs b/tests/oop/static_tests.rs index 7fb114f..12d8644 100644 --- a/tests/oop/static_tests.rs +++ b/tests/oop/static_tests.rs @@ -26,35 +26,35 @@ fn test_vector() { x = y; y = tmp; } - + mul() { x = x * m; y = y * m; } - + static new45(x) { Vec2:{x * m, x * m} } } - + let v = Vec2 :{ 1, 2 }; - + v.swap(); assert_eq(v.x, 2); - + v.mul(); assert_eq(v.y, 10); - + Vec2.m = 14; - + let v2 = Vec2.new45(5); - + assert_eq(v2.y, 70); - + assert_eq(Vec2.other, nah); print(v, v2); - + v.other = 5; assert_eq(Vec2.other, 5); "#) diff --git a/tests/oop/struct_tests.rs b/tests/oop/struct_tests.rs index cae6135..b782e39 100644 --- a/tests/oop/struct_tests.rs +++ b/tests/oop/struct_tests.rs @@ -6,17 +6,17 @@ fn test_box() { struct Box { f; } - + let b = Box :{ 5 }; assert_eq(b.f, 5); - + b.f = 10; assert_eq(b.f, 10); - + let b2 = b; - + assert_eq(b2.f, 10); - + b2.f = 20; assert_eq(b.f, 10); assert_eq(b2.f, 20); diff --git a/tests/scope_manipulation/basics.rs b/tests/scope_manipulation/basics.rs new file mode 100644 index 0000000..8480fc3 --- /dev/null +++ b/tests/scope_manipulation/basics.rs @@ -0,0 +1,79 @@ +use crate::run; + +#[test] +fn test_basics() { + run(r#" + let f1 = fn() { + let a = 5; + let b = 3; + scope() + }; + + let s = f1(); + + scope s { + let c = a + b; + assert_eq(c * a, 40); + } + + assert_eq(s.c, 8); + + assert_eq(scope s { + c = c * c; + c + 1 + }, 65); + + s.w = 1; + s.w = s.w + 1; + + assert_eq(s.w, 2); + "#) +} + +#[test] +#[should_panic(expected = "Expected `Scope` in scope modifier statement, got `Number`")] +fn test_unexpected_type_1() { + run(r#" + scope 1 {} + "#) +} + +#[test] +#[should_panic(expected = "Expected `Scope` in scope modifier expression, got `Number`")] +fn test_unexpected_type_2() { + run(r#" + scope 1 { nah }; + "#) +} + +#[test] +#[should_panic(expected = "division by zero")] +fn test_error_propagation_1() { + run(r#" + scope 1 / 0 {} + "#) +} + +#[test] +#[should_panic(expected = "division by zero")] +fn test_error_propagation_2() { + run(r#" + scope 1 / 0 { 1 }; + "#) +} + +#[test] +#[should_panic(expected = "division by zero")] +fn test_error_propagation_3() { + run(r#" + scope scope() { 1 / 0; } + "#) +} + +#[test] +#[should_panic(expected = "division by zero")] +fn test_error_propagation_4() { + run(r#" + scope scope() { 1 / 0; nah }; + "#) +} diff --git a/tests/scope_manipulation/mod.rs b/tests/scope_manipulation/mod.rs new file mode 100644 index 0000000..40db6c0 --- /dev/null +++ b/tests/scope_manipulation/mod.rs @@ -0,0 +1 @@ +mod basics;