diff --git a/bytecode/src/instruction.rs b/bytecode/src/instruction.rs index 5b0bff0..c1ccf2c 100644 --- a/bytecode/src/instruction.rs +++ b/bytecode/src/instruction.rs @@ -763,6 +763,83 @@ 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(); + + 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 + } + }; + + ctx.push(bool!(status)); + + 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(()) + } + + 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! { store(ctx, args) { let Some(name) = args.first() else { @@ -1017,7 +1094,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/instruction_constants.rs b/bytecode/src/instruction_constants.rs index 634c109..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 = 62; +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] = [ @@ -88,6 +88,9 @@ 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", + /* 0x3F [63] */ b"unwrap", + /* 0x40 [64] */ b"jmp_not_nil", ]; /// Similar to [`BIN_TO_REPR`][crate::instruction_constants::BIN_TO_REPR], @@ -155,6 +158,9 @@ pub static FUNCTION_POINTER_LOOKUP: [InstructionSignature; INSTRUCTION_COUNT] = implementations::export_name, implementations::export_special, implementations::load_self_export, + implementations::unwrap_into, + implementations::unwrap, + implementations::jmp_not_nil, ]; pub mod id { @@ -223,4 +229,7 @@ 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; + pub const UNWRAP: u8 = 63; + pub const JMP_NOT_NIL: u8 = 64; } diff --git a/bytecode/src/variables/primitive.rs b/bytecode/src/variables/primitive.rs index dceb4d9..3668110 100644 --- a/bytecode/src/variables/primitive.rs +++ b/bytecode/src/variables/primitive.rs @@ -166,12 +166,20 @@ impl Primitive { }; } + use Primitive as P; + + if let (P::Optional(maybe), yes) | (yes, P::Optional(maybe)) = (self, rhs) { + 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)); 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. @@ -349,7 +357,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.rs b/compiler/src/ast.rs index c04872f..0d9221b 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::{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 2ca32f5..775fcdb 100644 --- a/compiler/src/ast/assignment.rs +++ b/compiler/src/ast/assignment.rs @@ -149,21 +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)?, - } + 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/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/assignment/assignment_type.rs b/compiler/src/ast/assignment/assignment_type.rs index 36f7f8f..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()) - { + 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 33ca58d..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, WhileLoop, + ReturnStatement, Unwrap, UnwrapExpr, WhileLoop, }; #[derive(Debug)] @@ -28,6 +28,8 @@ pub(crate) enum Declaration { Break(Break), Assertion(Assertion), Class(Class), + UnwrapExpr(UnwrapExpr), + ValueUnwrap(Unwrap), } impl Dependencies for Declaration { @@ -42,7 +44,10 @@ impl Dependencies for Declaration { Self::NumberLoop(number_loop) => number_loop.supplies(), Self::Assertion(assertion) => assertion.supplies(), Self::Class(class) => class.supplies(), - Self::Reassignment(_) | Self::Continue(_) | Self::Break(_) => vec![], + Self::UnwrapExpr(unwrap_expr) => unwrap_expr.supplies(), + Self::ValueUnwrap(value_unwrap) => value_unwrap.supplies(), + Self::Reassignment(reassignment) => reassignment.supplies(), + Self::Continue(_) | Self::Break(_) => vec![], } } @@ -58,6 +63,8 @@ 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::ValueUnwrap(value_unwrap) => value_unwrap.net_dependencies(), Self::Continue(_) | Self::Break(_) => vec![], } } @@ -82,6 +89,16 @@ 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) + } + Self::ValueUnwrap(x) => { + let mut unwrap_compiled = x.compile(state)?; + unwrap_compiled.push(instruction!(void)); + Ok(unwrap_compiled) + } } } } @@ -111,6 +128,8 @@ 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)?), + 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 aeb1e24..a23a058 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/if_statement.rs b/compiler/src/ast/if_statement.rs index cfd5a53..c332ba2 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/math_expr.rs b/compiler/src/ast/math_expr.rs index 1260110..f78ad72 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 => "?=", } } } @@ -135,6 +138,7 @@ pub(crate) enum CallableContents { #[derive(Debug)] pub(crate) enum Expr { + Nil, Value(Value), ReferenceToSelf, ReferenceToConstructor(ClassType), @@ -228,6 +232,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 +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)), } } } @@ -307,6 +313,7 @@ impl Dependencies for Expr { E::DotLookup { lhs, .. } => lhs.net_dependencies(), E::ReferenceToSelf => vec![], E::ReferenceToConstructor(..) => vec![], + E::Nil => vec![], } } } @@ -433,6 +440,7 @@ fn compile_depth( result.append(&mut dot_chain.compile(state)?); Ok(result) } + Expr::Nil => Ok(vec![instruction!(reserve_primitive)]), } } @@ -458,6 +466,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)) @@ -530,6 +539,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..6f48cf6 --- /dev/null +++ b/compiler/src/ast/optionals.rs @@ -0,0 +1,185 @@ +use std::borrow::Cow; + +use crate::{ + ast::{new_err, r#type::IntoType, Dependency}, + instruction, + parser::{Node, Parser}, + VecErr, +}; + +use super::{Compile, Dependencies, Ident, TypeLayout, Value}; + +#[derive(Debug)] +pub(crate) struct UnwrapExpr { + ident: Ident, + value: Box, +} + +#[derive(Debug)] +pub(crate) enum Unwrap { + 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::Infallible { 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) + } + 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) + } + } + } +} + +impl IntoType for Unwrap { + fn for_type(&self) -> anyhow::Result { + 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) + } +} + +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( + &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(input: Node) -> Result> { + 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)?; + + 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 { + value: Box::new(value), + span, + }) + } + + 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(); + + 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), + }) + } +} 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..a8bd196 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}; @@ -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"), } } @@ -195,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(); @@ -214,6 +227,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, @@ -379,10 +393,13 @@ impl TypeLayout { ) } + /// - `Self` is the expected type + /// - `rhs` is the supplied type pub fn eq_complex( &self, rhs: &Self, executing_class: Option>, + lhs_allow_optional_unwrap: bool, ) -> bool { if self == rhs { return true; @@ -393,6 +410,12 @@ 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, } } @@ -400,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}") } @@ -419,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"), } } @@ -502,6 +527,22 @@ 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()); + } + + 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; }; @@ -514,6 +555,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 @@ -600,39 +642,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(); @@ -729,7 +738,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 +778,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/ast/value.rs b/compiler/src/ast/value.rs index ecbf78b..8a88c73 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::{bail, Result}; 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, + r#type::IntoType, string::AstString, CompilationState, Compile, CompiledItem, Dependencies, + Dependency, Expr, Function, Ident, List, Number, TypeLayout, Unwrap, UnwrapExpr, BOOL_TYPE, }; #[derive(Debug)] @@ -19,10 +19,13 @@ pub(crate) enum Value { Number(Number), String(AstString), MathExpr(Box), + UnwrapExpr(UnwrapExpr), + Unwrap(Unwrap), Boolean(bool), List(List), } +#[derive(Debug)] pub(crate) enum ValToUsize { Ok(usize), NotConstexpr, @@ -186,64 +189,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 +215,14 @@ 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 +233,13 @@ 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))?; + } + Value::Unwrap(unwrap) => { + let ty = unwrap.for_type()?.get_owned_type_recursively(); + ident.link_force_no_inherit(user_data, Cow::Owned(ty))?; + } } Ok(()) @@ -295,6 +256,8 @@ 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()), } } } @@ -310,6 +273,8 @@ 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(), + Self::Unwrap(unwrap) => unwrap.net_dependencies(), } } } @@ -324,6 +289,8 @@ 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), + Self::Unwrap(unwrap) => unwrap.compile(state), } } } @@ -360,13 +327,9 @@ impl Parser { // Rule::callable => Value::Callable(Self::callable(value)?), Rule::list => Value::List(Self::list(value)?), Rule::WHITESPACE => unreachable!("{:?}", value.as_span()), - x => { - return Err(vec![new_err( - input.as_span(), - &input.user_data().get_source_file_name(), - format!("not sure how to handle `{x:?}` :("), - )])? - } + Rule::unwrap_expr => Value::UnwrapExpr(Self::unwrap_expr(value)?), + Rule::value_unwrap => Value::Unwrap(Self::unwrap(value)?), + x => unreachable!("not sure how to handle `{x:?}`"), }; Ok(matched) 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/grammar.pest b/compiler/src/grammar.pest index 7d57e22..e54d2f1 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,14 +17,14 @@ 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 = { "!" } 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 +60,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)*)? } @@ -102,7 +84,12 @@ 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_unwrap = ${ "get" ~ WHITESPACE ~ value ~ (WHITESPACE ~ "or" ~ WHITESPACE ~ value)? } + +value = { function | unwrap_expr | value_unwrap | math_expr | ident | list } assignment_flag = { "modify" | "const" } // bad_assignment_flag = { &ident } @@ -157,7 +144,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 | 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.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/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 new file mode 100644 index 0000000..6fdef57 --- /dev/null +++ b/compiler/src/tests/optionals.rs @@ -0,0 +1,257 @@ +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(); +} + +#[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(); +} + +#[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(); +} + +#[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/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 diff --git a/examples/main/optionals/get.ms b/examples/main/optionals/get.ms new file mode 100644 index 0000000..06ef71e --- /dev/null +++ b/examples/main/optionals/get.ms @@ -0,0 +1,5 @@ +empty: int? = nil + +a = get empty or 5 + +print a \ 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..ce762ea --- /dev/null +++ b/examples/main/optionals/some_none.ms @@ -0,0 +1,16 @@ +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 + +print nil == 5 \ No newline at end of file diff --git a/examples/main/optionals/unwrap.ms b/examples/main/optionals/unwrap.ms new file mode 100644 index 0000000..beef287 --- /dev/null +++ b/examples/main/optionals/unwrap.ms @@ -0,0 +1,14 @@ +a: int? = nil + +if bbb ?= a { + print bbb +} else { + print "a is nil" +} + + +x = 5 +y ?= 10 + +print x + y +