diff --git a/.gitignore b/.gitignore index ed768f3..ca98cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target/ -Cargo.lock \ No newline at end of file +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index ab0dfb3..8c95443 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,11 @@ serde_json = "1.0.115" snailquote = "0.3.1" thiserror = "1.0.58" tree-sitter = "0.22.5" -tree-sitter-frugurt = "0.0.10" +tree-sitter-frugurt = "0.0.11" #tree-sitter-frugurt = { path = "../tree-sitter-frugurt" } #uid = "0.1.7" +macros = { path = "./macros" } +ctor = "0.2.8" [dev-dependencies] tempfile = "3.10.1" diff --git a/docs/highlighter/highlighter-info.js b/docs/highlighter/highlighter-info.js index 2375719..aee1063 100644 --- a/docs/highlighter/highlighter-info.js +++ b/docs/highlighter/highlighter-info.js @@ -1,23 +1,30 @@ window.highlighterInfo = { - queryString: `"break" @keyword -"commutative" @keyword -"continue" @keyword -"else" @keyword -"fn" @keyword -"if" @keyword -"let" @keyword -"operator" @keyword -"pub" @keyword -"return" @keyword -"struct" @keyword -"static" @keyword -"while" @keyword -"impl" @keyword + queryString: `[ + "break" + "class" + "commutative" + "continue" + "data" + "else" + "fn" + "if" + "impl" + "import" + "let" + "operator" + "pub" + "return" + "scope" + "struct" + "static" + "while" +] @keyword (number_literal) @number (string_literal) @string -(bool_literal) @bool -(comment) @comment +(bool_literal) @bool +(nah_literal) @nah +(comment) @comment (let_statement ident: (identifier) @function.declaration @@ -33,6 +40,7 @@ window.highlighterInfo = { "number": "#2AACB8", "string": "#6AAB73", "bool": "#CF8E6D", + "nah": "#CF8E6D", "comment": "#7A7E85", }, }; diff --git a/docs/highlighter/tree-sitter-frugurt.wasm b/docs/highlighter/tree-sitter-frugurt.wasm index e557487..e27876a 100644 Binary files a/docs/highlighter/tree-sitter-frugurt.wasm and b/docs/highlighter/tree-sitter-frugurt.wasm differ diff --git a/docs/highlighter/tree-sitter.wasm b/docs/highlighter/tree-sitter.wasm index 94bdca9..0ac880b 100644 Binary files a/docs/highlighter/tree-sitter.wasm and b/docs/highlighter/tree-sitter.wasm differ diff --git a/docs/src/01-getting-started/02-installation.md b/docs/src/01-getting-started/02-installation.md index ff02d34..45e63cf 100644 --- a/docs/src/01-getting-started/02-installation.md +++ b/docs/src/01-getting-started/02-installation.md @@ -10,4 +10,4 @@ You can download if from [release page](https://github.com/frugurt-lang/frugurt/ You can build Frugurt from [source code](https://github.com/frugurt-lang/frugurt) on any platform. -Use [Rust Toolchain](https://www.rust-lang.org/tools/install) to build interpreter. +Use [Rust Toolchain](https://www.rust-lang.org/tools/install) to build interpreter. diff --git a/docs/src/02-common-concepts/06-currying.md b/docs/src/02-common-concepts/06-currying.md index ebec343..3c599cc 100644 --- a/docs/src/02-common-concepts/06-currying.md +++ b/docs/src/02-common-concepts/06-currying.md @@ -1,6 +1,6 @@ # Currying -You can apply first n arguments to a function to obtain a new function that accepts the rest of the arguments. This is called currying. Curried can be curried as many times as you want. +You can apply first n arguments to a function to make a new function that accepts the rest of the arguments. This is called currying. Curried can be curried as many times as you want. ```frugurt let add = fn(a, b) { diff --git a/docs/src/03-object-oriented-programming/03-operators.md b/docs/src/03-object-oriented-programming/03-operators.md index a9cfd97..507a17a 100644 --- a/docs/src/03-object-oriented-programming/03-operators.md +++ b/docs/src/03-object-oriented-programming/03-operators.md @@ -40,13 +40,13 @@ print(a); // Vector{x=4, y=6} Operator precedences from highest to lowest: -- Custom operators have the highest precedence +- All custom operators - `**` `<>` - `*` `/` `%` - `+` `-` - `<` `>` `<=` `>=` - `==` `!=` - `&&` -- `||` - the lowest precedence +- `||` -All operators have left associativity \ No newline at end of file +All operators are left associative diff --git a/docs/src/03-object-oriented-programming/06-properties.md b/docs/src/03-object-oriented-programming/06-properties.md index ede9195..dcbe754 100644 --- a/docs/src/03-object-oriented-programming/06-properties.md +++ b/docs/src/03-object-oriented-programming/06-properties.md @@ -25,7 +25,7 @@ v.Length = 1; print(v); // Vec { x: 0.6, y: 0.8 } ``` -In this example, Vec has a "property" Length, that is freely computable from other fields. +In this example, Vec has a "property" Length that is easily computable from other fields. Like methods, properties can access fields, methods and other properties of the object. `(new_length)` can be omitted, in which case the default identifier `value` is used. Properties can be static. @@ -34,7 +34,7 @@ Also, there is no need to implement `get` and `set` every time. ```frugurt class Time { static time = 0; - + pub static Now { get { time } // this is equivalent to `get => time;` } @@ -44,5 +44,5 @@ print(Time.Now); ``` In this example, imagine game engine. -Static field time is updated by game engine every frame, and public property `Now` can be used to obtain current time +Static field time is updated by game engine every frame, and public property `Now` can be used to get current time on the user side. diff --git a/docs/src/04-scope-manipulation/01-index.md b/docs/src/04-scope-manipulation/01-index.md new file mode 100644 index 0000000..9f0717a --- /dev/null +++ b/docs/src/04-scope-manipulation/01-index.md @@ -0,0 +1,3 @@ +# Scope manipulation + +Frugurt supports explicit scope capturing and subsequent manipulation. diff --git a/docs/src/04-scope-manipulation/02-scope.md b/docs/src/04-scope-manipulation/02-scope.md new file mode 100644 index 0000000..ceb66e0 --- /dev/null +++ b/docs/src/04-scope-manipulation/02-scope.md @@ -0,0 +1,35 @@ +# Scope keyword + +Scope keyword can be used in three constructs: + +- `scope()` - captures scope in which it was evaluated +- `scope s { statements... }` - run statements in specified scope +- `scope s { statements... expression }` - run statements in specified scope and return result of expression + +Example: + +```frugurt +let f = fn () { + let a = 5; + let b = 3; + scope() +}; + +let scope_object = f(); + +print(scope_object.a); // 5 + +scope scope_object { + // this statement is executed in the same scope as the body of function f ran + // so the variables a and b are available here + print(a * b); // 15 +} + +scope_object.a = 10; // old variables can be re-assigned +scope_object.c = 20; // new variables can be declared + +print(scope scope_object { + let r = a + c; + r * b +}); // 90 +``` diff --git a/docs/src/04-scope-manipulation/03-imports.md b/docs/src/04-scope-manipulation/03-imports.md new file mode 100644 index 0000000..465ff1f --- /dev/null +++ b/docs/src/04-scope-manipulation/03-imports.md @@ -0,0 +1,29 @@ +# Imports + +Other files can be imported into your code by using the `import` expression. +Import expression returns the same scope object, which was mentioned in the previous chapter. + +Example: + +`main.fru` +```frugurt +let foo = import "foo.fru"; + +print(foo.f(1, 2)); // 3 + +// this is as badass as extremely stupid +scope foo { + let wow = 5; + + print(omg()); // 5 +} +``` + +`foo.fru` +```frugurt +let f = fn(x, y) { + x + y +}; + +let omg = fn() { wow }; +``` diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 73fd5a4..bac3f87 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -17,3 +17,6 @@ - [Methods](./03-object-oriented-programming/04-methods.md) - [Statics](./03-object-oriented-programming/05-statics.md) - [Properties](./03-object-oriented-programming/06-properties.md) +- [Scope manipulation](./04-scope-manipulation/01-index.md) + - [Scope keyword](./04-scope-manipulation/02-scope.md) + - [Imports](./04-scope-manipulation/03-imports.md) \ No newline at end of file diff --git a/macros/.gitignore b/macros/.gitignore new file mode 100644 index 0000000..ca98cd9 --- /dev/null +++ b/macros/.gitignore @@ -0,0 +1,2 @@ +/target/ +Cargo.lock diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..d3e27e1 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "2.0.66" +quote = "1.0.36" diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..762289b --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,27 @@ +use proc_macro::TokenStream; +use std::hash::{DefaultHasher, Hash, Hasher}; + +use quote::quote; +use syn::LitStr; + +#[proc_macro] +pub fn static_ident(input: TokenStream) -> TokenStream { + let ast: LitStr = syn::parse(input).unwrap(); + + let ident = ast.value(); + + let mut hasher = DefaultHasher::new(); + ident.hash(&mut hasher); + let hashed_ident = hasher.finish(); + + quote! { + { + #[ctor::ctor] + fn ident_ctor() { + Identifier::new(#ident); + } + Identifier::new_unchecked(#hashed_ident) + } + } + .into() +} diff --git a/obsidian/.gitignore b/obsidian/.gitignore new file mode 100644 index 0000000..0c5b2be --- /dev/null +++ b/obsidian/.gitignore @@ -0,0 +1 @@ +/.obsidian diff --git a/obsidian/FIXME.canvas b/obsidian/FIXME.canvas new file mode 100644 index 0000000..5a85a2e --- /dev/null +++ b/obsidian/FIXME.canvas @@ -0,0 +1,12 @@ +{ + "nodes":[ + {"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":"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 new file mode 100644 index 0000000..da854cb --- /dev/null +++ b/obsidian/TODO.canvas @@ -0,0 +1,25 @@ +{ + "nodes":[ + {"id":"0c274527d795ce38","type":"group","x":-80,"y":600,"width":400,"height":400,"label":"OPTIMIZATIONS"}, + {"id":"0e8289b07d6ca556","type":"group","x":-600,"y":600,"width":400,"height":380,"label":"CI/CD"}, + {"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":"1c18b7117da43e6e","type":"text","text":"\"evil\" features","x":-460,"y":-180,"width":250,"height":67,"color":"4"}, + {"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,"color":"4"}, + {"id":"aa2bea5c494cbddd","type":"text","text":"add traceback for errors","x":-160,"y":252,"width":250,"height":60}, + {"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}, + {"id":"97a68fbd25b52ede","type":"text","text":"destructuring let statements","x":800,"y":160,"width":250,"height":60}, + {"id":"feb6594a9d261a54","type":"text","text":"modules and imports\n- [ ] tests\n- [x] import as expression","x":260,"y":-180,"width":250,"height":120,"color":"3"}, + {"id":"18df0d00841f02bd","type":"text","text":"collections\n- [ ] list\n- [ ] set\n- [ ] map\n- [ ] tuple?","x":-80,"y":0,"width":250,"height":182,"color":"3"}, + {"id":"b9c4b54397d2bf2d","type":"text","text":"macro for computing hash of ident in compile time","x":-40,"y":660,"width":250,"height":87,"color":"4"}, + {"id":"b9e932828d9a3c13","type":"text","text":"make cd for windows and linux releases","x":-580,"y":640,"width":250,"height":60} + ], + "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":"10698732105d19f9","fromNode":"5d4e5bd937436926","fromSide":"right","toNode":"070a7b543247f7a2","toSide":"left"} + ] +} \ No newline at end of file diff --git a/obsidian/TOTHINK.canvas b/obsidian/TOTHINK.canvas new file mode 100644 index 0000000..517614c --- /dev/null +++ b/obsidian/TOTHINK.canvas @@ -0,0 +1,7 @@ +{ + "nodes":[ + {"id":"1ac37b84ff817ecc","x":-733,"y":-605,"width":473,"height":165,"type":"text","text":"Issue 1:\nIf curried function mutates one of it's arguments, should subsequent calls receive mutated value or should it be cloned on call?"}, + {"id":"901340d58aa31311","type":"text","text":"introduce new wrapper extensions like `wrap_ok`, `wrap_err`, `wrap_value`","x":-720,"y":-220,"width":250,"height":130} + ], + "edges":[] +} \ 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/ast_helpers.rs b/src/interpreter/ast_helpers.rs new file mode 100644 index 0000000..6a3490c --- /dev/null +++ b/src/interpreter/ast_helpers.rs @@ -0,0 +1,20 @@ +use std::rc::Rc; + +use crate::interpreter::{ + expression::FruExpression, identifier::Identifier, statement::FruStatement, + value::function::FormalParameters, +}; + +#[derive(Debug, Clone)] +pub struct RawStaticField { + pub ident: Identifier, + pub value: Option>, +} + +#[derive(Debug, Clone)] +pub struct RawMethod { + pub is_static: bool, + pub ident: Identifier, + pub parameters: FormalParameters, + pub body: Rc, +} diff --git a/src/interpreter/builtins/operators.rs b/src/interpreter/builtins/operators.rs index 0cad6cc..6852b5a 100644 --- a/src/interpreter/builtins/operators.rs +++ b/src/interpreter/builtins/operators.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use crate::interpreter::{ error::FruError, - identifier::{Identifier as Id, OperatorIdentifier as OpId}, - value::fru_value::FruValue, - value::operator::AnyOperator, + identifier::{id, OperatorIdentifier}, + value::{fru_value::FruValue, operator::AnyOperator}, }; macro_rules! builtin_operator { @@ -20,11 +19,11 @@ macro_rules! builtin_operator { } macro_rules! operator_group { - ($ident1:expr, $ident2:expr, [$(($op:ident, $fn_name:ident)),*]) => { + ($ident1:ident, $ident2:ident, [$(($op:ident, $fn_name:ident)),*]) => { [ $( ( - OpId::new(Id::$op(), $ident1, $ident2), + OperatorIdentifier::new(id::$op, id::$ident1, id::$ident2), AnyOperator::BuiltinOperator($fn_name), ) ),* @@ -32,44 +31,55 @@ 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) - ])); +pub fn builtin_operators() -> HashMap { + let mut res = HashMap::new(); + + res.extend(operator_group!( + NUMBER, + NUMBER, + [ + (PLUS, num_plus_num), + (MINUS, num_minus_num), + (MULTIPLY, num_mul_num), + (DIVIDE, num_div_num), + (MOD, num_mod_num), + (POW, num_pow_num), + (LESS, num_less_num), + (LESS_EQ, num_less_eq_num), + (GREATER, num_greater_num), + (GREATER_EQ, num_greater_eq_num), + (EQ, num_eq_num), + (NOT_EQ, num_not_eq_num) + ] + )); + + res.extend(operator_group!( + BOOL, + BOOL, + [(AND, bool_and_bool), (OR, bool_or_bool)] + )); + + res.extend(operator_group!( + STRING, + STRING, + [ + (COMBINE, string_concat), + (LESS, string_less_string), + (LESS_EQ, string_less_eq_string), + (GREATER, string_greater_string), + (GREATER_EQ, string_greater_eq_string), + (EQ, string_eq_string), + (NOT_EQ, string_not_eq_string) + ] + )); res.extend([ ( - OpId::new(Id::for_multiply(), Id::for_string(), Id::for_number()), + OperatorIdentifier::new(id::MULTIPLY, id::STRING, id::NUMBER), AnyOperator::BuiltinOperator(string_mul_num), ), ( - OpId::new(Id::for_multiply(), Id::for_number(), Id::for_string()), + OperatorIdentifier::new(id::MULTIPLY, id::NUMBER, id::STRING), AnyOperator::BuiltinOperator(num_mul_string), ), ]); @@ -128,6 +138,7 @@ builtin_operator!(string_greater_string, String, String, Bool, >); builtin_operator!(string_greater_eq_string, String, String, Bool, >=); builtin_operator!(string_eq_string, String, String, Bool, ==); builtin_operator!(string_not_eq_string, String, String, Bool, !=); + fn string_concat(left: FruValue, right: FruValue) -> Result { if let (FruValue::String(l), FruValue::String(r)) = (left, right) { return Ok(FruValue::String(l + &*r)); diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index e911f6f..86875d2 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -1,4 +1,3 @@ -// TODO: make use of this use thiserror::Error; use crate::interpreter::value::function::ArgumentError; diff --git a/src/interpreter/expression.rs b/src/interpreter/expression.rs index caa2af9..5907ab2 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 { + parameters: 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..8ed967a 100644 --- a/src/interpreter/identifier.rs +++ b/src/interpreter/identifier.rs @@ -1,10 +1,7 @@ use std::{ collections::HashMap, - fmt::Debug, - fmt::Display, - hash::DefaultHasher, - hash::Hash, - hash::Hasher, + fmt::{Debug, Display}, + hash::{DefaultHasher, Hash, Hasher}, sync::Mutex, }; @@ -26,18 +23,10 @@ pub struct OperatorIdentifier { right: Identifier, } -pub fn reset_poison() { - if BACKWARDS_MAP.lock().is_err() { - BACKWARDS_MAP.clear_poison() - } -} - impl Identifier { pub fn new(ident: &str) -> Self { let mut hasher = DefaultHasher::new(); - ident.hash(&mut hasher); - let hashed_ident = hasher.finish(); BACKWARDS_MAP @@ -48,6 +37,10 @@ impl Identifier { Self { hashed_ident } } + + pub const fn new_unchecked(hashed_ident: u64) -> Self { + Self { hashed_ident } + } } impl OperatorIdentifier { @@ -78,95 +71,36 @@ impl Debug for OperatorIdentifier { } } -impl Identifier { - // builtin types - pub fn for_nah() -> Self { - Self::new("Nah") - } - - pub fn for_number() -> Self { - Self::new("Number") - } - - pub fn for_bool() -> Self { - Self::new("Bool") - } - - pub fn for_string() -> Self { - Self::new("String") - } - - pub fn for_function() -> Self { - Self::new("Function") - } - - pub fn for_type() -> Self { - Self::new("Type") - } - - pub fn for_native_object() -> Self { - Self::new("NativeObject") - } - - // builtin operators - pub fn for_plus() -> Self { - Self::new("+") - } - - pub fn for_minus() -> Self { - Self::new("-") - } - - pub fn for_multiply() -> Self { - Self::new("*") - } - - pub fn for_divide() -> Self { - Self::new("/") - } - - pub fn for_mod() -> Self { - Self::new("%") - } - - pub fn for_pow() -> Self { - Self::new("**") - } - - pub fn for_and() -> Self { - Self::new("&&") - } - - pub fn for_or() -> Self { - Self::new("||") - } - - pub fn for_combine() -> Self { - Self::new("<>") - } - - // builtin operators (comparison) - pub fn for_less() -> Self { - Self::new("<") - } - - pub fn for_less_eq() -> Self { - Self::new("<=") - } - - pub fn for_greater() -> Self { - Self::new(">") - } - - pub fn for_greater_eq() -> Self { - Self::new(">=") - } - - pub fn for_eq() -> Self { - Self::new("==") - } - - pub fn for_not_eq() -> Self { - Self::new("!=") - } +pub mod id { + use macros::static_ident; + + use crate::interpreter::identifier::Identifier; + + // types + pub const NAH: Identifier = static_ident!("Nah"); + pub const NUMBER: Identifier = static_ident!("Number"); + pub const BOOL: Identifier = static_ident!("Bool"); + pub const STRING: Identifier = static_ident!("String"); + pub const FUNCTION: Identifier = static_ident!("Function"); + pub const TYPE: Identifier = static_ident!("Type"); + pub const NATIVE_OBJECT: Identifier = static_ident!("NativeObject"); + + // arithmetic + pub const PLUS: Identifier = static_ident!("+"); + pub const MINUS: Identifier = static_ident!("-"); + pub const MULTIPLY: Identifier = static_ident!("*"); + pub const DIVIDE: Identifier = static_ident!("/"); + pub const MOD: Identifier = static_ident!("%"); + pub const POW: Identifier = static_ident!("**"); + pub const AND: Identifier = static_ident!("&&"); + pub const OR: Identifier = static_ident!("||"); + pub const COMBINE: Identifier = static_ident!("<>"); + + // comparison + pub const LESS: Identifier = static_ident!("<"); + pub const LESS_EQ: Identifier = static_ident!("<="); + pub const GREATER: Identifier = static_ident!(">"); + pub const GREATER_EQ: Identifier = static_ident!(">="); + pub const EQ: Identifier = static_ident!("=="); + pub const NOT_EQ: Identifier = static_ident!("!="); } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index b47ad0a..78fe709 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,14 +1,12 @@ -mod builtins; -mod control; -mod error; -mod expression; -mod helpers; -mod identifier; -mod runner; -mod scope; -mod statement; -mod tree_sitter_parser; -mod value; - -pub use identifier::reset_poison; -pub use runner::execute_file; +mod ast_helpers; +pub mod builtins; +pub mod control; +pub mod error; +pub mod expression; +pub mod helpers; +pub mod identifier; +pub mod runner; +pub mod scope; +pub mod statement; +pub mod tree_sitter_parser; +pub mod value; diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index cc682b0..b0d93da 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -1,10 +1,15 @@ -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}")))?; + execute_source_code(source_code) +} + +pub fn execute_source_code(source_code: String) -> Result, FruError> { let ast = match tree_sitter_parser::parse(source_code) { Ok(ast) => ast, Err(err) => return Err(FruError::new(err.to_string())), 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 c1b97dd..ee846fa 100644 --- a/src/interpreter/statement.rs +++ b/src/interpreter/statement.rs @@ -1,23 +1,30 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::interpreter::{ + ast_helpers::{RawMethod, RawStaticField}, control::Control, expression::FruExpression, identifier::{Identifier, OperatorIdentifier}, scope::Scope, value::fru_type::{FruField, FruType, Property, TypeType}, value::fru_value::FruValue, - value::function::{FormalParameters, FruFunction}, + value::function::FruFunction, value::operator::AnyOperator, }; - -pub type RawMethods = Vec<(bool, Identifier, FormalParameters, Rc)>; +use crate::stdlib::scope::fru_scope::extract_scope_from_value; #[derive(Debug, Clone)] pub enum FruStatement { + SourceCode { + body: Vec, + }, Block { body: Vec, }, + ScopeModifier { + what: Box, + body: Vec, + }, Expression { value: Box, }, @@ -61,16 +68,22 @@ pub enum FruStatement { type_type: TypeType, ident: Identifier, fields: Vec, - static_fields: Vec<(FruField, Option>)>, + static_fields: Vec, properties: HashMap, static_properties: HashMap, - methods: RawMethods, + methods: Vec, }, } impl FruStatement { pub fn execute(&self, scope: Rc) -> Result<(), Control> { match self { + FruStatement::SourceCode { body } => { + for statement in body { + statement.execute(scope.clone())?; + } + } + FruStatement::Block { body } => { let new_scope = Scope::new_with_parent(scope.clone()); @@ -79,6 +92,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())?; } @@ -204,28 +234,28 @@ impl FruStatement { let mut methods_ = HashMap::new(); let mut static_methods_ = HashMap::new(); - for (is_static, ident, arg_list, body) in methods { - let mt = FruFunction { - argument_idents: arg_list.clone(), - body: body.clone(), + for method in methods { + let function = FruFunction { + parameters: method.parameters.clone(), + body: method.body.clone(), scope: scope.clone(), }; - if *is_static { - static_methods_.insert(*ident, mt); + if method.is_static { + static_methods_.insert(method.ident, function); } else { - methods_.insert(*ident, mt); + methods_.insert(method.ident, function); } } let mut static_fields_evaluated = HashMap::new(); - for (field, value) in static_fields { - let value = if let Some(v) = value { + for static_field in static_fields { + let value = if let Some(v) = &static_field.value { v.evaluate(scope.clone())? } else { FruValue::Nah }; - static_fields_evaluated.insert(field.ident, value); + static_fields_evaluated.insert(static_field.ident, value); } scope.let_variable( diff --git a/src/interpreter/tree_sitter_parser.rs b/src/interpreter/tree_sitter_parser.rs index 410a9c5..d669ea5 100644 --- a/src/interpreter/tree_sitter_parser.rs +++ b/src/interpreter/tree_sitter_parser.rs @@ -1,19 +1,22 @@ use std::{boxed::Box, collections::hash_map::Entry, collections::HashMap, rc::Rc, str::Utf8Error}; +use macros::static_ident; use snailquote::unescape; use thiserror::Error; use tree_sitter::{Node, Parser, Range}; use tree_sitter_frugurt; use crate::interpreter::{ + ast_helpers::{RawMethod, RawStaticField}, expression::FruExpression, helpers::WrappingExtension, identifier::Identifier, - statement::{FruStatement, RawMethods}, - value::fru_type::FruField, - value::fru_type::{Property, TypeType}, - value::fru_value::FruValue, - value::function::{ArgumentList, FormalParameters}, + statement::FruStatement, + value::{ + fru_type::{FruField, Property, TypeType}, + fru_value::FruValue, + function::{ArgumentList, FormalParameters}, + }, }; #[derive(Error, Debug)] @@ -78,7 +81,7 @@ pub enum ParseError { enum TypeMember { NormalField(FruField), - StaticField((FruField, Option>)), + StaticField(RawStaticField), Property(Property), StaticProperty(Property), } @@ -103,11 +106,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 +120,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 +159,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 +215,53 @@ fn search_for_errors(ast: Node) -> ParseError { fn parse_statement(ast: NodeWrapper) -> Result { let result_statement = match ast.grammar_name() { - "source_file" => { - FruStatement::Block { - 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 +320,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); } - } + }, } } @@ -392,106 +374,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 { @@ -519,11 +485,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 { @@ -540,12 +504,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), + }), } } @@ -563,16 +525,17 @@ fn parse_field(ast: NodeWrapper) -> Result { }); } - let res = FruField { - is_public, - ident, - type_ident, - }; - Ok(if is_static { - TypeMember::StaticField((res, value.map(Box::new))) + TypeMember::StaticField(RawStaticField { + ident, + value: value.map(Box::new), + }) } else { - TypeMember::NormalField(res) + TypeMember::NormalField(FruField { + is_public, + ident, + type_ident, + }) }) } @@ -582,7 +545,7 @@ fn parse_property(ast: NodeWrapper) -> Result { Set((Identifier, Rc), NodeWrapper<'a>), } - // TODO: add static and public modifiers + // TODO: add public modifier let ident = ast.get_child_ident("ident")?; let is_static = ast.get_child("static").is_ok(); @@ -596,7 +559,7 @@ fn parse_property(ast: NodeWrapper) -> Result { Item::Set( ( - ident.map_or_else(|| Identifier::new("value"), |x| x.0), + ident.map_or_else(|| static_ident!("value"), |x| x.0), x.parse_child_statement("body")?.wrap_rc(), ), x, @@ -648,22 +611,17 @@ fn parse_property(ast: NodeWrapper) -> Result { }) } -fn parse_impl(ast: NodeWrapper) -> Result { +fn parse_impl(ast: NodeWrapper) -> Result, ParseError> { ast.parse_children("methods", parse_method) } -fn parse_method( - ast: NodeWrapper, -) -> Result<(bool, Identifier, FormalParameters, Rc), ParseError> { - let is_static = ast.get_child("static").is_ok(); - - let ident = ast.get_child_ident("ident")?; - - let args = ast.parse_child("parameters", parse_formal_parameters)?; - - let body = ast.parse_child("body", parse_function_body)?.wrap_rc(); - - Ok((is_static, ident, args, body)) +fn parse_method(ast: NodeWrapper) -> Result { + Ok(RawMethod { + is_static: ast.get_child("static").is_ok(), + ident: ast.get_child_ident("ident")?, + parameters: ast.parse_child("parameters", parse_formal_parameters)?, + body: ast.parse_child("body", parse_function_body)?.wrap_rc(), + }) } fn parse_formal_parameters(ast: NodeWrapper) -> Result { @@ -696,18 +654,15 @@ 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), + }), } } @@ -716,9 +671,8 @@ fn parse_argument_list(ast: NodeWrapper) -> Result { let mut was_named = false; - for i in 0..args.len() { - // FIXME - if args[i].0.is_some() { + for (i, (name, _)) in args.iter().enumerate() { + if name.is_some() { was_named = true; } else if was_named { return Err(ParseError::Error { @@ -759,18 +713,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..090c116 100644 --- a/src/interpreter/value/fru_object.rs +++ b/src/interpreter/value/fru_object.rs @@ -63,13 +63,13 @@ impl FruObject { } if let Some(FruFunction { - argument_idents, + parameters: argument_idents, body, .. }) = self.get_type().get_method(ident) { return Ok(FruFunction { - argument_idents, + parameters: argument_idents, body, scope: Scope::new_with_object(self.clone()), } @@ -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_type.rs b/src/interpreter/value/fru_type.rs index 8a57fd8..c2241ce 100644 --- a/src/interpreter/value/fru_type.rs +++ b/src/interpreter/value/fru_type.rs @@ -132,7 +132,7 @@ impl FruType { if let Some(static_method) = self.internal.static_methods.get(&ident) { return Ok(FruFunction { - argument_idents: static_method.argument_idents.clone(), + parameters: static_method.parameters.clone(), body: static_method.body.clone(), scope: Scope::new_with_type(self.clone()), } diff --git a/src/interpreter/value/fru_value.rs b/src/interpreter/value/fru_value.rs index c67aa68..2d5f8d2 100644 --- a/src/interpreter/value/fru_value.rs +++ b/src/interpreter/value/fru_value.rs @@ -2,11 +2,14 @@ use std::{fmt::Debug, rc::Rc}; use crate::interpreter::{ error::FruError, + identifier::id, identifier::Identifier, - value::fru_object::FruObject, - value::fru_type::FruType, - value::function::{AnyFunction, CurriedFunction, EvaluatedArgumentList, FruFunction}, - value::native::object::NativeObject, + value::{ + fru_object::FruObject, + fru_type::FruType, + function::{AnyFunction, CurriedFunction, EvaluatedArgumentList, FruFunction}, + native::object::NativeObject, + }, }; pub type TFnBuiltin = fn(EvaluatedArgumentList) -> Result; @@ -32,12 +35,12 @@ pub enum FruValue { impl FruValue { pub fn get_type_identifier(&self) -> Identifier { match self { - FruValue::Nah => Identifier::for_nah(), - FruValue::Number(_) => Identifier::for_number(), - FruValue::Bool(_) => Identifier::for_bool(), - FruValue::String(_) => Identifier::for_string(), - FruValue::Function(_) => Identifier::for_function(), - FruValue::Type(_) => Identifier::for_type(), + FruValue::Nah => id::NAH, + FruValue::Number(_) => id::NUMBER, + FruValue::Bool(_) => id::BOOL, + FruValue::String(_) => id::STRING, + FruValue::Function(_) => id::FUNCTION, + FruValue::Type(_) => id::TYPE, FruValue::Object(obj) => obj.get_type().get_ident(), FruValue::NativeObject(obj) => obj.get_type_identifier(), } @@ -54,11 +57,9 @@ impl FruValue { pub fn curry_call(&self, args: EvaluatedArgumentList) -> Result { match self { FruValue::Function(func) => { - // TODO: test compatibility - match func { AnyFunction::CurriedFunction(func) => { - let mut new_args = func.saved_args.clone(); // TODO: fru_clone()? + let mut new_args = func.saved_args.clone(); // TODO: obsidian Issue 1 new_args.args.extend(args.args); Ok(FruValue::Function(AnyFunction::CurriedFunction(Rc::new( @@ -69,14 +70,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 +91,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 +106,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 +121,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/function.rs b/src/interpreter/value/function.rs index 8c238a2..40a232d 100644 --- a/src/interpreter/value/function.rs +++ b/src/interpreter/value/function.rs @@ -33,7 +33,7 @@ pub enum AnyFunction { #[derive(Clone)] pub struct FruFunction { - pub argument_idents: FormalParameters, + pub parameters: FormalParameters, pub body: Rc, pub scope: Rc, } @@ -77,7 +77,7 @@ impl FruFunction { fn call(&self, args: EvaluatedArgumentList) -> Result { let new_scope = Scope::new_with_parent(self.scope.clone()); - self.argument_idents.apply(args, new_scope.clone())?; + self.parameters.apply(args, new_scope.clone())?; returned_unit(self.body.execute(new_scope)) } diff --git a/src/interpreter/value/native/object.rs b/src/interpreter/value/native/object.rs index 451ee30..fa83c67 100644 --- a/src/interpreter/value/native/object.rs +++ b/src/interpreter/value/native/object.rs @@ -1,15 +1,17 @@ -use std::rc::Rc; +use std::{any::Any, rc::Rc}; use crate::interpreter::{ error::FruError, + identifier::id, identifier::Identifier, - value::fru_value::FruValue, - value::function::EvaluatedArgumentList, + value::{fru_value::FruValue, function::EvaluatedArgumentList}, }; pub trait INativeObject { + fn as_any(&self) -> &dyn Any; + fn get_type_identifier(&self) -> Identifier { - Identifier::for_native_object() + id::NATIVE_OBJECT } fn call(&self, _args: EvaluatedArgumentList) -> Result { @@ -44,17 +46,19 @@ pub trait INativeObject { )) } - fn fru_clone(&self) -> FruValue { - panic!(); - } + fn fru_clone(self: Rc) -> Rc; } #[derive(Clone)] pub struct NativeObject { - pub internal: Rc, + internal: Rc, } impl NativeObject { + pub fn new(internal: Rc) -> Self { + Self { internal } + } + pub fn get_type_identifier(&self) -> Identifier { self.internal.get_type_identifier() } @@ -80,6 +84,12 @@ impl NativeObject { } pub fn fru_clone(&self) -> FruValue { - self.internal.fru_clone() + FruValue::NativeObject(NativeObject { + internal: self.internal.clone().fru_clone(), + }) + } + + pub fn downcast(&self) -> Option<&T> { + self.internal.as_any().downcast_ref::() } } diff --git a/src/main.rs b/src/main.rs index 031c647..ea224d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,10 @@ -#![feature(iterator_try_collect)] - use std::{path::PathBuf, time::Instant}; +use crate::interpreter::runner::execute_file; use clap::Parser; -use crate::interpreter::execute_file; - mod interpreter; +mod stdlib; #[derive(Parser, Debug)] struct Args { diff --git a/src/stdlib/mod.rs b/src/stdlib/mod.rs new file mode 100644 index 0000000..88e6afc --- /dev/null +++ b/src/stdlib/mod.rs @@ -0,0 +1 @@ +pub mod scope; diff --git a/src/stdlib/scope/fru_scope.rs b/src/stdlib/scope/fru_scope.rs new file mode 100644 index 0000000..b809d51 --- /dev/null +++ b/src/stdlib/scope/fru_scope.rs @@ -0,0 +1,52 @@ +use std::{any::Any, rc::Rc}; + +use macros::static_ident; + +use crate::interpreter::{ + error::FruError, + identifier::Identifier, + scope::Scope, + value::fru_value::FruValue, + value::native::object::{INativeObject, NativeObject}, +}; + +pub struct FruScope { + scope: Rc, +} + +impl FruScope { + pub fn new_value(scope: Rc) -> FruValue { + FruValue::NativeObject(NativeObject::new(Rc::new(Self { scope }))) + } +} + +impl INativeObject for FruScope { + fn as_any(&self) -> &dyn Any { + self + } + + fn get_type_identifier(&self) -> Identifier { + static_ident!("Scope") + } + + fn get_prop(&self, ident: Identifier) -> Result { + self.scope.get_variable(ident) + } + + fn set_prop(&self, ident: Identifier, value: FruValue) -> Result<(), FruError> { + self.scope.let_set_variable(ident, value); + Ok(()) + } + + fn fru_clone(self: Rc) -> Rc { + self + } +} + +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/src/stdlib/scope/mod.rs b/src/stdlib/scope/mod.rs new file mode 100644 index 0000000..7ac7f6f --- /dev/null +++ b/src/stdlib/scope/mod.rs @@ -0,0 +1 @@ +pub mod fru_scope; 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 cf01904..66a2234 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,29 +1,20 @@ -#![feature(iterator_try_collect)] - -use std::io::Write; - -use tempfile::NamedTempFile; - -use crate::interpreter::{execute_file, reset_poison}; +use crate::interpreter::runner::execute_source_code; #[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) { - let mut file = NamedTempFile::new().expect("failed to create temporary file"); - - file.write_all(code.as_bytes()).expect("failed to write to temporary file"); - file.flush().unwrap(); - - reset_poison(); - - if let Err(err) = execute_file(file.path()) { + if let Err(err) = execute_source_code(code.to_owned()) { panic!("{}", err) } } 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..f0599af 100644 --- a/tests/oop/general_tests.rs +++ b/tests/oop/general_tests.rs @@ -35,30 +35,30 @@ fn test_operators() { struct Vec2 { 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..9ae1021 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,27 +163,27 @@ fn test_static_basics() { run(r#" let T = { let inner = 5; - + struct Thing { static Foo { get => inner + 5; set(val) { - inner = val - 5; + inner = val - 5; } } } - + 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; diff --git a/todo.md b/todo.md deleted file mode 100644 index de7284d..0000000 --- a/todo.md +++ /dev/null @@ -1,33 +0,0 @@ -# Possible bugs - -- `fru_clone()` horror - -# TODO - -- add `overload` keyword for functions and methods -- add variadic parameters -- add trait polymorphism for native types - - move FruValue::Object and FruValue::Type to FruValue:NativeObject - - add collections - - list - - set - - map - - tuple? -- implement scope manipulation features - - implement modules and imports -- implement "evil" features -- make cd for windows and linux releases -- add derivation and implicit derivation, and make them overridable (the main reason is equality of objects) -- add computed properties - -# Needed fixes - -- implement Display for FruValue and fix for FruObject -- remove or fix BACKWARDS_MAP in Identifier (fails testing with some probability) !! probably already fixed -- introduce a new error type for "method/field not found" -- automate wasm module for book -- make set expression, not statement - -# Possible improvements - -- introduce new wrapper extensions like `wrap_ok`, `wrap_err`, `wrap_value`? Needs lots of thinking.