From 19953cf07f306e03f4c072b2663fa187b9c5bfa5 Mon Sep 17 00:00:00 2001 From: Mateo Rodriguez Date: Mon, 16 Oct 2023 11:47:18 -0700 Subject: [PATCH 1/7] `nil` value and optional types groundwork --- bytecode/src/variables/primitive.rs | 2 +- .../src/ast/assignment/assignment_type.rs | 2 +- compiler/src/ast/math_expr.rs | 8 ++++++ compiler/src/ast/return.rs | 2 +- compiler/src/ast/type.rs | 28 +++++++++++++++++-- compiler/src/grammar.pest | 6 ++-- compiler/src/tests.rs | 1 + compiler/src/tests/optionals.rs | 17 +++++++++++ examples/main/optionals/some_none.ms | 14 ++++++++++ 9 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 compiler/src/tests/optionals.rs create mode 100644 examples/main/optionals/some_none.ms diff --git a/bytecode/src/variables/primitive.rs b/bytecode/src/variables/primitive.rs index dceb4d9..439681b 100644 --- a/bytecode/src/variables/primitive.rs +++ b/bytecode/src/variables/primitive.rs @@ -349,7 +349,7 @@ impl Primitive { } Object(o) => write!(f, "{}", o.borrow()), Optional(Some(primitive)) => write!(f, "{primitive}"), - Optional(None) => write!(f, "none"), + Optional(None) => write!(f, "nil"), } } } diff --git a/compiler/src/ast/assignment/assignment_type.rs b/compiler/src/ast/assignment/assignment_type.rs index 36f7f8f..db4fd5a 100644 --- a/compiler/src/ast/assignment/assignment_type.rs +++ b/compiler/src/ast/assignment/assignment_type.rs @@ -37,7 +37,7 @@ impl Parser { if !ty .as_ref() .get_type_recursively() - .eq_complex(assignment_ty.get_type_recursively(), self_type.as_ref()) + .eq_complex(assignment_ty.get_type_recursively(), self_type.as_ref(), false) { let hint = ty.get_error_hint_between_types(assignment_ty).map_or_else( || Cow::Borrowed(""), diff --git a/compiler/src/ast/math_expr.rs b/compiler/src/ast/math_expr.rs index 1260110..53bc412 100644 --- a/compiler/src/ast/math_expr.rs +++ b/compiler/src/ast/math_expr.rs @@ -135,6 +135,7 @@ pub(crate) enum CallableContents { #[derive(Debug)] pub(crate) enum Expr { + Nil, Value(Value), ReferenceToSelf, ReferenceToConstructor(ClassType), @@ -228,6 +229,7 @@ impl CompileTimeEvaluate for Expr { } Self::ReferenceToSelf => Ok(ConstexprEvaluation::Impossible), Self::ReferenceToConstructor(..) => Ok(ConstexprEvaluation::Impossible), + Self::Nil => Ok(ConstexprEvaluation::Impossible), Self::Callable { .. } => Ok(ConstexprEvaluation::Impossible), Self::Index { .. } => Ok(ConstexprEvaluation::Impossible), Self::DotLookup { .. } => Ok(ConstexprEvaluation::Impossible), @@ -273,6 +275,7 @@ impl IntoType for Expr { } Expr::Index { index, .. } => index.for_type(), Expr::DotLookup { expected_type, .. } => Ok(expected_type.to_owned()), + Expr::Nil => Ok(TypeLayout::Optional(None)) } } } @@ -307,6 +310,7 @@ impl Dependencies for Expr { E::DotLookup { lhs, .. } => lhs.net_dependencies(), E::ReferenceToSelf => vec![], E::ReferenceToConstructor(..) => vec![], + E::Nil => vec![], } } } @@ -433,6 +437,9 @@ fn compile_depth( result.append(&mut dot_chain.compile(state)?); Ok(result) } + Expr::Nil => { + Ok(vec![instruction!(reserve_primitive)]) + } } } @@ -458,6 +465,7 @@ fn parse_expr( Expr::Value(Value::Number(number)) } + Rule::nil => Expr::Nil, Rule::string => { let ast_string = Parser::string(Node::new_with_user_data(primary, user_data.clone())).to_err_vec()?; Expr::Value(Value::String(ast_string)) diff --git a/compiler/src/ast/return.rs b/compiler/src/ast/return.rs index 48412b4..0332ace 100644 --- a/compiler/src/ast/return.rs +++ b/compiler/src/ast/return.rs @@ -93,7 +93,7 @@ impl Parser { let class_type = input.user_data().get_type_of_executing_class(); - if !expected_return_type.eq_complex(&Cow::Borrowed(supplied_type), class_type) { + if !expected_return_type.eq_complex(&Cow::Borrowed(supplied_type), class_type, true) { return Err(vec![new_err( input.as_span(), &input.user_data().get_source_file_name(), diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index e99023a..57a862b 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -164,6 +164,7 @@ pub(crate) enum TypeLayout { Function(FunctionType), /// metadata wrapper around a [TypeLayout] CallbackVariable(Box), + Optional(Option>>), Native(NativeType), List(ListType), ValidIndexes(ListBound, ListBound), @@ -182,6 +183,8 @@ impl Display for TypeLayout { Self::ValidIndexes(lower, upper) => write!(f, "B({lower}..{upper})"), Self::Class(class_type) => write!(f, "{}", class_type.name()), Self::ClassSelf => write!(f, "Self"), + Self::Optional(Some(ty)) => write!(f, "{ty}?"), + Self::Optional(None) => write!(f, "!"), Self::Void => write!(f, "void"), } } @@ -214,6 +217,7 @@ impl TypeLayout { (Native(BigInt), Native(Int)) => "try adding 'B' before a number to convert it to a bigint, eg. `99` -> `B99` or `0x6` -> `B0x6`", (Native(Int), Native(Float)) => "cast this floating point value to an integer", (Native(Float), Native(Int | BigInt | Byte)) => "cast this integer type to a floating point", + (x, Optional(Some(y))) if x == y.as_ref().as_ref() => "unwrap this optional to use its value", (Native(Str(..)), _) => "call `.to_str()` on this item to convert it into a str", (Function(..), Function(..)) => "check the function type that you provided", _ => return None, @@ -383,6 +387,7 @@ impl TypeLayout { &self, rhs: &Self, executing_class: Option>, + lhs_allow_optional_unwrap: bool, ) -> bool { if self == rhs { return true; @@ -393,6 +398,15 @@ impl TypeLayout { | (TypeLayout::Class(other), Self::ClassSelf, Some(executing_class)) => { executing_class.deref().eq(other) } + (Self::Optional(None), Self::Optional(Some(_)), _) | (Self::Optional(Some(_)), Self::Optional(None), _) => { + true + } + (Self::Optional(Some(x)), y, _) => { + x.as_ref().as_ref() == y + } + (y, Self::Optional(Some(x)), _) if lhs_allow_optional_unwrap => { + x.as_ref().as_ref() == y + } _ => false, } } @@ -729,7 +743,13 @@ impl Parser { } pub fn r#type(input: Node) -> Result> { - let ty = input.children().single().unwrap(); + let mut children = input.children(); + + let ty = children.next().unwrap(); + + // let ty = input.children().single().unwrap(); + + let optional_modifier = children.next(); let span = ty.as_span(); let file_name = input.user_data().get_source_file_name(); @@ -763,6 +783,10 @@ impl Parser { let x = x.into_cow(); - Ok(x) + if optional_modifier.is_some() { + Ok(Cow::Owned(TypeLayout::Optional(Some(Box::new(x))))) + } else { + Ok(x) + } } } diff --git a/compiler/src/grammar.pest b/compiler/src/grammar.pest index 7d57e22..a5b388e 100644 --- a/compiler/src/grammar.pest +++ b/compiler/src/grammar.pest @@ -44,7 +44,7 @@ not = { "!" } primitive = _{ boolean | number | string } -allow_bin_op = _{ primitive | ident | function } +allow_bin_op = _{ nil | primitive | ident | function } math_primary = _{ allow_bin_op | "(" ~ math_expr ~ ")" } @@ -80,8 +80,10 @@ string = @{ "\"" ~ (("\\\"") | (!("\"") ~ ANY))* ~ "\"" } ident_chars = _{ (('a'..'z') | ('0'..'9') | ('A'..'Z') | "_") } +nil = { "nil" } ident = @{ !('0'..'9') ~ ident_chars+ } -type = { function_type | list_type_open_only | list_type | ident } +optional_modifier = { "?" } +type = { (function_type | list_type_open_only | list_type | ident) ~ optional_modifier? } ident_type = _{ ident ~ ":" ~ type } function_arguments = { (value ~ ("," ~ value)*)? } diff --git a/compiler/src/tests.rs b/compiler/src/tests.rs index 5f9e706..35a6cfd 100644 --- a/compiler/src/tests.rs +++ b/compiler/src/tests.rs @@ -5,6 +5,7 @@ mod functions; mod math; mod modify_edge_cases; mod number_loop; +mod optionals; #[macro_export] macro_rules! assert_proper_eq_hash { diff --git a/compiler/src/tests/optionals.rs b/compiler/src/tests/optionals.rs new file mode 100644 index 0000000..8d4ea7f --- /dev/null +++ b/compiler/src/tests/optionals.rs @@ -0,0 +1,17 @@ +use crate::eval; + +#[test] +#[should_panic = "unwrap this optional to use its value"] +fn type_mismatch() { + eval(r#" + give_name = fn(input: int) -> str? { + if input == 42 { + return "Mateo" + } else { + return nil + } + } + + maybe_me: str = give_name(42) + "#).unwrap(); +} \ No newline at end of file diff --git a/examples/main/optionals/some_none.ms b/examples/main/optionals/some_none.ms new file mode 100644 index 0000000..471eee6 --- /dev/null +++ b/examples/main/optionals/some_none.ms @@ -0,0 +1,14 @@ +give_name = fn(input: int) -> str? { + if input == 42 { + return "Mateo" + } else { + return nil + } +} + +x = give_name(30) + +print give_name(30) +print give_name(42) +print give_name(1) +print x \ No newline at end of file From 57b02764443298ef4cb1a9836635cd4940227081 Mon Sep 17 00:00:00 2001 From: mrodz <79176075+mrodz@users.noreply.github.com> Date: Mon, 16 Oct 2023 23:39:23 -0700 Subject: [PATCH 2/7] update type checking in function arguments --- compiler/src/ast/function_arguments.rs | 17 +++++++--- compiler/src/ast/type.rs | 35 ++------------------- examples/main/optionals/club.ms | 43 ++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 examples/main/optionals/club.ms diff --git a/compiler/src/ast/function_arguments.rs b/compiler/src/ast/function_arguments.rs index aeb1e24..fdff52a 100644 --- a/compiler/src/ast/function_arguments.rs +++ b/compiler/src/ast/function_arguments.rs @@ -33,6 +33,7 @@ impl Parser { let children = input.children(); let mut result = vec![]; + let mut result_len: usize = 0; let mut errors = vec![]; let expected_types: Cow>> = expected_parameters.to_types(); @@ -53,11 +54,17 @@ impl Parser { let user_gave = arg_ty.get_type_recursively(); - let class_self_case = allow_self_type - .map(|ty| expected_ty_at_idx.is_class_self() && ty.eq(user_gave)) - .unwrap_or(false); + let maybe_class_type = allow_self_type.map(|x| { + let TypeLayout::Class(class_type) = x else { + unreachable!(); + }; - if !user_gave.eq(expected_ty_at_idx) && !class_self_case { + class_type + }); + + result_len += 1; + + if !expected_ty_at_idx.eq_complex(&user_gave, maybe_class_type, false){ let argument_number = idx + 1; let error_message = format!("type mismatch when calling function (argument #{argument_number} was expected to be `{expected_ty_at_idx}` based on type signature, instead found `{user_gave}`)"); errors.push(new_err( @@ -72,7 +79,7 @@ impl Parser { result.push(value_for_arg) } - let result_len = result.len(); + // let result_len = result.len(); let expected_parameters_len = expected_parameters.len(); if result_len != expected_parameters_len { diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index 57a862b..f45df7b 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -383,6 +383,8 @@ impl TypeLayout { ) } + /// - `Self` is the expected type + /// - `rhs` is the supplied type pub fn eq_complex( &self, rhs: &Self, @@ -614,39 +616,6 @@ pub(crate) trait IntoType { } } -// pub(crate) fn type_from_str( -// input: &str, -// user_data: Rc, -// ) -> Result> { -// unsafe { -// if let Some(r#type) = user_data.get_type(input) { -// Ok(Cow::Borrowed(r#type)) -// } else { -// if let Ok(list_type_open_only) = -// parse_with_userdata_features(Rule::list_type_open_only, input, user_data.clone()) -// { -// let single = list_type_open_only.single()?; -// let ty = Parser::list_type_open_only(single)?; -// return Ok(Cow::Owned(TypeLayout::List(ty))); -// } else if let Ok(function_type) = -// parse_with_userdata_features(Rule::function_type, input, user_data.clone()) -// { -// let single = function_type.single()?; -// let ty = Parser::function_type(single)?; -// return Ok(Cow::Owned(TypeLayout::Function(Arc::new(ty)))); -// } else if let Ok(list_type) = -// parse_with_userdata_features(Rule::list_type, input, user_data) -// { -// let single = list_type.single()?; -// let ty = Parser::list_type(single)?; -// return Ok(Cow::Owned(TypeLayout::List(ty))); -// } - -// bail!("type '{input}' has not been registered") -// } -// } -// } - impl Parser { pub fn function_type(input: Node) -> Result { let mut children = input.children(); diff --git a/examples/main/optionals/club.ms b/examples/main/optionals/club.ms new file mode 100644 index 0000000..7d26f90 --- /dev/null +++ b/examples/main/optionals/club.ms @@ -0,0 +1,43 @@ +class Person { + name: str + age: int + + constructor(self, name: str, age: int) { + self.name = name + self.age = age + } +} + +new_club_member = fn(name: str, age: int) -> Person? { + if age >= 21 { + return Person(name, age) + } + + return nil +} + +give_drink = fn(age: int) -> str? { + if age < 21 { + return nil + } + + return "Booze" +} + +take_optional = fn(input: str?) { + print input +} + +triple_it = fn(input: str) -> str { + return input * 3 +} + +take_optional("cool") +take_optional(nil) + +take_optional(triple_it("\n")) + +print new_club_member("Mateo", 16) +print new_club_member("Devin", 22) + +# print triple_it(give_drink(21)) \ No newline at end of file From 03884f856957abc18e9d43a360a498b1137d0cc5 Mon Sep 17 00:00:00 2001 From: mrodz <79176075+mrodz@users.noreply.github.com> Date: Tue, 17 Oct 2023 00:37:45 -0700 Subject: [PATCH 3/7] grammar for `?=` --- compiler/src/ast.rs | 2 + compiler/src/ast/assignment.rs | 1 + .../src/ast/assignment/assignment_no_type.rs | 2 +- compiler/src/ast/math_expr.rs | 4 + compiler/src/ast/optionals.rs | 56 ++++++++++++++ compiler/src/ast/type.rs | 10 +++ compiler/src/ast/value.rs | 76 ++++--------------- compiler/src/grammar.pest | 5 +- examples/main/optionals/unwrap.ms | 5 ++ 9 files changed, 99 insertions(+), 62 deletions(-) create mode 100644 compiler/src/ast/optionals.rs create mode 100644 examples/main/optionals/unwrap.ms diff --git a/compiler/src/ast.rs b/compiler/src/ast.rs index c04872f..ddb1a60 100644 --- a/compiler/src/ast.rs +++ b/compiler/src/ast.rs @@ -17,6 +17,7 @@ mod loop_control_flow; mod math_expr; mod number; mod number_loop; +mod optionals; mod print_statement; mod reassignment; mod r#return; @@ -41,6 +42,7 @@ pub(crate) use loop_control_flow::{Break, Continue}; pub(crate) use math_expr::{Expr, Op as BinaryOperation}; pub(crate) use number::Number; pub(crate) use number_loop::NumberLoop; +pub(crate) use optionals::UnwrapExpr; pub(crate) use print_statement::PrintStatement; pub(crate) use r#return::ReturnStatement; pub(crate) use r#type::TypeLayout; diff --git a/compiler/src/ast/assignment.rs b/compiler/src/ast/assignment.rs index 2ca32f5..056e264 100644 --- a/compiler/src/ast/assignment.rs +++ b/compiler/src/ast/assignment.rs @@ -157,6 +157,7 @@ impl Compile for Assignment { Value::MathExpr(math_expr) => math_expr.compile(state)?, Value::Boolean(boolean) => boolean.compile(state)?, Value::List(list) => list.compile(state)?, + Value::UnwrapExpr(unwrap_expr) => unwrap_expr.compile(state)?, } }; diff --git a/compiler/src/ast/assignment/assignment_no_type.rs b/compiler/src/ast/assignment/assignment_no_type.rs index 8797ec9..484423c 100644 --- a/compiler/src/ast/assignment/assignment_no_type.rs +++ b/compiler/src/ast/assignment/assignment_no_type.rs @@ -41,7 +41,7 @@ impl Parser { input.as_span(), &input.user_data().get_source_file_name(), "could not get the type".into(), - || vec!["could not infer the type of the value\ntry explicitly defining a type: `name: type = ...`"], + || vec!["Could not infer the type of the value; try explicitly defining a type"], ) .to_err_vec()?; diff --git a/compiler/src/ast/math_expr.rs b/compiler/src/ast/math_expr.rs index 53bc412..19ecc62 100644 --- a/compiler/src/ast/math_expr.rs +++ b/compiler/src/ast/math_expr.rs @@ -62,6 +62,7 @@ pub static PRATT_PARSER: Lazy> = Lazy::new(|| { use Rule::*; PrattParser::new() + .op(Op::infix(unwrap, Left)) .op(Op::infix(or, Left) | Op::infix(xor, Left)) .op(Op::infix(and, Left)) .op(Op::infix(lt, Left) @@ -96,6 +97,7 @@ pub enum Op { And, Or, Xor, + Unwrap, } impl Op { @@ -116,6 +118,7 @@ impl Op { And => "&&", Or => "||", Xor => "^", + Unwrap => "?=", } } } @@ -538,6 +541,7 @@ fn parse_expr( Rule::and => Op::And, Rule::or => Op::Or, Rule::xor => Op::Xor, + Rule::unwrap => Op::Unwrap, rule => unreachable!("Expr::parse expected infix operation, found {:?}", rule), }; diff --git a/compiler/src/ast/optionals.rs b/compiler/src/ast/optionals.rs new file mode 100644 index 0000000..b1119bb --- /dev/null +++ b/compiler/src/ast/optionals.rs @@ -0,0 +1,56 @@ +use std::borrow::Cow; + +use anyhow::bail; + +use crate::{parser::{Parser, Node}, VecErr, ast::{r#type::IntoType, new_err}}; + +use super::{Dependencies, Compile, Ident, Value}; + +#[derive(Debug)] +pub(crate) struct UnwrapExpr { + ident: Ident, + value: Box, +} + +impl Dependencies for UnwrapExpr { + +} + +impl Compile for UnwrapExpr { + fn compile(&self, state: &super::CompilationState) -> anyhow::Result, anyhow::Error> { + todo!() + } +} + +impl Parser { + pub fn unwrap_expr(input: Node) -> Result> { + let mut children = input.children(); + + let ident = children.next().unwrap(); + let value = children.next().unwrap(); + + let value_span = value.as_span(); + + dbg!(ident.as_rule()); + dbg!(value.as_rule()); + + let mut ident = Self::ident(ident).to_err_vec()?; + + let value = Self::value(value)?; + + let value_ty = value.for_type().to_err_vec()?; + + let user_data = input.user_data(); + + let optional_check = value_ty.is_optional(); + + match optional_check { + (true, Some(known_type)) => ident.link_force_no_inherit(&user_data, known_type.clone()).to_err_vec()?, + (true, _) => return Err(vec![new_err(value_span, &user_data.get_source_file_name(), "the type of this optional is unknown".to_owned())]), + _ => ident.link_force_no_inherit(&user_data, Cow::Owned(value_ty)).to_err_vec()?, + } + + Ok(UnwrapExpr { ident, value: Box::new(value) }) + + } +} \ No newline at end of file diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index f45df7b..720d097 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -198,6 +198,16 @@ impl TypeLayout { matches!(me, TypeLayout::Native(NativeType::Bool)) } + pub fn is_optional(&self) -> (bool, Option<&Cow<'static, TypeLayout>>) { + let me = self.get_type_recursively(); + + if let TypeLayout::Optional(x @ Some(..)) = me { + return (true, x.as_ref().map(|x| x.as_ref())) + } + + (false, None) + } + pub fn is_float(&self) -> bool { let me = self.get_type_recursively(); diff --git a/compiler/src/ast/value.rs b/compiler/src/ast/value.rs index ecbf78b..31144d7 100644 --- a/compiler/src/ast/value.rs +++ b/compiler/src/ast/value.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use anyhow::Result; +use anyhow::{Result, bail}; use crate::{ parser::{AssocFileData, Node, Parser, Rule}, @@ -8,8 +8,8 @@ use crate::{ }; use super::{ - math_expr::Expr, new_err, r#type::IntoType, string::AstString, CompilationState, Compile, - CompiledItem, Dependencies, Dependency, Function, Ident, List, Number, TypeLayout, + Expr, new_err, r#type::IntoType, string::AstString, CompilationState, Compile, + CompiledItem, Dependencies, Dependency, Function, Ident, List, Number, TypeLayout, UnwrapExpr, BOOL_TYPE, }; #[derive(Debug)] @@ -19,6 +19,7 @@ pub(crate) enum Value { Number(Number), String(AstString), MathExpr(Box), + UnwrapExpr(UnwrapExpr), Boolean(bool), List(List), } @@ -186,64 +187,7 @@ impl Indexable for ValueChainType { } } -#[cfg(not)] -impl Compile for ValueChainType { - fn compile( - &self, - function_buffer: &mut Vec, - ) -> Result, anyhow::Error> { - match self { - Self::Index(index) => index.compile(function_buffer), - Self::Dot(_) => unimplemented!(), - } - } -} - -// #[derive(Debug)] -// pub(crate) struct ValueChain(pub(crate) Value, pub(crate) Option>); - -// impl Dependencies for ValueChain { -// fn dependencies(&self) -> Vec { -// let mut value_dependencies = self.0.net_dependencies(); -// if let Some(ref next) = self.1 { -// value_dependencies.append(&mut next.net_dependencies()); -// } -// value_dependencies -// } -// } - -// impl IntoType for ValueChain { -// fn for_type(&self) -> Result { -// if let Some(ref other) = self.1 { -// other.output_from_value(&self.0).map(|x| x.into_owned()) -// } else { -// self.0.for_type() -// } -// } -// } - impl Value { - // #[allow(unused)] - // pub fn is_value_callable(&self) -> bool { - // match &self.0 { - // Value::Callable(..) => true, - // Value::MathExpr(maybe_value) - // if matches!(maybe_value.deref(), Expr::Value(Value::Callable(..))) => - // { - // true - // } - // _ => false, - // } - // } - - // pub fn add_index(&mut self, index: Index) { - // if let Some(_) = self.1 { - // unreachable!("index already there!") - // } else { - // self.1 = Some(Box::new(ValueChainType::Index(index))) - // } - // } - pub fn is_callable(&self) -> Result { let ty = self.for_type()?; @@ -269,6 +213,11 @@ impl Value { } Value::MathExpr(ref math_expr) => { let ty = math_expr.for_type()?.get_owned_type_recursively(); + + if let TypeLayout::Optional(None) = ty { + bail!("Hint: specify this optional's type like `{}: TYPE? = nil`", ident.name()) + } + ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; } Value::Boolean(ref boolean) => { @@ -279,6 +228,9 @@ impl Value { let ty = list.for_type_force_mixed()?.get_owned_type_recursively(); ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; } + Value::UnwrapExpr(..) => { + ident.link_force_no_inherit(user_data, Cow::Borrowed(&BOOL_TYPE)); + } } Ok(()) @@ -295,6 +247,7 @@ impl IntoType for Value { Self::String(string) => string.for_type(), Self::Boolean(boolean) => boolean.for_type(), Self::List(list) => list.for_type(), + Self::UnwrapExpr(..) => Ok(BOOL_TYPE.to_owned()), } } } @@ -310,6 +263,7 @@ impl Dependencies for Value { // Self::Callable(callable) => callable.net_dependencies(), Self::Boolean(boolean) => boolean.net_dependencies(), Self::List(list) => list.net_dependencies(), + Self::UnwrapExpr(unwrap_expr) => unwrap_expr.net_dependencies(), } } } @@ -324,6 +278,7 @@ impl Compile for Value { Self::MathExpr(math_expr) => math_expr.compile(state), Self::Boolean(boolean) => boolean.compile(state), Self::List(list) => list.compile(state), + Self::UnwrapExpr(unwrap_expr) => unwrap_expr.compile(state), } } } @@ -360,6 +315,7 @@ impl Parser { // Rule::callable => Value::Callable(Self::callable(value)?), Rule::list => Value::List(Self::list(value)?), Rule::WHITESPACE => unreachable!("{:?}", value.as_span()), + Rule::unwrap_expr => Value::UnwrapExpr(Self::unwrap_expr(value)?), x => { return Err(vec![new_err( input.as_span(), diff --git a/compiler/src/grammar.pest b/compiler/src/grammar.pest index a5b388e..1e8a70e 100644 --- a/compiler/src/grammar.pest +++ b/compiler/src/grammar.pest @@ -104,7 +104,10 @@ list_type = { "[" ~ type? ~ ("," ~ (type ~ !("...")))* ~ ("," ~ open_ended_type) list_type_open_only = { "[" ~ open_ended_type ~ "]" } list_index = { ("[" ~ value ~ "]")+ } -value = { function | math_expr | ident | list } +unwrap = _{ "?=" } +unwrap_expr = { ident ~ unwrap ~ value } + +value = { function | unwrap_expr | math_expr | ident | list } assignment_flag = { "modify" | "const" } // bad_assignment_flag = { &ident } diff --git a/examples/main/optionals/unwrap.ms b/examples/main/optionals/unwrap.ms new file mode 100644 index 0000000..30feb0b --- /dev/null +++ b/examples/main/optionals/unwrap.ms @@ -0,0 +1,5 @@ +a: str? = nil + +b = a ?= a + +print b \ No newline at end of file From 0a3ae30c487a689c244b8d5dfe4866d4453b4b58 Mon Sep 17 00:00:00 2001 From: Mateo Rodriguez Date: Tue, 17 Oct 2023 11:48:04 -0700 Subject: [PATCH 4/7] add to `?=` operator --- bytecode/src/instruction.rs | 31 ++++++++++ bytecode/src/instruction_constants.rs | 5 +- bytecode/src/variables/primitive.rs | 8 ++- compiler/src/ast/declaration.rs | 11 +++- compiler/src/ast/if_statement.rs | 20 +++--- compiler/src/ast/optionals.rs | 88 +++++++++++++++++---------- compiler/src/ast/type.rs | 4 ++ compiler/src/ast/value.rs | 2 +- compiler/src/grammar.pest | 2 +- compiler/src/tests/optionals.rs | 20 ++++++ examples/main/optionals/some_none.ms | 4 +- examples/main/optionals/unwrap.ms | 14 ++++- 12 files changed, 157 insertions(+), 52 deletions(-) diff --git a/bytecode/src/instruction.rs b/bytecode/src/instruction.rs index 5b0bff0..65af0d6 100644 --- a/bytecode/src/instruction.rs +++ b/bytecode/src/instruction.rs @@ -763,6 +763,37 @@ pub mod implementations { } } + instruction! { + unwrap_into(ctx, args) { + let Some(name) = args.first() else { + bail!("`unwrap_into` requires a name"); + }; + + let Some(primitive) = ctx.pop() else { + bail!("`unwrap_into` requires a primitive at the top of the local operating stack"); + }; + + let status = match primitive { + Primitive::Optional(Some(unwrapped)) => { + let var = unwrapped.as_ref().to_owned(); + ctx.register_variable_local(name.to_owned(), var)?; + true + } + Primitive::Optional(None) => { + false + }, + other_primitive => { + ctx.register_variable_local(name.to_owned(), other_primitive)?; + true + } + }; + + ctx.push(bool!(status)); + + Ok(()) + } + } + instruction! { store(ctx, args) { let Some(name) = args.first() else { diff --git a/bytecode/src/instruction_constants.rs b/bytecode/src/instruction_constants.rs index 634c109..e20e3ad 100644 --- a/bytecode/src/instruction_constants.rs +++ b/bytecode/src/instruction_constants.rs @@ -22,7 +22,7 @@ pub static REPR_TO_BIN: Lazy> = Lazy::new(|| { /// /// Saving this as a constant makes it harder for the arrays to fall out of sync /// by requiring that they both take the same size. -pub const INSTRUCTION_COUNT: usize = 62; +pub const INSTRUCTION_COUNT: usize = 63; /// This is an array that provides O(1) lookups of names from bytes. pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ @@ -88,6 +88,7 @@ pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ /* 0x3B [59] */ b"export_name", /* 0x3C [60] */ b"export_special", /* 0x3D [61] */ b"load_self_export", + /* 0x3E [62] */ b"unwrap_into", ]; /// Similar to [`BIN_TO_REPR`][crate::instruction_constants::BIN_TO_REPR], @@ -155,6 +156,7 @@ pub static FUNCTION_POINTER_LOOKUP: [InstructionSignature; INSTRUCTION_COUNT] = implementations::export_name, implementations::export_special, implementations::load_self_export, + implementations::unwrap_into, ]; pub mod id { @@ -223,4 +225,5 @@ pub mod id { pub const EXPORT_NAME: u8 = 59; pub const EXPORT_SELF: u8 = 60; pub const LOAD_SELF_EXPORT: u8 = 61; + pub const UNWRAP_INTO: u8 = 62; } diff --git a/bytecode/src/variables/primitive.rs b/bytecode/src/variables/primitive.rs index 439681b..10c333e 100644 --- a/bytecode/src/variables/primitive.rs +++ b/bytecode/src/variables/primitive.rs @@ -166,12 +166,18 @@ impl Primitive { }; } + use Primitive as P; + + if let (P::Optional(maybe), yes) | (yes, P::Optional(maybe)) = (self, rhs) { + return Ok(maybe.as_ref().is_some_and(|some| some.as_ref() == yes)); + } + impl_eq!(Int with Float(r=f64), BigInt(r=i128), Byte(r=i32)); impl_eq!(Float with Int(r=f64), BigInt(r=f64), Byte(r=f64)); impl_eq!(BigInt with Float(r=f64), Int(r=i128), Byte(r=i128)); impl_eq!(Byte with Float(r=f64), BigInt(r=i128), Int(r=i32)); - impl_eq!(each Str, Bool with itself); + impl_eq!(each Optional, Str, Bool with itself); } /// Returns whether this primitive is numeric. diff --git a/compiler/src/ast/declaration.rs b/compiler/src/ast/declaration.rs index 33ca58d..ded9744 100644 --- a/compiler/src/ast/declaration.rs +++ b/compiler/src/ast/declaration.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ class::Class, Assertion, Assignment, Break, CompilationState, Compile, CompiledItem, Continue, Dependencies, Dependency, Expr, IfStatement, NumberLoop, PrintStatement, Reassignment, - ReturnStatement, WhileLoop, + ReturnStatement, WhileLoop, UnwrapExpr, }; #[derive(Debug)] @@ -28,6 +28,7 @@ pub(crate) enum Declaration { Break(Break), Assertion(Assertion), Class(Class), + UnwrapExpr(UnwrapExpr), } impl Dependencies for Declaration { @@ -42,6 +43,7 @@ impl Dependencies for Declaration { Self::NumberLoop(number_loop) => number_loop.supplies(), Self::Assertion(assertion) => assertion.supplies(), Self::Class(class) => class.supplies(), + Self::UnwrapExpr(unwrap_expr) => unwrap_expr.supplies(), Self::Reassignment(_) | Self::Continue(_) | Self::Break(_) => vec![], } } @@ -58,6 +60,7 @@ impl Dependencies for Declaration { Self::Reassignment(reassignment) => reassignment.net_dependencies(), Self::Assertion(assertion) => assertion.net_dependencies(), Self::Class(class) => class.net_dependencies(), + Self::UnwrapExpr(unwrap_expr) => unwrap_expr.net_dependencies(), Self::Continue(_) | Self::Break(_) => vec![], } } @@ -82,6 +85,11 @@ impl Compile for Declaration { Self::NumberLoop(x) => x.compile(state), Self::Assertion(x) => x.compile(state), Self::Class(x) => x.compile(state), + Self::UnwrapExpr(x) => { + let mut unwrap_expr_compiled = x.compile(state)?; + unwrap_expr_compiled.push(instruction!(void)); + Ok(unwrap_expr_compiled) + }, } } } @@ -111,6 +119,7 @@ impl Parser { Rule::reassignment => Declaration::Reassignment(Self::reassignment(declaration)?), Rule::assertion => Declaration::Assertion(Self::assertion(declaration)?), Rule::class => Declaration::Class(Self::class(declaration)?), + Rule::unwrap_expr => Declaration::UnwrapExpr(Self::unwrap_expr(declaration)?), x => unreachable!("{x:?} is not supported"), }; diff --git a/compiler/src/ast/if_statement.rs b/compiler/src/ast/if_statement.rs index cfd5a53..71cf826 100644 --- a/compiler/src/ast/if_statement.rs +++ b/compiler/src/ast/if_statement.rs @@ -133,6 +133,16 @@ impl Parser { let body = children.next().expect("no body"); let else_statement = children.next(); + let child_returns_type = input + .user_data() + .return_statement_expected_yield_type() + .map_or_else( + || ScopeReturnStatus::No, + |ty| ScopeReturnStatus::ParentShould(ty.clone()), + ); + + let if_scope: ScopeHandle = input.user_data().push_if_typed(child_returns_type); + let condition_as_value = Self::value(condition)?; let condition_type = condition_as_value.for_type().to_err_vec()?; @@ -148,16 +158,6 @@ impl Parser { let can_determine_is_if_branch_truthy = false; // TODO - let child_returns_type = input - .user_data() - .return_statement_expected_yield_type() - .map_or_else( - || ScopeReturnStatus::No, - |ty| ScopeReturnStatus::ParentShould(ty.clone()), - ); - - let if_scope: ScopeHandle = input.user_data().push_if_typed(child_returns_type); - let body_as_block = Self::block(body); // get the return type back diff --git a/compiler/src/ast/optionals.rs b/compiler/src/ast/optionals.rs index b1119bb..fd38b72 100644 --- a/compiler/src/ast/optionals.rs +++ b/compiler/src/ast/optionals.rs @@ -1,56 +1,78 @@ use std::borrow::Cow; -use anyhow::bail; +use crate::{ + ast::{new_err, r#type::IntoType}, + instruction, + parser::{Node, Parser}, + VecErr, +}; -use crate::{parser::{Parser, Node}, VecErr, ast::{r#type::IntoType, new_err}}; - -use super::{Dependencies, Compile, Ident, Value}; +use super::{Compile, Dependencies, Ident, Value}; #[derive(Debug)] pub(crate) struct UnwrapExpr { - ident: Ident, - value: Box, + ident: Ident, + value: Box, } -impl Dependencies for UnwrapExpr { - -} +impl Dependencies for UnwrapExpr {} impl Compile for UnwrapExpr { - fn compile(&self, state: &super::CompilationState) -> anyhow::Result, anyhow::Error> { - todo!() - } + fn compile( + &self, + state: &super::CompilationState, + ) -> anyhow::Result, anyhow::Error> { + let mut value_compiled = self.value.compile(state)?; + + let name = self.ident.name(); + + value_compiled.push(instruction!(unwrap_into name)); + + Ok(value_compiled) + } } impl Parser { - pub fn unwrap_expr(input: Node) -> Result> { - let mut children = input.children(); + pub fn unwrap_expr(input: Node) -> Result> { + let mut children = input.children(); - let ident = children.next().unwrap(); - let value = children.next().unwrap(); + let ident = children.next().unwrap(); + let value = children.next().unwrap(); - let value_span = value.as_span(); + let value_span = value.as_span(); - dbg!(ident.as_rule()); - dbg!(value.as_rule()); + dbg!(ident.as_rule()); + dbg!(value.as_rule()); - let mut ident = Self::ident(ident).to_err_vec()?; + let mut ident = Self::ident(ident).to_err_vec()?; - let value = Self::value(value)?; + let value = Self::value(value)?; - let value_ty = value.for_type().to_err_vec()?; + let value_ty = value.for_type().to_err_vec()?; - let user_data = input.user_data(); + let user_data = input.user_data(); - let optional_check = value_ty.is_optional(); + let optional_check = value_ty.is_optional(); - match optional_check { - (true, Some(known_type)) => ident.link_force_no_inherit(&user_data, known_type.clone()).to_err_vec()?, - (true, _) => return Err(vec![new_err(value_span, &user_data.get_source_file_name(), "the type of this optional is unknown".to_owned())]), - _ => ident.link_force_no_inherit(&user_data, Cow::Owned(value_ty)).to_err_vec()?, - } + match optional_check { + (true, Some(known_type)) => ident + .link_force_no_inherit(&user_data, known_type.clone()) + .to_err_vec()?, + (true, _) => { + return Err(vec![new_err( + value_span, + &user_data.get_source_file_name(), + "the type of this optional is unknown".to_owned(), + )]) + } + _ => ident + .link_force_no_inherit(&user_data, Cow::Owned(value_ty)) + .to_err_vec()?, + } - Ok(UnwrapExpr { ident, value: Box::new(value) }) - - } -} \ No newline at end of file + Ok(UnwrapExpr { + ident, + value: Box::new(value), + }) + } +} diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index 720d097..558cb96 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -528,6 +528,10 @@ impl TypeLayout { pub fn get_output_type(&self, other: &Self, op: &Op) -> Option { use TypeLayout::*; + if let (_, Optional(None), Eq | Neq) | (Optional(None), _, Eq | Neq) = (self, other, op) { + return Some(BOOL_TYPE.to_owned()); + } + let Native(me) = self.get_type_recursively() else { return None; }; diff --git a/compiler/src/ast/value.rs b/compiler/src/ast/value.rs index 31144d7..2f141d9 100644 --- a/compiler/src/ast/value.rs +++ b/compiler/src/ast/value.rs @@ -229,7 +229,7 @@ impl Value { ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; } Value::UnwrapExpr(..) => { - ident.link_force_no_inherit(user_data, Cow::Borrowed(&BOOL_TYPE)); + ident.link_force_no_inherit(user_data, Cow::Borrowed(&BOOL_TYPE))?; } } diff --git a/compiler/src/grammar.pest b/compiler/src/grammar.pest index 1e8a70e..a1a80a2 100644 --- a/compiler/src/grammar.pest +++ b/compiler/src/grammar.pest @@ -162,7 +162,7 @@ class_body = { "{" ~ class_feature* ~ "}" } class = { "class" ~ ident ~ class_body } -declaration = { class | reassignment | assignment | print_statement | assertion | return_statement | continue_statement | break_statement | if_statement | while_loop | number_loop | math_expr } +declaration = { class | reassignment | assignment | unwrap_expr | print_statement | assertion | return_statement | continue_statement | break_statement | if_statement | while_loop | number_loop | math_expr } file = { SOI ~ diff --git a/compiler/src/tests/optionals.rs b/compiler/src/tests/optionals.rs index 8d4ea7f..17eaf5c 100644 --- a/compiler/src/tests/optionals.rs +++ b/compiler/src/tests/optionals.rs @@ -14,4 +14,24 @@ fn type_mismatch() { maybe_me: str = give_name(42) "#).unwrap(); +} + +#[test] +fn optional_return_type() { + eval(r#" + give_name = fn(input: int) -> str? { + if input == 42 { + return "Mateo" + } else { + return nil + } + } + + x: str? = give_name(30) + + assert give_name(30) == nil + assert give_name(42) == "Mateo" + assert give_name(1) == nil + assert x == nil + "#).unwrap(); } \ No newline at end of file diff --git a/examples/main/optionals/some_none.ms b/examples/main/optionals/some_none.ms index 471eee6..ce762ea 100644 --- a/examples/main/optionals/some_none.ms +++ b/examples/main/optionals/some_none.ms @@ -11,4 +11,6 @@ x = give_name(30) print give_name(30) print give_name(42) print give_name(1) -print x \ No newline at end of file +print x + +print nil == 5 \ No newline at end of file diff --git a/examples/main/optionals/unwrap.ms b/examples/main/optionals/unwrap.ms index 30feb0b..ad6ea37 100644 --- a/examples/main/optionals/unwrap.ms +++ b/examples/main/optionals/unwrap.ms @@ -1,5 +1,13 @@ -a: str? = nil +a: int? = nil -b = a ?= a +if bbb ?= a { + print bbb +} else { + print "a is nil" +} -print b \ No newline at end of file + +x = 5 +y ?= 10 + +print x + y \ No newline at end of file From 2b28c5bdc70c53bbf98eac7171ab8a030ee30db6 Mon Sep 17 00:00:00 2001 From: mrodz <79176075+mrodz@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:58:54 -0700 Subject: [PATCH 5/7] bug fixes, fix edge cases, unit tests --- bytecode/src/instruction.rs | 5 +- bytecode/src/variables/primitive.rs | 4 +- .../src/ast/assignment/assignment_type.rs | 10 +- compiler/src/ast/declaration.rs | 4 +- compiler/src/ast/function_arguments.rs | 2 +- compiler/src/ast/if_statement.rs | 2 +- compiler/src/ast/math_expr.rs | 6 +- compiler/src/ast/optionals.rs | 5 +- compiler/src/ast/type.rs | 40 +++--- compiler/src/ast/value.rs | 13 +- compiler/src/ast/while_loop.rs | 20 +-- compiler/src/tests/math.rs | 10 +- compiler/src/tests/optionals.rs | 121 +++++++++++++++++- examples/main/optionals/unwrap.ms | 3 +- 14 files changed, 186 insertions(+), 59 deletions(-) diff --git a/bytecode/src/instruction.rs b/bytecode/src/instruction.rs index 65af0d6..03b86fd 100644 --- a/bytecode/src/instruction.rs +++ b/bytecode/src/instruction.rs @@ -779,7 +779,8 @@ pub mod implementations { ctx.register_variable_local(name.to_owned(), var)?; true } - Primitive::Optional(None) => { + primitive @ Primitive::Optional(None) => { + ctx.register_variable_local(name.to_owned(), primitive)?; false }, other_primitive => { @@ -1048,7 +1049,7 @@ pub mod implementations { let primitive = ctx.pop().unwrap(); - let result: Rc> = primitive.lookup(name).with_context(|| format!("{name} does not exist on {primitive:?}"))?; + let result: Rc> = primitive.lookup(name).with_context(|| format!("`{name}` does not exist on `{primitive:?}`"))?; // println!("LOOKING UP ON: {primitive:?}\nLOOKUP RETURNED: {result:?}"); diff --git a/bytecode/src/variables/primitive.rs b/bytecode/src/variables/primitive.rs index 10c333e..3668110 100644 --- a/bytecode/src/variables/primitive.rs +++ b/bytecode/src/variables/primitive.rs @@ -169,7 +169,9 @@ impl Primitive { use Primitive as P; if let (P::Optional(maybe), yes) | (yes, P::Optional(maybe)) = (self, rhs) { - return Ok(maybe.as_ref().is_some_and(|some| some.as_ref() == yes)); + if let Some(maybe_unwrapped) = maybe { + return Ok(maybe_unwrapped.as_ref() == yes); + }; } impl_eq!(Int with Float(r=f64), BigInt(r=i128), Byte(r=i32)); diff --git a/compiler/src/ast/assignment/assignment_type.rs b/compiler/src/ast/assignment/assignment_type.rs index db4fd5a..96e6835 100644 --- a/compiler/src/ast/assignment/assignment_type.rs +++ b/compiler/src/ast/assignment/assignment_type.rs @@ -34,11 +34,11 @@ impl Parser { let value: Value = Self::value(value)?; if let Ok(ref assignment_ty) = value.for_type() { - if !ty - .as_ref() - .get_type_recursively() - .eq_complex(assignment_ty.get_type_recursively(), self_type.as_ref(), false) - { + if !ty.as_ref().get_type_recursively().eq_complex( + assignment_ty.get_type_recursively(), + self_type.as_ref(), + false, + ) { let hint = ty.get_error_hint_between_types(assignment_ty).map_or_else( || Cow::Borrowed(""), |str| Cow::Owned(format!("\n\t- hint: {str}")), diff --git a/compiler/src/ast/declaration.rs b/compiler/src/ast/declaration.rs index ded9744..747e3d0 100644 --- a/compiler/src/ast/declaration.rs +++ b/compiler/src/ast/declaration.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ class::Class, Assertion, Assignment, Break, CompilationState, Compile, CompiledItem, Continue, Dependencies, Dependency, Expr, IfStatement, NumberLoop, PrintStatement, Reassignment, - ReturnStatement, WhileLoop, UnwrapExpr, + ReturnStatement, UnwrapExpr, WhileLoop, }; #[derive(Debug)] @@ -89,7 +89,7 @@ impl Compile for Declaration { let mut unwrap_expr_compiled = x.compile(state)?; unwrap_expr_compiled.push(instruction!(void)); Ok(unwrap_expr_compiled) - }, + } } } } diff --git a/compiler/src/ast/function_arguments.rs b/compiler/src/ast/function_arguments.rs index fdff52a..7997d7a 100644 --- a/compiler/src/ast/function_arguments.rs +++ b/compiler/src/ast/function_arguments.rs @@ -64,7 +64,7 @@ impl Parser { result_len += 1; - if !expected_ty_at_idx.eq_complex(&user_gave, maybe_class_type, false){ + if !expected_ty_at_idx.eq_complex(&user_gave, maybe_class_type, false) { let argument_number = idx + 1; let error_message = format!("type mismatch when calling function (argument #{argument_number} was expected to be `{expected_ty_at_idx}` based on type signature, instead found `{user_gave}`)"); errors.push(new_err( diff --git a/compiler/src/ast/if_statement.rs b/compiler/src/ast/if_statement.rs index 71cf826..c332ba2 100644 --- a/compiler/src/ast/if_statement.rs +++ b/compiler/src/ast/if_statement.rs @@ -140,7 +140,7 @@ impl Parser { || ScopeReturnStatus::No, |ty| ScopeReturnStatus::ParentShould(ty.clone()), ); - + let if_scope: ScopeHandle = input.user_data().push_if_typed(child_returns_type); let condition_as_value = Self::value(condition)?; diff --git a/compiler/src/ast/math_expr.rs b/compiler/src/ast/math_expr.rs index 19ecc62..f78ad72 100644 --- a/compiler/src/ast/math_expr.rs +++ b/compiler/src/ast/math_expr.rs @@ -278,7 +278,7 @@ impl IntoType for Expr { } Expr::Index { index, .. } => index.for_type(), Expr::DotLookup { expected_type, .. } => Ok(expected_type.to_owned()), - Expr::Nil => Ok(TypeLayout::Optional(None)) + Expr::Nil => Ok(TypeLayout::Optional(None)), } } } @@ -440,9 +440,7 @@ fn compile_depth( result.append(&mut dot_chain.compile(state)?); Ok(result) } - Expr::Nil => { - Ok(vec![instruction!(reserve_primitive)]) - } + Expr::Nil => Ok(vec![instruction!(reserve_primitive)]), } } diff --git a/compiler/src/ast/optionals.rs b/compiler/src/ast/optionals.rs index fd38b72..8b6f125 100644 --- a/compiler/src/ast/optionals.rs +++ b/compiler/src/ast/optionals.rs @@ -24,7 +24,7 @@ impl Compile for UnwrapExpr { ) -> anyhow::Result, anyhow::Error> { let mut value_compiled = self.value.compile(state)?; - let name = self.ident.name(); + let name = self.ident.name(); value_compiled.push(instruction!(unwrap_into name)); @@ -41,9 +41,6 @@ impl Parser { let value_span = value.as_span(); - dbg!(ident.as_rule()); - dbg!(value.as_rule()); - let mut ident = Self::ident(ident).to_err_vec()?; let value = Self::value(value)?; diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index 558cb96..13e93f3 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -4,7 +4,7 @@ use crate::{ scope::{ScopeReturnStatus, SuccessTypeSearchResult, TypeSearchResult}, CompilationError, }; -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use pest::Span; use std::{borrow::Cow, fmt::Display, hash::Hash, ops::Deref, sync::Arc}; @@ -202,7 +202,7 @@ impl TypeLayout { let me = self.get_type_recursively(); if let TypeLayout::Optional(x @ Some(..)) = me { - return (true, x.as_ref().map(|x| x.as_ref())) + return (true, x.as_ref().map(|x| x.as_ref())); } (false, None) @@ -410,12 +410,9 @@ impl TypeLayout { | (TypeLayout::Class(other), Self::ClassSelf, Some(executing_class)) => { executing_class.deref().eq(other) } - (Self::Optional(None), Self::Optional(Some(_)), _) | (Self::Optional(Some(_)), Self::Optional(None), _) => { - true - } - (Self::Optional(Some(x)), y, _) => { - x.as_ref().as_ref() == y - } + (Self::Optional(None), Self::Optional(Some(_)), _) + | (Self::Optional(Some(_)), Self::Optional(None), _) => true, + (Self::Optional(Some(x)), y, _) => x.as_ref().as_ref() == y, (y, Self::Optional(Some(x)), _) if lhs_allow_optional_unwrap => { x.as_ref().as_ref() == y } @@ -426,18 +423,18 @@ impl TypeLayout { pub fn get_output_type_from_index(&self, index: &Value) -> Result> { let me = self.get_type_recursively(); - let index_ty = index.for_type()?; + let index_ty = index.for_type().context("could not get type of index")?; if !index_ty.can_be_used_as_list_index() { bail!("`{index_ty}` cannot be used as an index here"); } - let index_as_usize = index.get_usize()?; + let index_as_usize = index.get_usize().context("could not get index as usize")?; - if let Self::Native(NativeType::Str(StrWrapper(Some(str_len)))) = me { - match index_as_usize { - ValToUsize::Ok(index) => { - if index >= *str_len { + if let Self::Native(NativeType::Str(StrWrapper(maybe_length))) = me { + match (&index_as_usize, maybe_length) { + (ValToUsize::Ok(index), Some(str_len)) => { + if index >= str_len { bail!("index {index} too big for str of len {str_len}") } @@ -445,8 +442,10 @@ impl TypeLayout { return Ok(Cow::Owned(native_type)); } - ValToUsize::NotConstexpr => (), - ValToUsize::NaN => bail!("str cannot be indexed by a non-int"), + (ValToUsize::Ok(..), None) | (ValToUsize::NotConstexpr, _) => { + return Ok(Cow::Borrowed(&STR_TYPE)); + } + (ValToUsize::NaN, _) => bail!("str cannot be indexed by a non-int"), } } @@ -532,6 +531,14 @@ impl TypeLayout { return Some(BOOL_TYPE.to_owned()); } + if let (yes, Optional(Some(maybe)), Eq | Neq) | (Optional(Some(maybe)), yes, Eq | Neq) = + (self, other, op) + { + if yes == maybe.as_ref().as_ref() { + return Some(BOOL_TYPE.to_owned()); + } + } + let Native(me) = self.get_type_recursively() else { return None; }; @@ -544,6 +551,7 @@ impl TypeLayout { use Op::*; let matched = match (me, other, op) { + (Str(..), Str(..), Eq | Neq) => Bool, (lhs, rhs, Gt | Lt | Gte | Lte | Eq | Neq) => { if lhs == rhs { Bool diff --git a/compiler/src/ast/value.rs b/compiler/src/ast/value.rs index 2f141d9..df94e9c 100644 --- a/compiler/src/ast/value.rs +++ b/compiler/src/ast/value.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; use crate::{ parser::{AssocFileData, Node, Parser, Rule}, @@ -8,8 +8,9 @@ use crate::{ }; use super::{ - Expr, new_err, r#type::IntoType, string::AstString, CompilationState, Compile, - CompiledItem, Dependencies, Dependency, Function, Ident, List, Number, TypeLayout, UnwrapExpr, BOOL_TYPE, + new_err, r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, + Dependencies, Dependency, Expr, Function, Ident, List, Number, TypeLayout, UnwrapExpr, + BOOL_TYPE, }; #[derive(Debug)] @@ -24,6 +25,7 @@ pub(crate) enum Value { List(List), } +#[derive(Debug)] pub(crate) enum ValToUsize { Ok(usize), NotConstexpr, @@ -215,7 +217,10 @@ impl Value { let ty = math_expr.for_type()?.get_owned_type_recursively(); if let TypeLayout::Optional(None) = ty { - bail!("Hint: specify this optional's type like `{}: TYPE? = nil`", ident.name()) + bail!( + "Hint: specify this optional's type like `{}: TYPE? = nil`", + ident.name() + ) } ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; diff --git a/compiler/src/ast/while_loop.rs b/compiler/src/ast/while_loop.rs index 99e4799..f438523 100644 --- a/compiler/src/ast/while_loop.rs +++ b/compiler/src/ast/while_loop.rs @@ -90,6 +90,16 @@ impl Parser { let condition_span = condition.as_span(); let block = children.next().unwrap(); + let child_returns_type = input + .user_data() + .return_statement_expected_yield_type() + .map_or_else( + || ScopeReturnStatus::No, + |ty| ScopeReturnStatus::ParentShould(ty.clone()), + ); + + let while_loop_scope = input.user_data().push_while_loop(child_returns_type); + let condition = Self::value(condition)?; let condition_ty = condition.for_type().to_err_vec()?; @@ -102,16 +112,6 @@ impl Parser { )]); } - let child_returns_type = input - .user_data() - .return_statement_expected_yield_type() - .map_or_else( - || ScopeReturnStatus::No, - |ty| ScopeReturnStatus::ParentShould(ty.clone()), - ); - - let while_loop_scope = input.user_data().push_while_loop(child_returns_type); - let body = Self::block(block)?; while_loop_scope.consume(); diff --git a/compiler/src/tests/math.rs b/compiler/src/tests/math.rs index 478cf54..d183275 100644 --- a/compiler/src/tests/math.rs +++ b/compiler/src/tests/math.rs @@ -56,7 +56,8 @@ fn binary_operations() { #[test] fn lazy_eval() { - eval(r#" + eval( + r#" truthy = fn() -> bool { return true } @@ -72,6 +73,7 @@ fn lazy_eval() { traitor = false traitor && die() - "#) - .unwrap(); -} \ No newline at end of file + "#, + ) + .unwrap(); +} diff --git a/compiler/src/tests/optionals.rs b/compiler/src/tests/optionals.rs index 17eaf5c..1acdc11 100644 --- a/compiler/src/tests/optionals.rs +++ b/compiler/src/tests/optionals.rs @@ -3,7 +3,8 @@ use crate::eval; #[test] #[should_panic = "unwrap this optional to use its value"] fn type_mismatch() { - eval(r#" + eval( + r#" give_name = fn(input: int) -> str? { if input == 42 { return "Mateo" @@ -13,12 +14,15 @@ fn type_mismatch() { } maybe_me: str = give_name(42) - "#).unwrap(); + "#, + ) + .unwrap(); } #[test] fn optional_return_type() { - eval(r#" + eval( + r#" give_name = fn(input: int) -> str? { if input == 42 { return "Mateo" @@ -33,5 +37,114 @@ fn optional_return_type() { assert give_name(42) == "Mateo" assert give_name(1) == nil assert x == nil - "#).unwrap(); + "#, + ) + .unwrap(); +} + +#[test] +fn normal_assignment_with_try_equals() { + eval( + r#" + a = 5 + b ?= 10 + assert a + b == 15 + "#, + ) + .unwrap(); +} + +#[test] +fn assignment_as_expr() { + eval( + r#" + c: int? = nil + + a = b ?= c + + assert b == nil + assert !a + assert b == c + "#, + ) + .unwrap(); +} + + +#[test] +fn iterative_approach() { + eval( + r#" + idx = 0 + const STRING = "Hello!" + + get_char = fn(idx: int) -> str? { + if idx > 5 { + return nil + } + + return STRING[idx] + } + + result = "" + + while next ?= get_char(idx) { + idx = idx + 1 + + result = result + next * idx + } + + assert result == "Heelllllllooooo!!!!!!" + "#, + ) + .unwrap(); +} + +#[test] +fn non_null_eval() { + eval( + r#" + if a ?= 5 { + # ... + } else { + assert false + } + "#, + ) + .unwrap(); +} + + +#[test] +#[should_panic = "use of undeclared variable"] +fn proper_scoping_if() { + eval( + r#" + const SOME_VALUE: str? = "Hello!" + + if a ?= SOME_VALUE { + # ... + } + + assert a == "Hello!" + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic = "use of undeclared variable"] +fn proper_scoping_while() { + eval( + r#" + const SOME_VALUE: str? = "Hello!" + + while xyz ?= SOME_VALUE { + # break + } + + assert xyz == "Hello!" + "#, + ) + .unwrap(); } \ No newline at end of file diff --git a/examples/main/optionals/unwrap.ms b/examples/main/optionals/unwrap.ms index ad6ea37..beef287 100644 --- a/examples/main/optionals/unwrap.ms +++ b/examples/main/optionals/unwrap.ms @@ -10,4 +10,5 @@ if bbb ?= a { x = 5 y ?= 10 -print x + y \ No newline at end of file +print x + y + From 32b9c27895acc3ddddc508e8344fccbe8e054978 Mon Sep 17 00:00:00 2001 From: mrodz <79176075+mrodz@users.noreply.github.com> Date: Tue, 17 Oct 2023 22:16:24 -0700 Subject: [PATCH 6/7] unwrap without fallback --- bytecode/src/instruction.rs | 24 ++++++++++ bytecode/src/instruction_constants.rs | 5 +- compiler/src/ast.rs | 2 +- compiler/src/ast/assignment.rs | 15 +----- compiler/src/ast/optionals.rs | 69 ++++++++++++++++++++++++++- compiler/src/ast/value.rs | 21 ++++---- compiler/src/grammar.pest | 28 ++--------- examples/main/optionals/get.ms | 5 ++ 8 files changed, 119 insertions(+), 50 deletions(-) create mode 100644 examples/main/optionals/get.ms diff --git a/bytecode/src/instruction.rs b/bytecode/src/instruction.rs index 03b86fd..18ca142 100644 --- a/bytecode/src/instruction.rs +++ b/bytecode/src/instruction.rs @@ -776,14 +776,21 @@ pub mod implementations { let status = match primitive { Primitive::Optional(Some(unwrapped)) => { let var = unwrapped.as_ref().to_owned(); + + let var = unsafe { var.move_out_of_heap_primitive() }; + ctx.register_variable_local(name.to_owned(), var)?; true } primitive @ Primitive::Optional(None) => { + let primitive = unsafe { primitive.move_out_of_heap_primitive() }; + ctx.register_variable_local(name.to_owned(), primitive)?; false }, other_primitive => { + let other_primitive = unsafe { other_primitive.move_out_of_heap_primitive() }; + ctx.register_variable_local(name.to_owned(), other_primitive)?; true } @@ -793,6 +800,23 @@ pub mod implementations { Ok(()) } + + unwrap(ctx, args) { + let Some(primitive) = ctx.get_last_op_item_mut() else { + bail!("`unwrap` requires a primitive at the top of the local operating stack"); + }; + + if let Primitive::Optional(optional) = primitive { + if let Some(new_primitive) = optional { + *primitive = *new_primitive.clone(); + } else { + let span = args.get(0).map(String::as_str); + bail!("LOGIC ERROR IN CODE >> {} >> unwrap of `nil`", span.unwrap_or("")); + } + } + + Ok(()) + } } instruction! { diff --git a/bytecode/src/instruction_constants.rs b/bytecode/src/instruction_constants.rs index e20e3ad..2eb5099 100644 --- a/bytecode/src/instruction_constants.rs +++ b/bytecode/src/instruction_constants.rs @@ -22,7 +22,7 @@ pub static REPR_TO_BIN: Lazy> = Lazy::new(|| { /// /// Saving this as a constant makes it harder for the arrays to fall out of sync /// by requiring that they both take the same size. -pub const INSTRUCTION_COUNT: usize = 63; +pub const INSTRUCTION_COUNT: usize = 64; /// This is an array that provides O(1) lookups of names from bytes. pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ @@ -89,6 +89,7 @@ pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ /* 0x3C [60] */ b"export_special", /* 0x3D [61] */ b"load_self_export", /* 0x3E [62] */ b"unwrap_into", + /* 0x3F [63] */ b"unwrap", ]; /// Similar to [`BIN_TO_REPR`][crate::instruction_constants::BIN_TO_REPR], @@ -157,6 +158,7 @@ pub static FUNCTION_POINTER_LOOKUP: [InstructionSignature; INSTRUCTION_COUNT] = implementations::export_special, implementations::load_self_export, implementations::unwrap_into, + implementations::unwrap, ]; pub mod id { @@ -226,4 +228,5 @@ pub mod id { pub const EXPORT_SELF: u8 = 60; pub const LOAD_SELF_EXPORT: u8 = 61; pub const UNWRAP_INTO: u8 = 62; + pub const UNWRAP: u8 = 63; } diff --git a/compiler/src/ast.rs b/compiler/src/ast.rs index ddb1a60..0d9221b 100644 --- a/compiler/src/ast.rs +++ b/compiler/src/ast.rs @@ -42,7 +42,7 @@ pub(crate) use loop_control_flow::{Break, Continue}; pub(crate) use math_expr::{Expr, Op as BinaryOperation}; pub(crate) use number::Number; pub(crate) use number_loop::NumberLoop; -pub(crate) use optionals::UnwrapExpr; +pub(crate) use optionals::{Unwrap, UnwrapExpr}; pub(crate) use print_statement::PrintStatement; pub(crate) use r#return::ReturnStatement; pub(crate) use r#type::TypeLayout; diff --git a/compiler/src/ast/assignment.rs b/compiler/src/ast/assignment.rs index 056e264..775fcdb 100644 --- a/compiler/src/ast/assignment.rs +++ b/compiler/src/ast/assignment.rs @@ -149,22 +149,9 @@ impl Compile for Assignment { let mut value_init = if let ConstexprEvaluation::Owned(value) = maybe_constexpr_eval { value.compile(state)? } else { - match &self.value() { - Value::Ident(ident) => ident.compile(state)?, - Value::Function(function) => function.in_place_compile_for_value(state)?, - Value::Number(number) => number.compile(state)?, - Value::String(string) => string.compile(state)?, - Value::MathExpr(math_expr) => math_expr.compile(state)?, - Value::Boolean(boolean) => boolean.compile(state)?, - Value::List(list) => list.compile(state)?, - Value::UnwrapExpr(unwrap_expr) => unwrap_expr.compile(state)?, - } + self.value().compile(state)? }; - // if let Some(ref next) = &self.value { - // value_init.append(&mut next.compile(function_buffer)?); - // } - match self { Self::Single { ident, .. } => { let name = ident.name(); diff --git a/compiler/src/ast/optionals.rs b/compiler/src/ast/optionals.rs index 8b6f125..0751734 100644 --- a/compiler/src/ast/optionals.rs +++ b/compiler/src/ast/optionals.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use crate::{ - ast::{new_err, r#type::IntoType}, + ast::{new_err, r#type::IntoType, Dependency}, instruction, parser::{Node, Parser}, VecErr, @@ -15,7 +15,58 @@ pub(crate) struct UnwrapExpr { value: Box, } -impl Dependencies for UnwrapExpr {} +#[derive(Debug)] +pub(crate) enum Unwrap { + Fallible(Box, String), +} + +impl Unwrap { + pub fn value(&self) -> &Value { + match self { + Self::Fallible(value, ..) => value.as_ref(), + } + } +} + +impl Compile for Unwrap { + fn compile(&self, state: &super::CompilationState) -> anyhow::Result, anyhow::Error> { + match self { + Self::Fallible(value, span) => { + let mut value_compiled = value.compile(state)?; + + value_compiled.push(instruction!(unwrap span)); + + Ok(value_compiled) + } + } + } +} + +impl IntoType for Unwrap { + fn for_type(&self) -> anyhow::Result { + self.value().for_type() + } +} + +impl Dependencies for Unwrap { + fn dependencies(&self) -> Vec { + self.value().net_dependencies() + } + + fn supplies(&self) -> Vec { + self.value().supplies() + } +} + +impl Dependencies for UnwrapExpr { + fn supplies(&self) -> Vec { + vec![Dependency::new(Cow::Borrowed(&self.ident))] + } + + fn dependencies(&self) -> Vec { + self.value.net_dependencies() + } +} impl Compile for UnwrapExpr { fn compile( @@ -33,6 +84,20 @@ impl Compile for UnwrapExpr { } impl Parser { + pub fn unwrap(input: Node) -> Result> { + let value_node = input.children().single().expect("`get` found multiple `value` nodes"); + + let value_span = value_node.as_span(); + + let (line, col) = value_span.start_pos().line_col(); + + let value = Self::value(value_node)?; + + let span = format!("{}:{line}:{col}", &input.user_data().get_source_file_name(), ); + + Ok(Unwrap::Fallible(Box::new(value), span)) + } + pub fn unwrap_expr(input: Node) -> Result> { let mut children = input.children(); diff --git a/compiler/src/ast/value.rs b/compiler/src/ast/value.rs index df94e9c..6d7bbf1 100644 --- a/compiler/src/ast/value.rs +++ b/compiler/src/ast/value.rs @@ -8,8 +8,8 @@ use crate::{ }; use super::{ - new_err, r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, - Dependencies, Dependency, Expr, Function, Ident, List, Number, TypeLayout, UnwrapExpr, + r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, + Dependencies, Dependency, Expr, Function, Ident, List, Number, TypeLayout, Unwrap, UnwrapExpr, BOOL_TYPE, }; @@ -21,6 +21,7 @@ pub(crate) enum Value { String(AstString), MathExpr(Box), UnwrapExpr(UnwrapExpr), + Unwrap(Unwrap), Boolean(bool), List(List), } @@ -236,6 +237,10 @@ impl Value { Value::UnwrapExpr(..) => { ident.link_force_no_inherit(user_data, Cow::Borrowed(&BOOL_TYPE))?; } + Value::Unwrap(unwrap) => { + let ty = unwrap.for_type()?.get_owned_type_recursively(); + ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; + } } Ok(()) @@ -252,6 +257,7 @@ impl IntoType for Value { Self::String(string) => string.for_type(), Self::Boolean(boolean) => boolean.for_type(), Self::List(list) => list.for_type(), + Self::Unwrap(unwrap_expr) => unwrap_expr.for_type(), Self::UnwrapExpr(..) => Ok(BOOL_TYPE.to_owned()), } } @@ -269,6 +275,7 @@ impl Dependencies for Value { Self::Boolean(boolean) => boolean.net_dependencies(), Self::List(list) => list.net_dependencies(), Self::UnwrapExpr(unwrap_expr) => unwrap_expr.net_dependencies(), + Self::Unwrap(unwrap) => unwrap.net_dependencies(), } } } @@ -284,6 +291,7 @@ impl Compile for Value { Self::Boolean(boolean) => boolean.compile(state), Self::List(list) => list.compile(state), Self::UnwrapExpr(unwrap_expr) => unwrap_expr.compile(state), + Self::Unwrap(unwrap) => unwrap.compile(state), } } } @@ -321,13 +329,8 @@ impl Parser { Rule::list => Value::List(Self::list(value)?), Rule::WHITESPACE => unreachable!("{:?}", value.as_span()), Rule::unwrap_expr => Value::UnwrapExpr(Self::unwrap_expr(value)?), - x => { - return Err(vec![new_err( - input.as_span(), - &input.user_data().get_source_file_name(), - format!("not sure how to handle `{x:?}` :("), - )])? - } + Rule::value_unwrap => Value::Unwrap(Self::unwrap(value)?), + x => unreachable!("not sure how to handle `{x:?}`"), }; Ok(matched) diff --git a/compiler/src/grammar.pest b/compiler/src/grammar.pest index a1a80a2..7e556d2 100644 --- a/compiler/src/grammar.pest +++ b/compiler/src/grammar.pest @@ -7,26 +7,6 @@ block_comment = _{ "###" ~ (!"###" ~ ANY)* ~ "###" } COMMENT = _{ block_comment | line_comment } -keywords = _{ - "fn" | - "obj" | - "print" | - "return" | - "if" | - "else" | - "true" | - "false" | - "modify" | - "const" | - "self" | - "while" | - "assert" | - "class" | - "constructor" | - "Self" - -} - integer = @{ '0'..'9' ~ ('0'..'9' | ("_" ~ ('0'..'9')))* } hex_digit = _{ '0'..'9' | 'a'..'f' | 'A'..'F' } @@ -37,7 +17,7 @@ float = @{ (integer ~ "." ~ integer) | (integer ~ ("f" | "F")) } bigint = @{ "B" ~ (hex_int | integer) } number = { bigint | hex_int | byte | float | integer } -boolean = { "true" | "false" } +boolean = @{ "true" | "false" } unary_minus = { "-" } not = { "!" } @@ -80,7 +60,7 @@ string = @{ "\"" ~ (("\\\"") | (!("\"") ~ ANY))* ~ "\"" } ident_chars = _{ (('a'..'z') | ('0'..'9') | ('A'..'Z') | "_") } -nil = { "nil" } +nil = @{ "nil" } ident = @{ !('0'..'9') ~ ident_chars+ } optional_modifier = { "?" } type = { (function_type | list_type_open_only | list_type | ident) ~ optional_modifier? } @@ -107,7 +87,9 @@ list_index = { ("[" ~ value ~ "]")+ } unwrap = _{ "?=" } unwrap_expr = { ident ~ unwrap ~ value } -value = { function | unwrap_expr | math_expr | ident | list } +value_unwrap = ${ "get" ~ WHITESPACE ~ value } + +value = { function | unwrap_expr | value_unwrap | math_expr | ident | list } assignment_flag = { "modify" | "const" } // bad_assignment_flag = { &ident } diff --git a/examples/main/optionals/get.ms b/examples/main/optionals/get.ms new file mode 100644 index 0000000..c5f40c9 --- /dev/null +++ b/examples/main/optionals/get.ms @@ -0,0 +1,5 @@ +empty: int? = nil + +a = get empty + +print a \ No newline at end of file From e1e54fb2b638ff2169971e1406ff93872196f24f Mon Sep 17 00:00:00 2001 From: mrodz <79176075+mrodz@users.noreply.github.com> Date: Wed, 18 Oct 2023 00:16:49 -0700 Subject: [PATCH 7/7] `get` keyword, unit tests, bug fixes, cargo fmt --- bytecode/src/instruction.rs | 21 +++++ bytecode/src/instruction_constants.rs | 5 +- compiler/src/ast/declaration.rs | 14 ++- compiler/src/ast/function_arguments.rs | 2 +- compiler/src/ast/optionals.rs | 73 +++++++++++++--- compiler/src/ast/type.rs | 4 + compiler/src/ast/value.rs | 5 +- compiler/src/grammar.pest | 4 +- compiler/src/tests/optionals.rs | 113 ++++++++++++++++++++++++- examples/main/optionals/get.ms | 2 +- 10 files changed, 216 insertions(+), 27 deletions(-) diff --git a/bytecode/src/instruction.rs b/bytecode/src/instruction.rs index 18ca142..c1ccf2c 100644 --- a/bytecode/src/instruction.rs +++ b/bytecode/src/instruction.rs @@ -817,6 +817,27 @@ pub mod implementations { Ok(()) } + + jmp_not_nil(ctx, args) { + let Some(primitive) = ctx.get_last_op_item_mut() else { + bail!("`jmp_not_nil` requires a primitive at the top of the local operating stack"); + }; + + let Some(lines_to_jump) = args.get(0) else { + bail!("`jmp_not_nil` requires lines_to_jump"); + }; + + let lines_to_jump = lines_to_jump.parse::().context("jmp_not_nil needs lines_to_jump: isize")?; + + if let Primitive::Optional(None) = primitive { + ctx.pop(); + return Ok(()) + } + + ctx.signal(InstructionExitState::Goto(lines_to_jump)); + + Ok(()) + } } instruction! { diff --git a/bytecode/src/instruction_constants.rs b/bytecode/src/instruction_constants.rs index 2eb5099..f4bc63f 100644 --- a/bytecode/src/instruction_constants.rs +++ b/bytecode/src/instruction_constants.rs @@ -22,7 +22,7 @@ pub static REPR_TO_BIN: Lazy> = Lazy::new(|| { /// /// Saving this as a constant makes it harder for the arrays to fall out of sync /// by requiring that they both take the same size. -pub const INSTRUCTION_COUNT: usize = 64; +pub const INSTRUCTION_COUNT: usize = 65; /// This is an array that provides O(1) lookups of names from bytes. pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ @@ -90,6 +90,7 @@ pub static BIN_TO_REPR: [&[u8]; INSTRUCTION_COUNT] = [ /* 0x3D [61] */ b"load_self_export", /* 0x3E [62] */ b"unwrap_into", /* 0x3F [63] */ b"unwrap", + /* 0x40 [64] */ b"jmp_not_nil", ]; /// Similar to [`BIN_TO_REPR`][crate::instruction_constants::BIN_TO_REPR], @@ -159,6 +160,7 @@ pub static FUNCTION_POINTER_LOOKUP: [InstructionSignature; INSTRUCTION_COUNT] = implementations::load_self_export, implementations::unwrap_into, implementations::unwrap, + implementations::jmp_not_nil, ]; pub mod id { @@ -229,4 +231,5 @@ pub mod id { pub const LOAD_SELF_EXPORT: u8 = 61; pub const UNWRAP_INTO: u8 = 62; pub const UNWRAP: u8 = 63; + pub const JMP_NOT_NIL: u8 = 64; } diff --git a/compiler/src/ast/declaration.rs b/compiler/src/ast/declaration.rs index 747e3d0..fc0faaf 100644 --- a/compiler/src/ast/declaration.rs +++ b/compiler/src/ast/declaration.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ class::Class, Assertion, Assignment, Break, CompilationState, Compile, CompiledItem, Continue, Dependencies, Dependency, Expr, IfStatement, NumberLoop, PrintStatement, Reassignment, - ReturnStatement, UnwrapExpr, WhileLoop, + ReturnStatement, Unwrap, UnwrapExpr, WhileLoop, }; #[derive(Debug)] @@ -29,6 +29,7 @@ pub(crate) enum Declaration { Assertion(Assertion), Class(Class), UnwrapExpr(UnwrapExpr), + ValueUnwrap(Unwrap), } impl Dependencies for Declaration { @@ -44,7 +45,9 @@ impl Dependencies for Declaration { Self::Assertion(assertion) => assertion.supplies(), Self::Class(class) => class.supplies(), Self::UnwrapExpr(unwrap_expr) => unwrap_expr.supplies(), - Self::Reassignment(_) | Self::Continue(_) | Self::Break(_) => vec![], + Self::ValueUnwrap(value_unwrap) => value_unwrap.supplies(), + Self::Reassignment(reassignment) => reassignment.supplies(), + Self::Continue(_) | Self::Break(_) => vec![], } } @@ -61,6 +64,7 @@ impl Dependencies for Declaration { Self::Assertion(assertion) => assertion.net_dependencies(), Self::Class(class) => class.net_dependencies(), Self::UnwrapExpr(unwrap_expr) => unwrap_expr.net_dependencies(), + Self::ValueUnwrap(value_unwrap) => value_unwrap.net_dependencies(), Self::Continue(_) | Self::Break(_) => vec![], } } @@ -90,6 +94,11 @@ impl Compile for Declaration { unwrap_expr_compiled.push(instruction!(void)); Ok(unwrap_expr_compiled) } + Self::ValueUnwrap(x) => { + let mut unwrap_compiled = x.compile(state)?; + unwrap_compiled.push(instruction!(void)); + Ok(unwrap_compiled) + } } } } @@ -120,6 +129,7 @@ impl Parser { Rule::assertion => Declaration::Assertion(Self::assertion(declaration)?), Rule::class => Declaration::Class(Self::class(declaration)?), Rule::unwrap_expr => Declaration::UnwrapExpr(Self::unwrap_expr(declaration)?), + Rule::value_unwrap => Declaration::ValueUnwrap(Self::unwrap(declaration)?), x => unreachable!("{x:?} is not supported"), }; diff --git a/compiler/src/ast/function_arguments.rs b/compiler/src/ast/function_arguments.rs index 7997d7a..a23a058 100644 --- a/compiler/src/ast/function_arguments.rs +++ b/compiler/src/ast/function_arguments.rs @@ -64,7 +64,7 @@ impl Parser { result_len += 1; - if !expected_ty_at_idx.eq_complex(&user_gave, maybe_class_type, false) { + if !expected_ty_at_idx.eq_complex(user_gave, maybe_class_type, false) { let argument_number = idx + 1; let error_message = format!("type mismatch when calling function (argument #{argument_number} was expected to be `{expected_ty_at_idx}` based on type signature, instead found `{user_gave}`)"); errors.push(new_err( diff --git a/compiler/src/ast/optionals.rs b/compiler/src/ast/optionals.rs index 0751734..6f48cf6 100644 --- a/compiler/src/ast/optionals.rs +++ b/compiler/src/ast/optionals.rs @@ -7,7 +7,7 @@ use crate::{ VecErr, }; -use super::{Compile, Dependencies, Ident, Value}; +use super::{Compile, Dependencies, Ident, TypeLayout, Value}; #[derive(Debug)] pub(crate) struct UnwrapExpr { @@ -17,25 +17,49 @@ pub(crate) struct UnwrapExpr { #[derive(Debug)] pub(crate) enum Unwrap { - Fallible(Box, String), + Fallible { + value: Box, + span: String, + }, + Infallible { + value: Box, + fallback: Box, + }, } impl Unwrap { pub fn value(&self) -> &Value { match self { - Self::Fallible(value, ..) => value.as_ref(), + Self::Fallible { value, .. } => value.as_ref(), + Self::Infallible { value, .. } => value.as_ref(), } } } impl Compile for Unwrap { - fn compile(&self, state: &super::CompilationState) -> anyhow::Result, anyhow::Error> { + fn compile( + &self, + state: &super::CompilationState, + ) -> anyhow::Result, anyhow::Error> { match self { - Self::Fallible(value, span) => { + Self::Fallible { value, span } => { let mut value_compiled = value.compile(state)?; value_compiled.push(instruction!(unwrap span)); - + + Ok(value_compiled) + } + Self::Infallible { value, fallback } => { + let mut value_compiled = value.compile(state)?; + + let mut fallback_compiled = fallback.compile(state)?; + + let instructions_to_skip = fallback_compiled.len() + 1; + + value_compiled.push(instruction!(jmp_not_nil instructions_to_skip)); + + value_compiled.append(&mut fallback_compiled); + Ok(value_compiled) } } @@ -44,7 +68,17 @@ impl Compile for Unwrap { impl IntoType for Unwrap { fn for_type(&self) -> anyhow::Result { - self.value().for_type() + let ty = self.value().for_type()?; + + if let (true, unwrapped_ty) = ty.is_optional() { + if let Some(type_exists) = unwrapped_ty { + return Ok(type_exists.clone().into_owned()); + } else { + return Ok(TypeLayout::Optional(None)); + } + } + + Ok(ty) } } @@ -85,17 +119,28 @@ impl Compile for UnwrapExpr { impl Parser { pub fn unwrap(input: Node) -> Result> { - let value_node = input.children().single().expect("`get` found multiple `value` nodes"); + let mut children = input.children(); + let value_node = children.next().unwrap(); let value_span = value_node.as_span(); - let (line, col) = value_span.start_pos().line_col(); - let value = Self::value(value_node)?; - let span = format!("{}:{line}:{col}", &input.user_data().get_source_file_name(), ); + if let Some(fallback) = children.next() { + let fallback = Self::value(fallback)?; + + return Ok(Unwrap::Infallible { + value: Box::new(value), + fallback: Box::new(fallback), + }); + } + + let span = format!("{}:{line}:{col}", &input.user_data().get_source_file_name(),); - Ok(Unwrap::Fallible(Box::new(value), span)) + Ok(Unwrap::Fallible { + value: Box::new(value), + span, + }) } pub fn unwrap_expr(input: Node) -> Result> { @@ -118,7 +163,7 @@ impl Parser { match optional_check { (true, Some(known_type)) => ident - .link_force_no_inherit(&user_data, known_type.clone()) + .link_force_no_inherit(user_data, known_type.clone()) .to_err_vec()?, (true, _) => { return Err(vec![new_err( @@ -128,7 +173,7 @@ impl Parser { )]) } _ => ident - .link_force_no_inherit(&user_data, Cow::Owned(value_ty)) + .link_force_no_inherit(user_data, Cow::Owned(value_ty)) .to_err_vec()?, } diff --git a/compiler/src/ast/type.rs b/compiler/src/ast/type.rs index 13e93f3..a8bd196 100644 --- a/compiler/src/ast/type.rs +++ b/compiler/src/ast/type.rs @@ -527,6 +527,10 @@ impl TypeLayout { pub fn get_output_type(&self, other: &Self, op: &Op) -> Option { use TypeLayout::*; + if self == other && matches!(op, Eq | Neq) { + return Some(BOOL_TYPE.to_owned()); + } + if let (_, Optional(None), Eq | Neq) | (Optional(None), _, Eq | Neq) = (self, other, op) { return Some(BOOL_TYPE.to_owned()); } diff --git a/compiler/src/ast/value.rs b/compiler/src/ast/value.rs index 6d7bbf1..8a88c73 100644 --- a/compiler/src/ast/value.rs +++ b/compiler/src/ast/value.rs @@ -8,9 +8,8 @@ use crate::{ }; use super::{ - r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, - Dependencies, Dependency, Expr, Function, Ident, List, Number, TypeLayout, Unwrap, UnwrapExpr, - BOOL_TYPE, + r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, Dependencies, + Dependency, Expr, Function, Ident, List, Number, TypeLayout, Unwrap, UnwrapExpr, BOOL_TYPE, }; #[derive(Debug)] diff --git a/compiler/src/grammar.pest b/compiler/src/grammar.pest index 7e556d2..e54d2f1 100644 --- a/compiler/src/grammar.pest +++ b/compiler/src/grammar.pest @@ -87,7 +87,7 @@ list_index = { ("[" ~ value ~ "]")+ } unwrap = _{ "?=" } unwrap_expr = { ident ~ unwrap ~ value } -value_unwrap = ${ "get" ~ WHITESPACE ~ value } +value_unwrap = ${ "get" ~ WHITESPACE ~ value ~ (WHITESPACE ~ "or" ~ WHITESPACE ~ value)? } value = { function | unwrap_expr | value_unwrap | math_expr | ident | list } @@ -144,7 +144,7 @@ class_body = { "{" ~ class_feature* ~ "}" } class = { "class" ~ ident ~ class_body } -declaration = { class | reassignment | assignment | unwrap_expr | print_statement | assertion | return_statement | continue_statement | break_statement | if_statement | while_loop | number_loop | math_expr } +declaration = { class | reassignment | assignment | unwrap_expr | value_unwrap | print_statement | assertion | return_statement | continue_statement | break_statement | if_statement | while_loop | number_loop | math_expr } file = { SOI ~ diff --git a/compiler/src/tests/optionals.rs b/compiler/src/tests/optionals.rs index 1acdc11..6fdef57 100644 --- a/compiler/src/tests/optionals.rs +++ b/compiler/src/tests/optionals.rs @@ -70,7 +70,6 @@ fn assignment_as_expr() { .unwrap(); } - #[test] fn iterative_approach() { eval( @@ -114,7 +113,6 @@ fn non_null_eval() { .unwrap(); } - #[test] #[should_panic = "use of undeclared variable"] fn proper_scoping_if() { @@ -147,4 +145,113 @@ fn proper_scoping_while() { "#, ) .unwrap(); -} \ No newline at end of file +} + +#[test] +fn get_keyword() { + eval( + r#" + + coolest_person: str? = nil + + const LIKES_GREEN = true + const CAN_CODE = true + const CAN_COOK = true + + if LIKES_GREEN && CAN_CODE && CAN_COOK { + coolest_person = "Mom" + } + + name: str = get coolest_person + + assert name == "Mom" + assert coolest_person == name + "#, + ) + .unwrap(); +} + +#[test] +fn get_or_simple() { + eval( + r#" + x = get nil or 5 + assert x == 5 + "#, + ) + .unwrap(); +} + +#[test] +fn get_or_complex() { + eval( + r#" + give_booze = fn(age: int) -> str? { + if age < 21 { + return nil + } + + return "Moonshine" + } + + first_label = get give_booze(16) or "Oops! You're underage" + assert first_label == "Oops! You're underage" + + first_label = get give_booze(55) or "shhh this will never emerge" + assert first_label == "Moonshine" + + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic = "[Interpreter crashed]"] +fn abort_store() { + eval( + r#" + y = get nil + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic = "[Interpreter crashed]"] +fn abort_declaration() { + eval( + r#" + get nil + "#, + ) + .unwrap(); +} + +#[test] +fn lazy_eval() { + eval( + r#" + die = fn() { + assert false + } + + get "liberty" or die() + "#, + ) + .unwrap(); +} + +#[test] +#[should_panic] +fn abort_in_or() { + eval( + r#" + die = fn() { + assert false + } + + get nil or die() + "#, + ) + .unwrap(); +} diff --git a/examples/main/optionals/get.ms b/examples/main/optionals/get.ms index c5f40c9..06ef71e 100644 --- a/examples/main/optionals/get.ms +++ b/examples/main/optionals/get.ms @@ -1,5 +1,5 @@ empty: int? = nil -a = get empty +a = get empty or 5 print a \ No newline at end of file