diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index a50a8febcb..6e33aaaa5a 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -30,6 +30,26 @@ pub type SendSet = im::hashset::HashSet; pub type BumpMap<'a, K, V> = hashbrown::HashMap>; +pub trait BumpMapDefault<'a> { + fn new_in(arena: &'a bumpalo::Bump) -> Self; + + fn with_capacity_in(capacity: usize, arena: &'a bumpalo::Bump) -> Self; +} + +impl<'a, K, V> BumpMapDefault<'a> for BumpMap<'a, K, V> { + fn new_in(arena: &'a bumpalo::Bump) -> Self { + hashbrown::HashMap::with_hasher_in(default_hasher(), hashbrown::BumpWrapper(arena)) + } + + fn with_capacity_in(capacity: usize, arena: &'a bumpalo::Bump) -> Self { + hashbrown::HashMap::with_capacity_and_hasher_in( + capacity, + default_hasher(), + hashbrown::BumpWrapper(arena), + ) + } +} + pub fn arena_join<'a, I>(arena: &'a Bump, strings: &mut I, join_str: &str) -> String<'a> where I: Iterator, diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 7c665fba2f..778ffa9b1f 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -842,7 +842,7 @@ struct State<'a> { /// pending specializations in the same thread. pub needs_specialization: MutSet, - pub all_pending_specializations: MutMap, PendingSpecialization>>, + pub all_pending_specializations: MutMap, PendingSpecialization<'a>>>, pub specializations_in_flight: u32, @@ -3969,6 +3969,7 @@ fn add_def_to_module<'a>( procs.insert_exposed( symbol, layout, + mono_env.arena, mono_env.subs, def.annotation, annotation, @@ -4021,6 +4022,7 @@ fn add_def_to_module<'a>( procs.insert_exposed( symbol, layout, + mono_env.arena, mono_env.subs, def.annotation, annotation, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index a04da8c89d..ffd6f231ea 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6,7 +6,7 @@ use crate::layout::{ }; use bumpalo::collections::Vec; use bumpalo::Bump; -use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet}; +use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap, MutSet}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -77,31 +77,35 @@ impl<'a> CapturedSymbols<'a> { } #[derive(Clone, Debug, PartialEq)] -pub struct PendingSpecialization { +pub struct PendingSpecialization<'a> { solved_type: SolvedType, - host_exposed_aliases: MutMap, + host_exposed_aliases: BumpMap<'a, Symbol, SolvedType>, } -impl PendingSpecialization { - pub fn from_var(subs: &Subs, var: Variable) -> Self { +impl<'a> PendingSpecialization<'a> { + pub fn from_var(arena: &'a Bump, subs: &Subs, var: Variable) -> Self { let solved_type = SolvedType::from_var(subs, var); PendingSpecialization { solved_type, - host_exposed_aliases: MutMap::default(), + host_exposed_aliases: BumpMap::new_in(arena), } } pub fn from_var_host_exposed( + arena: &'a Bump, subs: &Subs, var: Variable, - host_exposed_aliases: &MutMap, + exposed: &MutMap, ) -> Self { let solved_type = SolvedType::from_var(subs, var); - let host_exposed_aliases = host_exposed_aliases - .iter() - .map(|(symbol, variable)| (*symbol, SolvedType::from_var(subs, *variable))) - .collect(); + let mut host_exposed_aliases = BumpMap::with_capacity_in(exposed.len(), arena); + + host_exposed_aliases.extend( + exposed + .iter() + .map(|(symbol, variable)| (*symbol, SolvedType::from_var(subs, *variable))), + ); PendingSpecialization { solved_type, @@ -275,7 +279,8 @@ pub struct Procs<'a> { pub partial_procs: MutMap>, pub imported_module_thunks: MutSet, pub module_thunks: MutSet, - pub pending_specializations: Option, PendingSpecialization>>>, + pub pending_specializations: + Option, PendingSpecialization<'a>>>>, pub specialized: MutMap<(Symbol, Layout<'a>), InProgressProc<'a>>, pub call_by_pointer_wrappers: MutMap, pub runtime_errors: MutMap, @@ -502,7 +507,7 @@ impl<'a> Procs<'a> { // Changing it to use .entry() would necessarily make it incorrect. #[allow(clippy::map_entry)] if !already_specialized { - let pending = PendingSpecialization::from_var(env.subs, annotation); + let pending = PendingSpecialization::from_var(env.arena, env.subs, annotation); let partial_proc; if let Some(existing) = self.partial_procs.get(&symbol) { @@ -576,6 +581,7 @@ impl<'a> Procs<'a> { &mut self, name: Symbol, layout: Layout<'a>, + arena: &'a Bump, subs: &Subs, opt_annotation: Option, fn_var: Variable, @@ -590,8 +596,9 @@ impl<'a> Procs<'a> { // We're done with that tuple, so move layout back out to avoid cloning it. let (name, layout) = tuple; let pending = match opt_annotation { - None => PendingSpecialization::from_var(subs, fn_var), + None => PendingSpecialization::from_var(arena, subs, fn_var), Some(annotation) => PendingSpecialization::from_var_host_exposed( + arena, subs, fn_var, &annotation.introduced_variables.host_exposed_aliases, @@ -634,7 +641,7 @@ impl<'a> Procs<'a> { // We're done with that tuple, so move layout back out to avoid cloning it. let (name, layout) = tuple; - let pending = PendingSpecialization::from_var(env.subs, fn_var); + let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); // This should only be called when pending_specializations is Some. // Otherwise, it's being called in the wrong pass! @@ -674,10 +681,10 @@ impl<'a> Procs<'a> { } fn add_pending<'a>( - pending_specializations: &mut MutMap, PendingSpecialization>>, + pending_specializations: &mut MutMap, PendingSpecialization<'a>>>, symbol: Symbol, layout: Layout<'a>, - pending: PendingSpecialization, + pending: PendingSpecialization<'a>, ) { let all_pending = pending_specializations .entry(symbol) @@ -1662,7 +1669,7 @@ pub fn specialize_all<'a>( name, layout_cache, solved_type, - MutMap::default(), + BumpMap::new_in(env.arena), partial_proc, ) { Ok((proc, layout)) => { @@ -1833,8 +1840,7 @@ fn specialize_external<'a>( let host_exposed_layouts = if host_exposed_variables.is_empty() { HostExposedLayouts::NotHostExposed } else { - let mut aliases = - hashbrown::HashMap::with_hasher_in(default_hasher(), hashbrown::BumpWrapper(env.arena)); + let mut aliases = BumpMap::new_in(env.arena); for (symbol, variable) in host_exposed_variables { let layout = layout_cache @@ -2336,7 +2342,7 @@ fn specialize_solved_type<'a>( proc_name: Symbol, layout_cache: &mut LayoutCache<'a>, solved_type: SolvedType, - host_exposed_aliases: MutMap, + host_exposed_aliases: BumpMap, partial_proc: PartialProc<'a>, ) -> Result<(Proc<'a>, Layout<'a>), SpecializeFailure<'a>> { // add the specializations that other modules require of us @@ -4832,7 +4838,7 @@ fn from_can_when<'a>( ) } -fn substitute(substitutions: &MutMap, s: Symbol) -> Option { +fn substitute(substitutions: &BumpMap, s: Symbol) -> Option { match substitutions.get(&s) { Some(new) => { debug_assert!(!substitutions.contains_key(new)); @@ -4843,7 +4849,7 @@ fn substitute(substitutions: &MutMap, s: Symbol) -> Option(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, to: Symbol) { - let mut subs = MutMap::default(); + let mut subs = BumpMap::with_capacity_in(1, arena); subs.insert(from, to); // TODO clean this up @@ -4856,7 +4862,7 @@ fn substitute_in_exprs<'a>(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, t fn substitute_in_stmt_help<'a>( arena: &'a Bump, stmt: &'a Stmt<'a>, - subs: &MutMap, + subs: &BumpMap, ) -> Option<&'a Stmt<'a>> { use Stmt::*; @@ -5024,7 +5030,7 @@ fn substitute_in_stmt_help<'a>( fn substitute_in_call<'a>( arena: &'a Bump, call: &'a Call<'a>, - subs: &MutMap, + subs: &BumpMap, ) -> Option> { let Call { call_type, @@ -5087,7 +5093,7 @@ fn substitute_in_call<'a>( fn substitute_in_expr<'a>( arena: &'a Bump, expr: &'a Expr<'a>, - subs: &MutMap, + subs: &BumpMap, ) -> Option> { use Expr::*; @@ -6090,7 +6096,7 @@ fn call_by_name<'a>( let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); assign_to_symbols(env, procs, layout_cache, iter, result) } else { - let pending = PendingSpecialization::from_var(env.subs, fn_var); + let pending = PendingSpecialization::from_var(env.arena, env.subs, fn_var); // When requested (that is, when procs.pending_specializations is `Some`), // store a pending specialization rather than specializing immediately. @@ -6927,7 +6933,7 @@ fn from_can_pattern_help<'a>( // sorted fields based on the destruct let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); - let destructs_by_label = env.arena.alloc(MutMap::default()); + let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena); destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x))); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index b93df0e815..96d75da8ed 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -104,6 +104,8 @@ e.g. you have a test `calculate_sum_test` that only uses the function `add`, whe * Suggested quick fixes should be directly visible and clickable. Not like in vs code where you put the caret on an error until a lightbulb appears in the margin which you have to click for the fixes to apppear, after which you click to apply the fix you want :( . * Regex-like find and substitution based on plain english description and example (replacement). i.e. replace all `[` between double quotes with `{`. [Inspiration](https://alexmoltzau.medium.com/english-to-regex-thanks-to-gpt-3-13f03b68236e). * Show productivity tips based on behavior. i.e. if the user is scrolling through the error bar and clicking on the next error several times, show a tip with "go to next error" shortcut. +* Command to "benchmark this function" or "benchmark this test" with flamegraph and execution time per line. + #### Autocomplete - Use more space for autocomplete options: diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index b94597226a..0f80a0398a 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -130,6 +130,9 @@ pub enum EdError { #[snafu(display("RecordWithoutFields: expected record to have at least one field because it is not an EmpyRecord."))] RecordWithoutFields { backtrace: Backtrace }, + #[snafu(display("StringParseError: {}", msg))] + StringParseError { msg: String, backtrace: Backtrace }, + #[snafu(display("UIError: {}", msg))] UIErrorBacktrace { msg: String, backtrace: Backtrace }, } diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 4c812a631f..05433270a9 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -126,6 +126,10 @@ impl<'a> EdModel<'a> { Ok(prev_id_opt) } + + pub fn node_exists_at_caret(&self) -> bool { + self.grid_node_map.node_exists_at_pos(self.get_caret()) + } } #[derive(Debug)] diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index c7167d2ac4..2b0d07e883 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -2,7 +2,6 @@ use crate::editor::code_lines::CodeLines; use crate::editor::ed_error::from_ui_res; -use crate::editor::ed_error::print_ui_err; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingSelection; use crate::editor::grid_node_map::GridNodeMap; @@ -12,6 +11,8 @@ use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::SelectedExpression; +use crate::editor::mvc::int_update::start_new_int; +use crate::editor::mvc::int_update::update_int; use crate::editor::mvc::lookup_update::update_invalid_lookup; use crate::editor::mvc::record_update::start_new_record; use crate::editor::mvc::record_update::update_empty_record; @@ -39,6 +40,7 @@ use crate::ui::text::text_pos::TextPos; use crate::ui::text::{lines, lines::Lines, lines::SelectableLines}; use crate::ui::ui_error::UIResult; use crate::window::keyboard_input::Modifiers; +use bumpalo::Bump; use roc_can::expected::Expected; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; @@ -160,6 +162,33 @@ impl<'a> EdModel<'a> { self.code_lines.del_at_line(line_nr, index) } + pub fn set_selected_expr( + &mut self, + expr_start_pos: TextPos, + expr_end_pos: TextPos, + ast_node_id: NodeId, + mark_node_id: MarkNodeId, + ) -> EdResult<()> { + self.set_raw_sel(RawSelection { + start_pos: expr_start_pos, + end_pos: expr_end_pos, + })?; + + self.set_caret(expr_start_pos); + + //let type_str = self.expr2_to_type(ast_node_id); + + self.selected_expr_opt = Some(SelectedExpression { + ast_node_id, + mark_node_id, + type_str: PoolStr::new("{}", self.module.env.pool), // TODO get this PoolStr from type inference + }); + + self.dirty = true; + + Ok(()) + } + // select all MarkupNodes that refer to specific ast node and its children. pub fn select_expr(&mut self) -> EdResult<()> { // include parent in selection if an `Expr2` was already selected @@ -174,22 +203,7 @@ impl<'a> EdModel<'a> { .grid_node_map .get_nested_start_end_pos(parent_id, self)?; - self.set_raw_sel(RawSelection { - start_pos: expr_start_pos, - end_pos: expr_end_pos, - })?; - - self.set_caret(expr_start_pos); - - //let type_str = self.expr2_to_type(ast_node_id); - - self.selected_expr_opt = Some(SelectedExpression { - ast_node_id, - mark_node_id: parent_id, - type_str: PoolStr::new("{}", self.module.env.pool), // TODO get this PoolStr from type inference - }); - - self.dirty = true; + self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, parent_id)?; } } else { // select `Expr2` in which caret is currently positioned @@ -198,22 +212,17 @@ impl<'a> EdModel<'a> { let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self .grid_node_map .get_expr_start_end_pos(self.get_caret(), &self)?; - self.set_raw_sel(RawSelection { - start_pos: expr_start_pos, - end_pos: expr_end_pos, - })?; - self.set_caret(expr_start_pos); + self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; + } else if self + .grid_node_map + .node_exists_at_pos(caret_pos.decrement_col()) + { + let (expr_start_pos, expr_end_pos, ast_node_id, mark_node_id) = self + .grid_node_map + .get_expr_start_end_pos(self.get_caret().decrement_col(), &self)?; - //let type_str = self.expr2_to_type(ast_node_id); - - self.selected_expr_opt = Some(SelectedExpression { - ast_node_id, - mark_node_id, - type_str: PoolStr::new("{}", self.module.env.pool), // TODO get this PoolStr from type inference - }); - - self.dirty = true; + self.set_selected_expr(expr_start_pos, expr_end_pos, ast_node_id, mark_node_id)?; } } @@ -223,8 +232,10 @@ impl<'a> EdModel<'a> { fn expr2_to_type(&mut self, expr2_id: NodeId) -> PoolStr { let var = self.module.env.var_store.fresh(); let expr = self.module.env.pool.get(expr2_id); + let arena = Bump::new(); let constrained = constrain_expr( + &arena, &mut self.module.env, &expr, Expected::NoExpectation(Type2::Variable(var)), @@ -542,11 +553,9 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult InputOutcome::Accepted } ch => { - let curr_mark_node_id_res = ed_model.get_curr_mark_node_id(); - let outcome = - match curr_mark_node_id_res { - Ok(curr_mark_node_id) => { + if ed_model.node_exists_at_caret() { + let curr_mark_node_id = ed_model.get_curr_mark_node_id()?; let curr_mark_node = ed_model.markup_node_pool.get(curr_mark_node_id); let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; @@ -561,6 +570,9 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult '{' => { start_new_record(ed_model)? } + '0'..='9' => { + start_new_int(ed_model, ch)? + } _ => InputOutcome::Ignored } } else if let Some(prev_mark_node_id) = prev_mark_node_id_opt{ @@ -602,68 +614,78 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult InputOutcome::Ignored } } + Expr2::SmallInt{ .. } => { + update_int(ed_model, curr_mark_node_id, ch)? + } _ => InputOutcome::Ignored } } else if ch.is_ascii_alphanumeric() { // prev_mark_node_id != curr_mark_node_id - let prev_ast_node_id = - ed_model - .markup_node_pool - .get(prev_mark_node_id) - .get_ast_node_id(); - let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id); - - match prev_node_ref { - Expr2::InvalidLookup(old_pool_str) => { - update_invalid_lookup( - &ch.to_string(), - old_pool_str, - prev_mark_node_id, - prev_ast_node_id, - ed_model - )? - } - Expr2::Record{ record_var:_, fields } => { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - - if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) && - prev_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - prev_mark_node_id, - fields, - ed_model, - )? - } else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? { - update_record_field( - &ch.to_string(), - ed_model.get_caret(), - curr_mark_node_id, - fields, - ed_model, - )? - } else { - InputOutcome::Ignored - } + match ast_node_ref { + Expr2::SmallInt{ .. } => { + update_int(ed_model, curr_mark_node_id, ch)? } _ => { - match ast_node_ref { - Expr2::EmptyRecord => { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + let prev_ast_node_id = + ed_model + .markup_node_pool + .get(prev_mark_node_id) + .get_ast_node_id(); - if ch.is_ascii_alphabetic() && ch.is_ascii_lowercase() { - update_empty_record( + let prev_node_ref = ed_model.module.env.pool.get(prev_ast_node_id); + + match prev_node_ref { + Expr2::InvalidLookup(old_pool_str) => { + update_invalid_lookup( + &ch.to_string(), + old_pool_str, + prev_mark_node_id, + prev_ast_node_id, + ed_model + )? + } + Expr2::Record{ record_var:_, fields } => { + let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + + if (curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE || curr_mark_node.get_content()? == nodes::COLON) && + prev_mark_node.is_all_alphanumeric()? { + update_record_field( &ch.to_string(), + ed_model.get_caret(), prev_mark_node_id, - sibling_ids, - ed_model + fields, + ed_model, + )? + } else if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE && curr_mark_node.is_all_alphanumeric()? { + update_record_field( + &ch.to_string(), + ed_model.get_caret(), + curr_mark_node_id, + fields, + ed_model, )? } else { InputOutcome::Ignored } } - _ => InputOutcome::Ignored + Expr2::SmallInt{ .. } => { + update_int(ed_model, prev_mark_node_id, ch)? + } + _ => { + match ast_node_ref { + Expr2::EmptyRecord => { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + + update_empty_record( + &ch.to_string(), + prev_mark_node_id, + sibling_ids, + ed_model + )? + } + _ => InputOutcome::Ignored + } + } } } } @@ -682,15 +704,35 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult } } else { - // Not supporting any Expr2 right now that would allow prepending at the start of a line + match ast_node_ref { + Expr2::SmallInt{ .. } => { + update_int(ed_model, curr_mark_node_id, ch)? + }, + // only SmallInt currently allows prepending at the start + _ => InputOutcome::Ignored + } + } + + } else { //no MarkupNode at the current position + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + + let prev_ast_node = ed_model.module.env.pool.get(prev_mark_node.get_ast_node_id()); + + match prev_ast_node { + Expr2::SmallInt{ .. } => { + update_int(ed_model, prev_mark_node_id, ch)? + }, + _ => { + InputOutcome::Ignored + } + } + } else { InputOutcome::Ignored } - }, - Err(e) => { - print_ui_err(&e); - InputOutcome::Ignored - } - }; + }; + if let InputOutcome::Accepted = outcome { ed_model.set_sel_none(); @@ -748,6 +790,10 @@ pub mod test_ed_update { assert_insert_seq(pre_lines, expected_post_lines, &new_char.to_string()) } + pub fn assert_insert_ignore(lines: &[&str], new_char: char) -> Result<(), String> { + assert_insert_seq(lines, lines, &new_char.to_string()) + } + // Create ed_model from pre_lines DSL, do handle_new_char() for every char in new_char_seq, check if modified ed_model has expected // string representation of code, caret position and active selection. pub fn assert_insert_seq( @@ -782,13 +828,69 @@ pub mod test_ed_update { // space is added because Blank is inserted assert_insert(&["┃"], &["┃ "], 'a')?; assert_insert(&["┃"], &["┃ "], ';')?; - assert_insert(&["┃"], &["┃ "], '5')?; assert_insert(&["┃"], &["┃ "], '-')?; assert_insert(&["┃"], &["┃ "], '_')?; Ok(()) } + #[test] + fn test_int() -> Result<(), String> { + assert_insert(&["┃"], &["0┃"], '0')?; + assert_insert(&["┃"], &["1┃"], '1')?; + assert_insert(&["┃"], &["2┃"], '2')?; + assert_insert(&["┃"], &["3┃"], '3')?; + assert_insert(&["┃"], &["4┃"], '4')?; + assert_insert(&["┃"], &["5┃"], '5')?; + assert_insert(&["┃"], &["6┃"], '6')?; + assert_insert(&["┃"], &["7┃"], '7')?; + assert_insert(&["┃"], &["8┃"], '8')?; + assert_insert(&["┃"], &["9┃"], '9')?; + + assert_insert(&["1┃"], &["19┃"], '9')?; + assert_insert(&["9876┃"], &["98769┃"], '9')?; + assert_insert(&["10┃"], &["103┃"], '3')?; + assert_insert(&["┃0"], &["1┃0"], '1')?; + assert_insert(&["10000┃"], &["100000┃"], '0')?; + + assert_insert(&["┃1234"], &["5┃1234"], '5')?; + assert_insert(&["1┃234"], &["10┃234"], '0')?; + assert_insert(&["12┃34"], &["121┃34"], '1')?; + assert_insert(&["123┃4"], &["1232┃4"], '2')?; + + Ok(()) + } + + #[test] + fn test_ignore_int() -> Result<(), String> { + assert_insert_seq_ignore(&["┃0"], "{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["┃7"], "{}()[]-><-_\"azAZ:@")?; + + assert_insert_seq_ignore(&["0┃"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["8┃"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["20┃"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["83┃"], ",{}()[]-><-_\"azAZ:@")?; + + assert_insert_seq_ignore(&["1┃0"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["8┃4"], ",{}()[]-><-_\"azAZ:@")?; + + assert_insert_seq_ignore(&["┃10"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["┃84"], ",{}()[]-><-_\"azAZ:@")?; + + assert_insert_seq_ignore(&["129┃96"], ",{}()[]-><-_\"azAZ:@")?; + assert_insert_seq_ignore(&["97┃684"], ",{}()[]-><-_\"azAZ:@")?; + + assert_insert_ignore(&["0┃"], '0')?; + assert_insert_ignore(&["0┃"], '9')?; + assert_insert_ignore(&["┃0"], '0')?; + assert_insert_ignore(&["┃1234"], '0')?; + assert_insert_ignore(&["┃100"], '0')?; + + Ok(()) + } + + //TODO test_int arch bit limit + #[test] fn test_string() -> Result<(), String> { assert_insert(&["┃"], &["\"┃\""], '"')?; @@ -923,14 +1025,29 @@ pub mod test_ed_update { assert_insert_seq(&["{ a┃ }"], &["{ a: \"┃\" }"], ":\"")?; assert_insert_seq(&["{ abc┃ }"], &["{ abc: \"┃\" }"], ":\"")?; + assert_insert_seq(&["{ a┃ }"], &["{ a: 0┃ }"], ":0")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc: 9┃ }"], ":9")?; + assert_insert_seq(&["{ a┃ }"], &["{ a: 1000┃ }"], ":1000")?; + assert_insert_seq(&["{ abc┃ }"], &["{ abc: 98761┃ }"], ":98761")?; + assert_insert(&["{ a: \"┃\" }"], &["{ a: \"a┃\" }"], 'a')?; assert_insert(&["{ a: \"a┃\" }"], &["{ a: \"ab┃\" }"], 'b')?; assert_insert(&["{ a: \"a┃b\" }"], &["{ a: \"az┃b\" }"], 'z')?; assert_insert(&["{ a: \"┃ab\" }"], &["{ a: \"z┃ab\" }"], 'z')?; + assert_insert(&["{ a: 1┃ }"], &["{ a: 10┃ }"], '0')?; + assert_insert(&["{ a: 100┃ }"], &["{ a: 1004┃ }"], '4')?; + assert_insert(&["{ a: 9┃76 }"], &["{ a: 98┃76 }"], '8')?; + assert_insert(&["{ a: 4┃691 }"], &["{ a: 40┃691 }"], '0')?; + assert_insert(&["{ a: 469┃1 }"], &["{ a: 4699┃1 }"], '9')?; + assert_insert(&["{ camelCase: \"┃\" }"], &["{ camelCase: \"a┃\" }"], 'a')?; assert_insert(&["{ camelCase: \"a┃\" }"], &["{ camelCase: \"ab┃\" }"], 'b')?; + assert_insert(&["{ camelCase: 3┃ }"], &["{ camelCase: 35┃ }"], '5')?; + assert_insert(&["{ camelCase: ┃2 }"], &["{ camelCase: 5┃2 }"], '5')?; + assert_insert(&["{ camelCase: 10┃2 }"], &["{ camelCase: 106┃2 }"], '6')?; + assert_insert(&["{ a┃: \"\" }"], &["{ ab┃: \"\" }"], 'b')?; assert_insert(&["{ ┃a: \"\" }"], &["{ z┃a: \"\" }"], 'z')?; assert_insert(&["{ ab┃: \"\" }"], &["{ abc┃: \"\" }"], 'c')?; @@ -951,7 +1068,16 @@ pub mod test_ed_update { 'z', )?; + assert_insert(&["{ a┃: 0 }"], &["{ ab┃: 0 }"], 'b')?; + assert_insert(&["{ ┃a: 2100 }"], &["{ z┃a: 2100 }"], 'z')?; + assert_insert(&["{ ab┃: 9876 }"], &["{ abc┃: 9876 }"], 'c')?; + assert_insert(&["{ ┃ab: 102 }"], &["{ z┃ab: 102 }"], 'z')?; + assert_insert(&["{ camelCase┃: 99999 }"], &["{ camelCaseB┃: 99999 }"], 'B')?; + assert_insert(&["{ camel┃Case: 88156 }"], &["{ camelZ┃Case: 88156 }"], 'Z')?; + assert_insert(&["{ ┃camelCase: 1 }"], &["{ z┃camelCase: 1 }"], 'z')?; + assert_insert_seq(&["┃"], &["{ camelCase: \"hello┃\" }"], "{camelCase:\"hello")?; + assert_insert_seq(&["┃"], &["{ camelCase: 10009┃ }"], "{camelCase:10009")?; Ok(()) } @@ -1013,6 +1139,25 @@ pub mod test_ed_update { "ul", )?; + assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: 1┃ } }"], ":1")?; + assert_insert_seq( + &["{ abc: { camelCase┃ } }"], + &["{ abc: { camelCase: 0┃ } }"], + ":0", + )?; + assert_insert_seq( + &["{ camelCase: { z┃ } }"], + &["{ camelCase: { z: 45┃ } }"], + ":45", + )?; + + assert_insert_seq(&["{ a: { zulu: ┃0 } }"], &["{ a: { zulu: 4┃0 } }"], "4")?; + assert_insert_seq( + &["{ a: { zulu: 10┃98 } }"], + &["{ a: { zulu: 1077┃98 } }"], + "77", + )?; + assert_insert_seq(&["{ a: { zulu┃ } }"], &["{ a: { zulu: { ┃ } } }"], ":{")?; assert_insert_seq( &["{ abc: { camelCase┃ } }"], @@ -1071,146 +1216,199 @@ pub mod test_ed_update { Ok(()) } + const IGNORE_CHARS: &str = "{}()[]-><-_\"azAZ:@09"; + const IGNORE_NO_LTR: &str = "{\"5"; + const IGNORE_NO_NUM: &str = "a{\""; + #[test] fn test_ignore_record() -> Result<(), String> { - assert_insert_seq_ignore(&["┃{ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ }┃"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ ┃}"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["{ ┃ }"], "{\"5")?; - assert_insert_seq_ignore(&["{ ┃a }"], "{\"5")?; - assert_insert_seq_ignore(&["{ ┃abc }"], "{\"5")?; + assert_insert_seq_ignore(&["{ ┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ ┃a }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ ┃abc }"], IGNORE_NO_LTR)?; - assert_insert_seq_ignore(&["┃{ a }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a }┃"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ a }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a ┃}"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ a }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a15 }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a15 }┃"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ a15 }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a15 ┃}"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ a15 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a15 }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a15 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a15 ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ camelCase }┃"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ camelCase }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ camelCase ┃}"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ camelCase }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase }┃"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ camelCase }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase ┃}"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ a: \"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: ┃\"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"\"┃ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"\" }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: ┃\"\" }"], "0")?; + assert_insert_seq_ignore(&["{ a: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: \"\" }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ camelCase: \"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ camelCase: \"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ camelCase: ┃\"\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ camelCase: \"\"┃ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ camelCase: \"\" }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ a: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a: 2 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: ┃6 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ a: 8┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ a: 0 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"z\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ a: \"z\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: ┃\"z\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"z\"┃ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"z\" }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ camelCase: 1 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ camelCase: 7 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase: ┃2 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCase: 4┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCase: 9 }┃"], IGNORE_CHARS)?; - assert_insert_seq_ignore(&["┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], "a{\"5")?; - assert_insert_seq_ignore(&["{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], "a{\"5")?; + assert_insert_seq_ignore(&["┃{ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ camelCase: \"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase: ┃\"\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase: \"\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ camelCase: \"\" }┃"], IGNORE_CHARS)?; + + assert_insert_seq_ignore(&["┃{ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a: \"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: ┃\"z\" }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: \"z\"┃ }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: \"z\" }┃"], IGNORE_CHARS)?; + + assert_insert_seq_ignore( + &["┃{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + IGNORE_CHARS, + )?; + assert_insert_seq_ignore( + &["{┃ a: \"hello, hello.0123456789ZXY{}[]-><-\" }"], + IGNORE_CHARS, + )?; + assert_insert_seq_ignore( + &["{ a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" }"], + IGNORE_CHARS, + )?; + assert_insert_seq_ignore( + &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ }"], + IGNORE_CHARS, + )?; + assert_insert_seq_ignore( + &["{ a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃"], + IGNORE_CHARS, + )?; + + assert_insert_seq_ignore(&["┃{ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{┃ a: 915480 }"], IGNORE_CHARS)?; + assert_insert_seq_ignore(&["{ a: ┃915480 }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ a: 915480┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ a: 915480 }┃"], IGNORE_CHARS)?; Ok(()) } #[test] fn test_ignore_nested_record() -> Result<(), String> { - assert_insert_seq_ignore(&["{ a: { ┃ } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ a: ┃{ } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ a: {┃ } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ a: { }┃ }"], "{\"5")?; - assert_insert_seq_ignore(&["{ a: { } ┃}"], "{\"5")?; - assert_insert_seq_ignore(&["{ a: { } }┃"], "{\"5")?; - assert_insert_seq_ignore(&["{ a:┃ { } }"], "{\"5")?; - assert_insert_seq_ignore(&["{┃ a: { } }"], "{\"5")?; - assert_insert_seq_ignore(&["┃{ a: { } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ a: { ┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a: ┃{ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a: {┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a: { }┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a: { } ┃}"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a: { } }┃"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ a:┃ { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{┃ a: { } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["┃{ a: { } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore(&["{ ┃a: { } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a ┃} }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a }┃ }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } ┃}"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } }┃"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a } }"], "{\"5")?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a } }"], "{\"5")?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a ┃} }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a }┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } ┃}"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a } }┃"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a } }"], "1")?; assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a } }"], "1")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ \"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" ┃} }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: \"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: \"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" }┃ }"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } ┃}"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } }┃"], "{\"5")?; - assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: \"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: \"\" } }"], "{\"5")?; - assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: \"\" } }"], "{\"5")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\"┃ } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃\"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ \"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" ┃} }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: \"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: \"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" }┃ }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } ┃}"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: \"\" } }┃"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: \"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: \"\" } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: \"\" } }"], "1")?; assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: \"\" } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 0┃ } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: ┃123 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a:┃ 999 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 80 ┃} }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: {┃ z15a: 99000 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: ┃{ z15a: 12 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 7 }┃ }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 98 } ┃}"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1: { z15a: 4582 } }┃"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ camelCaseB1:┃ { z15a: 0 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{┃ camelCaseB1: { z15a: 44 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["┃{ camelCaseB1: { z15a: 100123 } }"], IGNORE_NO_NUM)?; + assert_insert_seq_ignore(&["{ ┃camelCaseB1: { z15a: 5 } }"], "1")?; + assert_insert_seq_ignore(&["{ camelCaseB1: { ┃z15a: 6 } }"], "1")?; + assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\"┃ } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: ┃\"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a:┃ \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" ┃} }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: {┃ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: ┃{ z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" }┃ }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } ┃}"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }┃"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ camelCaseB1:┃ { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{┃ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["┃{ camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ ┃camelCaseB1: { z15a: \"hello, hello.0123456789ZXY{}[]-><-\" } }"], @@ -1223,35 +1421,35 @@ pub mod test_ed_update { assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase ┃} } } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } ┃} } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }┃"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } ┃} } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: {┃ e: { p: { camelCase } } } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase } } } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], - "{\"5", + IGNORE_NO_LTR, )?; assert_insert_seq_ignore( &["{ ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"], @@ -1301,6 +1499,20 @@ pub mod test_ed_update { Ok(()) } + #[test] + fn test_ctrl_shift_up_int() -> Result<(), String> { + assert_ctrl_shift_up(&["5┃"], &["┃❮5❯"])?; + assert_ctrl_shift_up_repeat(&["0┃"], &["┃❮0❯"], 3)?; + assert_ctrl_shift_up(&["12345┃"], &["┃❮12345❯"])?; + assert_ctrl_shift_up(&["┃12345"], &["┃❮12345❯"])?; + assert_ctrl_shift_up(&["1┃2345"], &["┃❮12345❯"])?; + assert_ctrl_shift_up(&["12┃345"], &["┃❮12345❯"])?; + assert_ctrl_shift_up(&["123┃45"], &["┃❮12345❯"])?; + assert_ctrl_shift_up(&["1234┃5"], &["┃❮12345❯"])?; + + Ok(()) + } + #[test] fn test_ctrl_shift_up_string() -> Result<(), String> { assert_ctrl_shift_up(&["\"┃\""], &["┃❮\"\"❯"])?; @@ -1317,8 +1529,8 @@ pub mod test_ed_update { &["┃❮\"hello, hello.0123456789ZXY{}[]-><-\"❯"], )?; - assert_ctrl_shift_up(&["\"\"┃"], &["\"\"┃"])?; - assert_ctrl_shift_up(&["\"abc\"┃"], &["\"abc\"┃"])?; + assert_ctrl_shift_up(&["\"\"┃"], &["┃❮\"\"❯"])?; + assert_ctrl_shift_up(&["\"abc\"┃"], &["┃❮\"abc\"❯"])?; Ok(()) } @@ -1330,7 +1542,7 @@ pub mod test_ed_update { assert_ctrl_shift_up(&["┃{ }"], &["┃❮{ }❯"])?; assert_ctrl_shift_up(&["{ ┃}"], &["┃❮{ }❯"])?; assert_ctrl_shift_up_repeat(&["{ ┃ }"], &["┃❮{ }❯"], 4)?; - assert_ctrl_shift_up(&["{ }┃"], &["{ }┃"])?; + assert_ctrl_shift_up(&["{ }┃"], &["┃❮{ }❯"])?; assert_ctrl_shift_up(&["{ pear┃ }"], &["┃❮{ pear }❯"])?; assert_ctrl_shift_up(&["{ pea┃r }"], &["┃❮{ pear }❯"])?; @@ -1340,7 +1552,7 @@ pub mod test_ed_update { assert_ctrl_shift_up(&["┃{ pear }"], &["┃❮{ pear }❯"])?; assert_ctrl_shift_up(&["{ pear ┃}"], &["┃❮{ pear }❯"])?; assert_ctrl_shift_up_repeat(&["{ pear┃ }"], &["┃❮{ pear }❯"], 3)?; - assert_ctrl_shift_up(&["{ pear }┃"], &["{ pear }┃"])?; + assert_ctrl_shift_up(&["{ pear }┃"], &["┃❮{ pear }❯"])?; assert_ctrl_shift_up(&["{ camelCase123┃ }"], &["┃❮{ camelCase123 }❯"])?; @@ -1349,7 +1561,7 @@ pub mod test_ed_update { assert_ctrl_shift_up(&["{ a: \"\"┃ }"], &["┃❮{ a: \"\" }❯"])?; assert_ctrl_shift_up(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"])?; assert_ctrl_shift_up_repeat(&["{ a: \"\" ┃}"], &["┃❮{ a: \"\" }❯"], 3)?; - assert_ctrl_shift_up(&["{ a: \"\" }┃"], &["{ a: \"\" }┃"])?; + assert_ctrl_shift_up(&["{ a: \"\" }┃"], &["┃❮{ a: \"\" }❯"])?; assert_ctrl_shift_up(&["{ a:┃ \"\" }"], &["┃❮{ a: \"\" }❯"])?; assert_ctrl_shift_up(&["{ a┃: \"\" }"], &["┃❮{ a: \"\" }❯"])?; assert_ctrl_shift_up(&["{ ┃a: \"\" }"], &["┃❮{ a: \"\" }❯"])?; @@ -1358,6 +1570,21 @@ pub mod test_ed_update { assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 2)?; assert_ctrl_shift_up_repeat(&["{ a: \"┃\" }"], &["┃❮{ a: \"\" }❯"], 4)?; + assert_ctrl_shift_up(&["{ a: 1┃0 }"], &["{ a: ┃❮10❯ }"])?; + assert_ctrl_shift_up(&["{ a: ┃9 }"], &["{ a: ┃❮9❯ }"])?; + assert_ctrl_shift_up(&["{ a: 98┃89 }"], &["{ a: ┃❮9889❯ }"])?; + assert_ctrl_shift_up(&["{ a: 44┃ }"], &["┃❮{ a: 44 }❯"])?; + assert_ctrl_shift_up(&["{ a: 0 ┃}"], &["┃❮{ a: 0 }❯"])?; + assert_ctrl_shift_up_repeat(&["{ a: 123 ┃}"], &["┃❮{ a: 123 }❯"], 3)?; + assert_ctrl_shift_up(&["{ a: 96 }┃"], &["┃❮{ a: 96 }❯"])?; + assert_ctrl_shift_up(&["{ a:┃ 985600 }"], &["┃❮{ a: 985600 }❯"])?; + assert_ctrl_shift_up(&["{ a┃: 5648 }"], &["┃❮{ a: 5648 }❯"])?; + assert_ctrl_shift_up(&["{ ┃a: 1000000 }"], &["┃❮{ a: 1000000 }❯"])?; + assert_ctrl_shift_up(&["{┃ a: 1 }"], &["┃❮{ a: 1 }❯"])?; + assert_ctrl_shift_up(&["┃{ a: 900600 }"], &["┃❮{ a: 900600 }❯"])?; + assert_ctrl_shift_up_repeat(&["{ a: 10┃000 }"], &["┃❮{ a: 10000 }❯"], 2)?; + assert_ctrl_shift_up_repeat(&["{ a: ┃45 }"], &["┃❮{ a: 45 }❯"], 4)?; + assert_ctrl_shift_up(&["{ abc: \"de┃\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; assert_ctrl_shift_up(&["{ abc: \"d┃e\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; assert_ctrl_shift_up(&["{ abc: \"┃de\" }"], &["{ abc: ┃❮\"de\"❯ }"])?; @@ -1432,7 +1659,7 @@ pub mod test_ed_update { )?; assert_ctrl_shift_up( &["{ abc: { de: \"f g\" } }┃"], - &["{ abc: { de: \"f g\" } }┃"], + &["┃❮{ abc: { de: \"f g\" } }❯"], )?; assert_ctrl_shift_up_repeat( @@ -1451,6 +1678,23 @@ pub mod test_ed_update { 4, )?; + assert_ctrl_shift_up(&["{ abc: { de: ┃951 } }"], &["{ abc: { de: ┃❮951❯ } }"])?; + assert_ctrl_shift_up(&["{ abc: { de: 11┃0 } }"], &["{ abc: { de: ┃❮110❯ } }"])?; + assert_ctrl_shift_up(&["{ abc: { de: 444┃ } }"], &["{ abc: ┃❮{ de: 444 }❯ }"])?; + assert_ctrl_shift_up(&["{ abc: { de┃: 99 } }"], &["{ abc: ┃❮{ de: 99 }❯ }"])?; + assert_ctrl_shift_up(&["{ abc: {┃ de: 0 } }"], &["{ abc: ┃❮{ de: 0 }❯ }"])?; + assert_ctrl_shift_up(&["{ abc: { de: 230 ┃} }"], &["{ abc: ┃❮{ de: 230 }❯ }"])?; + assert_ctrl_shift_up(&["{ abc: { de: 7 }┃ }"], &["┃❮{ abc: { de: 7 } }❯"])?; + assert_ctrl_shift_up(&["┃{ abc: { de: 1 } }"], &["┃❮{ abc: { de: 1 } }❯"])?; + assert_ctrl_shift_up( + &["{ abc: { de: 111111 } }┃"], + &["┃❮{ abc: { de: 111111 } }❯"], + )?; + + assert_ctrl_shift_up_repeat(&["{ abc: { de: 1┃5 } }"], &["{ abc: ┃❮{ de: 15 }❯ }"], 2)?; + assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃55 } }"], &["┃❮{ abc: { de: 55 } }❯"], 3)?; + assert_ctrl_shift_up_repeat(&["{ abc: { de: ┃400 } }"], &["┃❮{ abc: { de: 400 } }❯"], 4)?; + assert_ctrl_shift_up_repeat( &["{ g: { oi: { ng: { d: { e: { e: { p: { camelCase┃ } } } } } } } }"], &["{ g: { oi: { ng: { d: ┃❮{ e: { e: { p: { camelCase } } } }❯ } } } }"], @@ -1597,6 +1841,15 @@ pub mod test_ed_update { Ok(()) } + #[test] + fn test_ctrl_shift_up_move_int() -> Result<(), String> { + assert_ctrl_shift_single_up_move(&["┃0"], &["0┃"], move_down!())?; + assert_ctrl_shift_single_up_move(&["┃9654"], &["┃9654"], move_up!())?; + assert_ctrl_shift_single_up_move(&["┃100546"], &["100546┃"], move_end!())?; + + Ok(()) + } + #[test] fn test_ctrl_shift_up_move_string() -> Result<(), String> { assert_ctrl_shift_single_up_move(&["┃\"\""], &["\"\"┃"], move_down!())?; @@ -1629,6 +1882,13 @@ pub mod test_ed_update { move_down!(), )?; + assert_ctrl_shift_up_move( + &["{ camelCase: { cC123: 9┃5 } }"], + &["{ camelCase: { cC123: 95 }┃ }"], + 2, + move_down!(), + )?; + Ok(()) } @@ -1675,11 +1935,21 @@ pub mod test_ed_update { Ok(()) } + #[test] + fn test_ctrl_shift_up_backspace_int() -> Result<(), String> { + // Blank is inserted when root is deleted + assert_ctrl_shift_single_up_backspace(&["95┃21"], &["┃ "])?; + assert_ctrl_shift_single_up_backspace(&["0┃"], &["┃ "])?; + assert_ctrl_shift_single_up_backspace(&["┃10000"], &["┃ "])?; + + Ok(()) + } + #[test] fn test_ctrl_shift_up_backspace_string() -> Result<(), String> { // Blank is inserted when root is deleted assert_ctrl_shift_single_up_backspace(&["\"┃\""], &["┃ "])?; - assert_ctrl_shift_single_up_backspace(&["\"\"┃"], &["\"\"┃"])?; + assert_ctrl_shift_single_up_backspace(&["\"\"┃"], &["┃ "])?; assert_ctrl_shift_single_up_backspace(&["┃\"abc\""], &["┃ "])?; assert_ctrl_shift_single_up_backspace( &["\"hello┃, hello.0123456789ZXY{}[]-><-\""], @@ -1699,6 +1969,7 @@ pub mod test_ed_update { assert_ctrl_shift_single_up_backspace(&["{ a: ┃{ b } }"], &["{ a: ┃ }"])?; assert_ctrl_shift_single_up_backspace(&["{ a: \"┃b cd\" }"], &["{ a: ┃ }"])?; + assert_ctrl_shift_single_up_backspace(&["{ a: ┃12 }"], &["{ a: ┃ }"])?; assert_ctrl_shift_single_up_backspace( &["{ g: { oi: { ng: { d: { ┃e: { e: { p: { camelCase } } } } } } } }"], &["{ g: { oi: { ng: { d: ┃ } } } }"], @@ -1709,6 +1980,11 @@ pub mod test_ed_update { &["{ a: { b: ┃ } }"], 2, )?; + assert_ctrl_shift_up_backspace( + &["{ a: { b: { c: 100┃000 } } }"], + &["{ a: { b: ┃ } }"], + 2, + )?; assert_ctrl_shift_up_backspace(&["{ a: { b: { c: {┃ } } } }"], &["{ a: { b: ┃ } }"], 2)?; assert_ctrl_shift_up_backspace( &["{ g: { oi: { ng: { d: { e: { e: { p┃: { camelCase } } } } } } } }"], diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs new file mode 100644 index 0000000000..8af06a2abe --- /dev/null +++ b/editor/src/editor/mvc/int_update.rs @@ -0,0 +1,156 @@ +use crate::editor::ed_error::EdResult; +use crate::editor::ed_error::StringParseError; +use crate::editor::markup::attribute::Attributes; +use crate::editor::markup::nodes::MarkupNode; +use crate::editor::mvc::app_update::InputOutcome; +use crate::editor::mvc::ed_model::EdModel; +use crate::editor::mvc::ed_update::get_node_context; +use crate::editor::mvc::ed_update::NodeContext; +use crate::editor::slow_pool::MarkNodeId; +use crate::editor::syntax_highlight::HighlightStyle; +use crate::lang::ast::Expr2::SmallInt; +use crate::lang::ast::IntVal; +use crate::lang::ast::{IntStyle, IntVal::*}; +use crate::lang::pool::PoolStr; +use crate::ui::text::lines::SelectableLines; + +// digit_char should be verified to be a digit before calling this function +pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult { + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, + parent_id_opt, + ast_node_id, + } = get_node_context(&ed_model)?; + + let is_blank_node = curr_mark_node.is_blank(); + + let int_var = ed_model.module.env.var_store.fresh(); + + let digit_string = digit_char.to_string(); + + let expr2_node = SmallInt { + number: IntVal::U64(*digit_char as u64), // TODO determine if u64 on wordlength of current arch, perhaps introduce Unknown(i64) + var: int_var, + style: IntStyle::Decimal, + text: PoolStr::new(&digit_string, &mut ed_model.module.env.pool), + }; + + ed_model.module.env.pool.set(ast_node_id, expr2_node); + + let int_node = MarkupNode::Text { + content: digit_string, + ast_node_id, + syn_high_style: HighlightStyle::Number, + attributes: Attributes::new(), + parent_id_opt, + }; + + if is_blank_node { + ed_model + .markup_node_pool + .replace_node(curr_mark_node_id, int_node); + + // remove data corresponding to Blank node + ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?; + + let char_len = 1; + ed_model.simple_move_carets_right(char_len); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &digit_char.to_string(), + curr_mark_node_id, + )?; + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } +} + +// TODO check if new int needs more than e.g. 64 bits +pub fn update_int( + ed_model: &mut EdModel, + int_mark_node_id: MarkNodeId, + ch: &char, +) -> EdResult { + if ch.is_ascii_digit() { + let old_caret_pos = ed_model.get_caret(); + + let node_caret_offset = ed_model + .grid_node_map + .get_offset_to_node_id(old_caret_pos, int_mark_node_id)?; + + let int_mark_node = ed_model.markup_node_pool.get_mut(int_mark_node_id); + let int_ast_node_id = int_mark_node.get_ast_node_id(); + + let content_str_mut = int_mark_node.get_content_mut()?; + + // 00, 01 are not valid ints + if (content_str_mut == "0" && (node_caret_offset == 1 || *ch == '0')) + || (*ch == '0' && node_caret_offset == 0) + { + Ok(InputOutcome::Ignored) + } else { + content_str_mut.insert(node_caret_offset, *ch); + + let content_str = int_mark_node.get_content()?; + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + &ch.to_string(), + int_mark_node_id, + )?; + + // update ast + let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool); + let int_ast_node = ed_model.module.env.pool.get_mut(int_ast_node_id); + match int_ast_node { + SmallInt { number, text, .. } => { + update_small_int_num(number, &content_str)?; + + *text = new_pool_str; + } + _ => unimplemented!("TODO implement updating this type of Number"), + } + + // update caret + ed_model.simple_move_carets_right(1); + + Ok(InputOutcome::Accepted) + } + } else { + Ok(InputOutcome::Ignored) + } +} + +fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> { + *number = match number { + I64(_) => I64(check_parse_res(updated_str.parse::())?), + U64(_) => U64(check_parse_res(updated_str.parse::())?), + I32(_) => I32(check_parse_res(updated_str.parse::())?), + U32(_) => U32(check_parse_res(updated_str.parse::())?), + I16(_) => I16(check_parse_res(updated_str.parse::())?), + U16(_) => U16(check_parse_res(updated_str.parse::())?), + I8(_) => I8(check_parse_res(updated_str.parse::())?), + U8(_) => U8(check_parse_res(updated_str.parse::())?), + }; + + Ok(()) +} + +fn check_parse_res(parse_res: Result) -> EdResult { + match parse_res { + Ok(some_type) => Ok(some_type), + Err(parse_err) => StringParseError { + msg: format!("{:?}", parse_err), + } + .fail(), + } +} diff --git a/editor/src/editor/mvc/mod.rs b/editor/src/editor/mvc/mod.rs index 5adbe7a1eb..b393cfd0a2 100644 --- a/editor/src/editor/mvc/mod.rs +++ b/editor/src/editor/mvc/mod.rs @@ -3,6 +3,7 @@ pub mod app_update; pub mod ed_model; pub mod ed_update; pub mod ed_view; +mod int_update; mod lookup_update; mod record_update; mod string_update; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index a5b9ffae7e..9cf37c95e0 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -96,69 +96,77 @@ pub fn update_empty_record( sibling_ids: Vec, ed_model: &mut EdModel, ) -> EdResult { - let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); + let mut input_chars = new_input.chars(); - let NodeContext { - old_caret_pos, - curr_mark_node_id, - curr_mark_node, - parent_id_opt, - ast_node_id, - } = get_node_context(&ed_model)?; - - if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE - && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE + if input_chars.all(|ch| ch.is_ascii_alphabetic()) + && input_chars.all(|ch| ch.is_ascii_lowercase()) { - // update AST - let record_var = ed_model.module.env.var_store.fresh(); - let field_name = PoolStr::new(new_input, &mut ed_model.module.env.pool); - let field_var = ed_model.module.env.var_store.fresh(); - //TODO actually check if field_str belongs to a previously defined variable - let first_field = RecordField::InvalidLabelOnly(field_name, field_var); + let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - let fields = PoolVec::new(vec![first_field].into_iter(), &mut ed_model.module.env.pool); - - let new_ast_node = Expr2::Record { record_var, fields }; - - ed_model.module.env.pool.set(ast_node_id, new_ast_node); - - // update Markup - - let record_field_node = MarkupNode::Text { - content: new_input.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::RecordField, - attributes: Attributes::new(), + let NodeContext { + old_caret_pos, + curr_mark_node_id, + curr_mark_node, parent_id_opt, - }; + ast_node_id, + } = get_node_context(&ed_model)?; - let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + if prev_mark_node.get_content()? == nodes::LEFT_ACCOLADE + && curr_mark_node.get_content()? == nodes::RIGHT_ACCOLADE + { + // update AST + let record_var = ed_model.module.env.var_store.fresh(); + let field_name = PoolStr::new(new_input, &mut ed_model.module.env.pool); + let field_var = ed_model.module.env.var_store.fresh(); + //TODO actually check if field_str belongs to a previously defined variable + let first_field = RecordField::InvalidLabelOnly(field_name, field_var); - if let Some(parent_id) = parent_id_opt { - let parent = ed_model.markup_node_pool.get_mut(parent_id); + let fields = PoolVec::new(vec![first_field].into_iter(), &mut ed_model.module.env.pool); - let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + let new_ast_node = Expr2::Record { record_var, fields }; - parent.add_child_at_index(new_child_index, record_field_node_id)?; - } else { - MissingParent { - node_id: curr_mark_node_id, + ed_model.module.env.pool.set(ast_node_id, new_ast_node); + + // update Markup + + let record_field_node = MarkupNode::Text { + content: new_input.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::RecordField, + attributes: Attributes::new(), + parent_id_opt, + }; + + let record_field_node_id = ed_model.markup_node_pool.add(record_field_node); + + if let Some(parent_id) = parent_id_opt { + let parent = ed_model.markup_node_pool.get_mut(parent_id); + + let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + + parent.add_child_at_index(new_child_index, record_field_node_id)?; + } else { + MissingParent { + node_id: curr_mark_node_id, + } + .fail()? } - .fail()? + + // update caret + ed_model.simple_move_carets_right(1); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + new_input, + record_field_node_id, + )?; + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) } - - // update caret - ed_model.simple_move_carets_right(1); - - // update GridNodeMap and CodeLines - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column, - new_input, - record_field_node_id, - )?; - - Ok(InputOutcome::Accepted) } else { Ok(InputOutcome::Ignored) } @@ -173,87 +181,124 @@ pub fn update_record_colon( curr_mark_node_id, curr_mark_node, parent_id_opt, - ast_node_id: _, + ast_node_id, } = get_node_context(&ed_model)?; - if let Some(parent_id) = parent_id_opt { - let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); + let curr_ast_node = ed_model.module.env.pool.get(ast_node_id); - let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; + let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; + if let Some(prev_mark_node_id) = prev_mark_node_id_opt { + let prev_mark_node = ed_model.markup_node_pool.get(prev_mark_node_id); - let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); + let prev_ast_node = ed_model + .module + .env + .pool + .get(prev_mark_node.get_ast_node_id()); - match ast_node_ref { - Expr2::Record { - record_var: _, - fields, - } => { - // update AST node - let new_field_val = Expr2::Blank; - let new_field_val_id = ed_model.module.env.pool.add(new_field_val); + // current and prev node should always point to record when in valid position to add ':' + if matches!(prev_ast_node, Expr2::Record { .. }) + && matches!(curr_ast_node, Expr2::Record { .. }) + { + let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.markup_node_pool); - let first_field_mut = fields - .iter_mut(ed_model.module.env.pool) - .next() - .with_context(|| RecordWithoutFields {})?; + let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?; - *first_field_mut = RecordField::LabeledValue( - *first_field_mut.get_record_field_pool_str(), - *first_field_mut.get_record_field_var(), - new_field_val_id, - ); + let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); - // update Markup - let record_colon = nodes::COLON; + match ast_node_ref { + Expr2::Record { + record_var: _, + fields, + } => { + if ed_model.node_exists_at_caret() { + let next_mark_node_id = + ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; + let next_mark_node = ed_model.markup_node_pool.get(next_mark_node_id); + if next_mark_node.get_content()? == nodes::RIGHT_ACCOLADE { + // update AST node + let new_field_val = Expr2::Blank; + let new_field_val_id = ed_model.module.env.pool.add(new_field_val); - let record_colon_node = MarkupNode::Text { - content: record_colon.to_owned(), - ast_node_id: record_ast_node_id, - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + let first_field_mut = fields + .iter_mut(ed_model.module.env.pool) + .next() + .with_context(|| RecordWithoutFields {})?; - let record_colon_node_id = ed_model.markup_node_pool.add(record_colon_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index(new_child_index, record_colon_node_id)?; + *first_field_mut = RecordField::LabeledValue( + *first_field_mut.get_record_field_pool_str(), + *first_field_mut.get_record_field_var(), + new_field_val_id, + ); - let record_blank_node = MarkupNode::Blank { - ast_node_id: new_field_val_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt: Some(parent_id), - }; + // update Markup + let record_colon = nodes::COLON; - let record_blank_node_id = ed_model.markup_node_pool.add(record_blank_node); - ed_model - .markup_node_pool - .get_mut(parent_id) - .add_child_at_index(new_child_index + 1, record_blank_node_id)?; + let record_colon_node = MarkupNode::Text { + content: record_colon.to_owned(), + ast_node_id: record_ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt: Some(parent_id), + }; - // update caret - ed_model.simple_move_carets_right(record_colon.len()); + let record_colon_node_id = + ed_model.markup_node_pool.add(record_colon_node); + ed_model + .markup_node_pool + .get_mut(parent_id) + .add_child_at_index(new_child_index, record_colon_node_id)?; - // update GridNodeMap and CodeLines - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column, - nodes::COLON, - record_colon_node_id, - )?; + let record_blank_node = MarkupNode::Blank { + ast_node_id: new_field_val_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt: Some(parent_id), + }; - ed_model.insert_between_line( - old_caret_pos.line, - old_caret_pos.column + nodes::COLON.len(), - nodes::BLANK_PLACEHOLDER, - record_blank_node_id, - )?; + let record_blank_node_id = + ed_model.markup_node_pool.add(record_blank_node); + ed_model + .markup_node_pool + .get_mut(parent_id) + .add_child_at_index( + new_child_index + 1, + record_blank_node_id, + )?; - Ok(InputOutcome::Accepted) + // update caret + ed_model.simple_move_carets_right(record_colon.len()); + + // update GridNodeMap and CodeLines + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column, + nodes::COLON, + record_colon_node_id, + )?; + + ed_model.insert_between_line( + old_caret_pos.line, + old_caret_pos.column + nodes::COLON.len(), + nodes::BLANK_PLACEHOLDER, + record_blank_node_id, + )?; + + Ok(InputOutcome::Accepted) + } else { + Ok(InputOutcome::Ignored) + } + } else { + Ok(InputOutcome::Ignored) + } + } + _ => Ok(InputOutcome::Ignored), + } + } else { + Ok(InputOutcome::Ignored) } - other => unimplemented!("TODO implement updating of Expr2 {:?}.", other), + } else { + Ok(InputOutcome::Ignored) } } else { MissingParent { diff --git a/editor/src/editor/resources/strings.rs b/editor/src/editor/resources/strings.rs index a4dfb174b7..d4731e8174 100644 --- a/editor/src/editor/resources/strings.rs +++ b/editor/src/editor/resources/strings.rs @@ -1,3 +1,3 @@ pub const NOTHING_OPENED: &str = "Execute `cargo run edit ` to open a file."; pub const START_TIP: &str = - "Start by typing '{' or '\"'.\nInput chars that would create parse errors will be ignored."; + "Start by typing '{', '\"' or a number.\nInput chars that would create parse errors will be ignored."; diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs index 4c196b21c0..3fe5057e7d 100644 --- a/editor/src/lang/ast.rs +++ b/editor/src/lang/ast.rs @@ -437,6 +437,9 @@ fn expr2_to_string_helper( Expr2::InvalidLookup(pool_str) => { out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); } + Expr2::SmallInt { text, .. } => { + out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); + } other => todo!("Implement for {:?}", other), } diff --git a/editor/src/lang/constrain.rs b/editor/src/lang/constrain.rs index 0e0e193120..f09137c4b3 100644 --- a/editor/src/lang/constrain.rs +++ b/editor/src/lang/constrain.rs @@ -1,34 +1,91 @@ -use crate::lang::pool::{Pool, PoolVec}; -use crate::lang::{ast::Expr2, expr::Env, types::Type2}; +use bumpalo::{collections::Vec as BumpVec, Bump}; + +use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone}; +use crate::lang::{ + ast::Expr2, + expr::Env, + types::{Type2, TypeId}, +}; use roc_can::expected::Expected; +use roc_collections::all::SendMap; use roc_module::symbol::Symbol; -use roc_region::all::Region; -use roc_types::types::Category; +use roc_region::all::{Located, Region}; +use roc_types::{ + subs::Variable, + types::{Category, Reason}, +}; #[derive(Debug)] -pub enum Constraint { +pub enum Constraint<'a> { Eq(Type2, Expected, Category, Region), // Store(Type, Variable, &'static str, u32), // Lookup(Symbol, Expected, Region), // Pattern(Region, PatternCategory, Type, PExpected), + And(BumpVec<'a, Constraint<'a>>), + Let(&'a LetConstraint<'a>), + // SaveTheEnvironment, True, // Used for things that always unify, e.g. blanks and runtime errors - // SaveTheEnvironment, - // Let(Box), - // And(Vec), } -pub fn constrain_expr( +#[derive(Debug)] +pub struct LetConstraint<'a> { + pub rigid_vars: BumpVec<'a, Variable>, + pub flex_vars: BumpVec<'a, Variable>, + pub def_types: SendMap>, + pub defs_constraint: Constraint<'a>, + pub ret_constraint: Constraint<'a>, +} + +pub fn constrain_expr<'a>( + arena: &'a Bump, env: &mut Env, expr: &Expr2, expected: Expected, region: Region, -) -> Constraint { +) -> Constraint<'a> { use Constraint::*; match expr { - Expr2::EmptyRecord => Eq(Type2::EmptyRec, expected, Category::Record, region), Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region), + Expr2::EmptyRecord => Eq(Type2::EmptyRec, expected, Category::Record, region), + Expr2::SmallInt { var, .. } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + let rigid_vars = BumpVec::new_in(arena); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + let num_type = Type2::Variable(*var); + + flex_vars.push(*var); + + let precision_var = env.var_store.fresh(); + + let range_type = Type2::Variable(precision_var); + + let range_type_id = env.pool.add(range_type); + + and_constraints.push(Eq( + num_type.shallow_clone(), + Expected::ForReason(Reason::IntLiteral, num_int(env.pool, range_type_id), region), + Category::Int, + region, + )); + + and_constraints.push(Eq(num_type, expected, Category::Int, region)); + + let defs_constraint = And(and_constraints); + + let let_constraint = arena.alloc(LetConstraint { + rigid_vars, + flex_vars, + def_types: SendMap::default(), + defs_constraint, + ret_constraint: Constraint::True, + }); + + Let(let_constraint) + } _ => todo!("implement constaints for {:?}", expr), } } @@ -36,3 +93,83 @@ pub fn constrain_expr( fn str_type(pool: &mut Pool) -> Type2 { Type2::Apply(Symbol::STR_STR, PoolVec::empty(pool)) } + +fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { + let num_integer_type = num_integer(pool, range); + let num_integer_id = pool.add(num_integer_type); + + let num_num_type = num_num(pool, num_integer_id); + let num_num_id = pool.add(num_num_type); + + Type2::Alias( + Symbol::NUM_INT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + num_num_id, + ) +} + +fn _num_signed64(pool: &mut Pool) -> Type2 { + let alias_content = Type2::TagUnion( + PoolVec::new( + // TagName::Private(Symbol::NUM_AT_SIGNED64) + vec![(PoolStr::new("Num.@Signed64", pool), PoolVec::empty(pool))].into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_SIGNED64, + PoolVec::empty(pool), + pool.add(alias_content), + ) +} + +fn num_integer(pool: &mut Pool, range: TypeId) -> Type2 { + let range_type = pool.get(range); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + // TagName::Private(Symbol::NUM_AT_INTEGER) + PoolStr::new("Num.@Integer", pool), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_INTEGER, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + pool.add(alias_content), + ) +} + +fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { + let range_type = pool.get(type_id); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + // TagName::Private(Symbol::NUM_AT_NUM) + PoolStr::new("Num.@Num", pool), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_NUM, + PoolVec::new( + vec![(PoolStr::new("range", pool), type_id)].into_iter(), + pool, + ), + pool.add(alias_content), + ) +} diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs index c2e3fd089c..8fe3975630 100644 --- a/editor/src/lang/solve.rs +++ b/editor/src/lang/solve.rs @@ -4,7 +4,7 @@ use crate::lang::constrain::Constraint::{self, *}; use crate::lang::pool::Pool; use crate::lang::types::Type2; use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::MutMap; +use roc_collections::all::{ImMap, MutMap}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::solved_types::Solved; @@ -177,7 +177,7 @@ pub fn run_in_place( #[allow(clippy::too_many_arguments)] fn solve( mempool: &mut Pool, - _env: &Env, + env: &Env, state: State, rank: Rank, pools: &mut Pools, @@ -235,387 +235,407 @@ fn solve( state } } - } // Store(source, target, _filename, _linenr) => { - // // a special version of Eq that is used to store types in the AST. - // // IT DOES NOT REPORT ERRORS! - // let actual = type_to_var(subs, rank, pools, cached_aliases, source); - // let target = *target; - // - // match unify(subs, actual, target) { - // Success(vars) => { - // introduce(subs, rank, pools, &vars); - // - // state - // } - // Failure(vars, _actual_type, _expected_type) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // BadType(vars, _problem) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // } - // } - // Lookup(symbol, expectation, region) => { - // match env.vars_by_symbol.get(&symbol) { - // Some(var) => { - // // Deep copy the vars associated with this symbol before unifying them. - // // Otherwise, suppose we have this: - // // - // // identity = \a -> a - // // - // // x = identity 5 - // // - // // When we call (identity 5), it's important that we not unify - // // on identity's original vars. If we do, the type of `identity` will be - // // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; - // // the type of `identity` is more general than that! - // // - // // Instead, we want to unify on a *copy* of its vars. If the copy unifies - // // successfully (in this case, to `Int -> Int`), we can use that to - // // infer the type of this lookup (in this case, `Int`) without ever - // // having mutated the original. - // // - // // If this Lookup is targeting a value in another module, - // // then we copy from that module's Subs into our own. If the value - // // is being looked up in this module, then we use our Subs as both - // // the source and destination. - // let actual = deep_copy_var(subs, rank, pools, *var); - // let expected = type_to_var( - // subs, - // rank, - // pools, - // cached_aliases, - // expectation.get_type_ref(), - // ); - // match unify(subs, actual, expected) { - // Success(vars) => { - // introduce(subs, rank, pools, &vars); - // - // state - // } - // - // Failure(vars, actual_type, expected_type) => { - // introduce(subs, rank, pools, &vars); - // - // let problem = TypeError::BadExpr( - // *region, - // Category::Lookup(*symbol), - // actual_type, - // expectation.clone().replace(expected_type), - // ); - // - // problems.push(problem); - // - // state - // } - // BadType(vars, problem) => { - // introduce(subs, rank, pools, &vars); - // - // problems.push(TypeError::BadType(problem)); - // - // state - // } - // } - // } - // None => { - // problems.push(TypeError::UnexposedLookup(*symbol)); - // - // state - // } - // } - // } - // And(sub_constraints) => { - // let mut state = state; - // - // for sub_constraint in sub_constraints.iter() { - // state = solve( - // env, - // state, - // rank, - // pools, - // problems, - // cached_aliases, - // subs, - // sub_constraint, - // ); - // } - // - // state - // } - // Pattern(region, category, typ, expectation) => { - // let actual = type_to_var(subs, rank, pools, cached_aliases, typ); - // let expected = type_to_var( - // subs, - // rank, - // pools, - // cached_aliases, - // expectation.get_type_ref(), - // ); - // - // match unify(subs, actual, expected) { - // Success(vars) => { - // introduce(subs, rank, pools, &vars); - // - // state - // } - // Failure(vars, actual_type, expected_type) => { - // introduce(subs, rank, pools, &vars); - // - // let problem = TypeError::BadPattern( - // *region, - // category.clone(), - // actual_type, - // expectation.clone().replace(expected_type), - // ); - // - // problems.push(problem); - // - // state - // } - // BadType(vars, problem) => { - // introduce(subs, rank, pools, &vars); - // - // problems.push(TypeError::BadType(problem)); - // - // state - // } - // } - // } - // Let(let_con) => { - // match &let_con.ret_constraint { - // True if let_con.rigid_vars.is_empty() => { - // introduce(subs, rank, pools, &let_con.flex_vars); - // - // // If the return expression is guaranteed to solve, - // // solve the assignments themselves and move on. - // solve( - // &env, - // state, - // rank, - // pools, - // problems, - // cached_aliases, - // subs, - // &let_con.defs_constraint, - // ) - // } - // ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - // let state = solve( - // env, - // state, - // rank, - // pools, - // problems, - // cached_aliases, - // subs, - // &let_con.defs_constraint, - // ); - // - // // Add a variable for each def to new_vars_by_env. - // let mut local_def_vars = ImMap::default(); - // - // for (symbol, loc_type) in let_con.def_types.iter() { - // let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); - // - // local_def_vars.insert( - // *symbol, - // Located { - // value: var, - // region: loc_type.region, - // }, - // ); - // } - // - // let mut new_env = env.clone(); - // for (symbol, loc_var) in local_def_vars.iter() { - // if !new_env.vars_by_symbol.contains_key(&symbol) { - // new_env.vars_by_symbol.insert(*symbol, loc_var.value); - // } - // } - // - // let new_state = solve( - // &new_env, - // state, - // rank, - // pools, - // problems, - // cached_aliases, - // subs, - // ret_con, - // ); - // - // for (symbol, loc_var) in local_def_vars { - // check_for_infinite_type(subs, problems, symbol, loc_var); - // } - // - // new_state - // } - // ret_con => { - // let rigid_vars = &let_con.rigid_vars; - // let flex_vars = &let_con.flex_vars; - // - // // work in the next pool to localize header - // let next_rank = rank.next(); - // - // // introduce variables - // for &var in rigid_vars.iter().chain(flex_vars.iter()) { - // subs.set_rank(var, next_rank); - // } - // - // // determine the next pool - // let next_pools; - // if next_rank.into_usize() < pools.len() { - // next_pools = pools - // } else { - // // we should be off by one at this point - // debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - // pools.extend_to(next_rank.into_usize()); - // next_pools = pools; - // } - // - // let pool: &mut Vec = next_pools.get_mut(next_rank); - // - // // Replace the contents of this pool with rigid_vars and flex_vars - // pool.clear(); - // pool.reserve(rigid_vars.len() + flex_vars.len()); - // pool.extend(rigid_vars.iter()); - // pool.extend(flex_vars.iter()); - // - // // run solver in next pool - // - // // Add a variable for each def to local_def_vars. - // let mut local_def_vars = ImMap::default(); - // - // for (symbol, loc_type) in let_con.def_types.iter() { - // let def_type = &loc_type.value; - // - // let var = - // type_to_var(subs, next_rank, next_pools, cached_aliases, def_type); - // - // local_def_vars.insert( - // *symbol, - // Located { - // value: var, - // region: loc_type.region, - // }, - // ); - // } - // - // // Solve the assignments' constraints first. - // let State { - // env: saved_env, - // mark, - // } = solve( - // &env, - // state, - // next_rank, - // next_pools, - // problems, - // cached_aliases, - // subs, - // &let_con.defs_constraint, - // ); - // - // let young_mark = mark; - // let visit_mark = young_mark.next(); - // let final_mark = visit_mark.next(); - // - // debug_assert_eq!( - // { - // let offenders = next_pools - // .get(next_rank) - // .iter() - // .filter(|var| { - // let current = subs.get_without_compacting( - // roc_types::subs::Variable::clone(var), - // ); - // - // current.rank.into_usize() > next_rank.into_usize() - // }) - // .collect::>(); - // - // let result = offenders.len(); - // - // if result > 0 { - // dbg!(&subs, &offenders, &let_con.def_types); - // } - // - // result - // }, - // 0 - // ); - // - // // pop pool - // generalize(subs, young_mark, visit_mark, next_rank, next_pools); - // - // next_pools.get_mut(next_rank).clear(); - // - // // check that things went well - // debug_assert!({ - // // NOTE the `subs.redundant` check is added for the uniqueness - // // inference, and does not come from elm. It's unclear whether this is - // // a bug with uniqueness inference (something is redundant that - // // shouldn't be) or that it just never came up in elm. - // let failing: Vec<_> = rigid_vars - // .iter() - // .filter(|&var| { - // !subs.redundant(*var) - // && subs.get_without_compacting(*var).rank != Rank::NONE - // }) - // .collect(); - // - // if !failing.is_empty() { - // println!("Rigids {:?}", &rigid_vars); - // println!("Failing {:?}", failing); - // } - // - // failing.is_empty() - // }); - // - // let mut new_env = env.clone(); - // for (symbol, loc_var) in local_def_vars.iter() { - // // when there are duplicates, keep the one from `env` - // if !new_env.vars_by_symbol.contains_key(&symbol) { - // new_env.vars_by_symbol.insert(*symbol, loc_var.value); - // } - // } - // - // // Note that this vars_by_symbol is the one returned by the - // // previous call to solve() - // let temp_state = State { - // env: saved_env, - // mark: final_mark, - // }; - // - // // Now solve the body, using the new vars_by_symbol which includes - // // the assignments' name-to-variable mappings. - // let new_state = solve( - // &new_env, - // temp_state, - // rank, - // next_pools, - // problems, - // cached_aliases, - // subs, - // &ret_con, - // ); - // - // for (symbol, loc_var) in local_def_vars { - // check_for_infinite_type(subs, problems, symbol, loc_var); - // } - // - // new_state - // } - // } - // } + } + // Store(source, target, _filename, _linenr) => { + // // a special version of Eq that is used to store types in the AST. + // // IT DOES NOT REPORT ERRORS! + // let actual = type_to_var(subs, rank, pools, cached_aliases, source); + // let target = *target; + // + // match unify(subs, actual, target) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, _actual_type, _expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // BadType(vars, _problem) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // } + // } + // Lookup(symbol, expectation, region) => { + // match env.vars_by_symbol.get(&symbol) { + // Some(var) => { + // // Deep copy the vars associated with this symbol before unifying them. + // // Otherwise, suppose we have this: + // // + // // identity = \a -> a + // // + // // x = identity 5 + // // + // // When we call (identity 5), it's important that we not unify + // // on identity's original vars. If we do, the type of `identity` will be + // // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; + // // the type of `identity` is more general than that! + // // + // // Instead, we want to unify on a *copy* of its vars. If the copy unifies + // // successfully (in this case, to `Int -> Int`), we can use that to + // // infer the type of this lookup (in this case, `Int`) without ever + // // having mutated the original. + // // + // // If this Lookup is targeting a value in another module, + // // then we copy from that module's Subs into our own. If the value + // // is being looked up in this module, then we use our Subs as both + // // the source and destination. + // let actual = deep_copy_var(subs, rank, pools, *var); + // let expected = type_to_var( + // subs, + // rank, + // pools, + // cached_aliases, + // expectation.get_type_ref(), + // ); + // match unify(subs, actual, expected) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // + // Failure(vars, actual_type, expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // let problem = TypeError::BadExpr( + // *region, + // Category::Lookup(*symbol), + // actual_type, + // expectation.clone().replace(expected_type), + // ); + // + // problems.push(problem); + // + // state + // } + // BadType(vars, problem) => { + // introduce(subs, rank, pools, &vars); + // + // problems.push(TypeError::BadType(problem)); + // + // state + // } + // } + // } + // None => { + // problems.push(TypeError::UnexposedLookup(*symbol)); + // + // state + // } + // } + // } + And(sub_constraints) => { + let mut state = state; + + for sub_constraint in sub_constraints.iter() { + state = solve( + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + sub_constraint, + ); + } + + state + } + // Pattern(region, category, typ, expectation) => { + // let actual = type_to_var(subs, rank, pools, cached_aliases, typ); + // let expected = type_to_var( + // subs, + // rank, + // pools, + // cached_aliases, + // expectation.get_type_ref(), + // ); + // + // match unify(subs, actual, expected) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, actual_type, expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // let problem = TypeError::BadPattern( + // *region, + // category.clone(), + // actual_type, + // expectation.clone().replace(expected_type), + // ); + // + // problems.push(problem); + // + // state + // } + // BadType(vars, problem) => { + // introduce(subs, rank, pools, &vars); + // + // problems.push(TypeError::BadType(problem)); + // + // state + // } + // } + // } + Let(let_con) => { + match &let_con.ret_constraint { + True if let_con.rigid_vars.is_empty() => { + introduce(subs, rank, pools, &let_con.flex_vars); + + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + solve( + mempool, + &env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ) + } + ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + let state = solve( + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + // Add a variable for each def to new_vars_by_env. + let mut local_def_vars = ImMap::default(); + + for (symbol, loc_type) in let_con.def_types.iter() { + let var = type_to_var( + mempool, + subs, + rank, + pools, + cached_aliases, + &loc_type.value, + ); + + local_def_vars.insert( + *symbol, + Located { + value: var, + region: loc_type.region, + }, + ); + } + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + let new_state = solve( + mempool, + &new_env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + ret_con => { + let rigid_vars = &let_con.rigid_vars; + let flex_vars = &let_con.flex_vars; + + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); + } + + // determine the next pool + let next_pools; + if next_rank.into_usize() < pools.len() { + next_pools = pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); + next_pools = pools; + } + + let pool: &mut Vec = next_pools.get_mut(next_rank); + + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); + + // run solver in next pool + + // Add a variable for each def to local_def_vars. + let mut local_def_vars = ImMap::default(); + + for (symbol, loc_type) in let_con.def_types.iter() { + let def_type = &loc_type.value; + + let var = type_to_var( + mempool, + subs, + next_rank, + next_pools, + cached_aliases, + def_type, + ); + + local_def_vars.insert( + *symbol, + Located { + value: var, + region: loc_type.region, + }, + ); + } + + // Solve the assignments' constraints first. + let State { + env: saved_env, + mark, + } = solve( + mempool, + &env, + state, + next_rank, + next_pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + debug_assert_eq!( + { + let offenders = next_pools + .get(next_rank) + .iter() + .filter(|var| { + let current = subs.get_without_compacting( + roc_types::subs::Variable::clone(var), + ); + + current.rank.into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, next_pools); + + next_pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| { + !subs.redundant(*var) + && subs.get_without_compacting(*var).rank != Rank::NONE + }) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + // when there are duplicates, keep the one from `env` + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let temp_state = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + let new_state = solve( + mempool, + &new_env, + temp_state, + rank, + next_pools, + problems, + cached_aliases, + subs, + &ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + } + } // _ => todo!("implement {:?}", constraint), } } @@ -641,7 +661,7 @@ pub fn insert_type_into_subs(mempool: &mut Pool, subs: &mut Subs, typ: &Type2) - } fn type_to_variable( - mempool: &mut Pool, + mempool: &Pool, subs: &mut Subs, rank: Rank, pools: &mut Pools, @@ -707,6 +727,95 @@ fn type_to_variable( register(subs, rank, pools, content) } + Alias(Symbol::BOOL_BOOL, _, _) => roc_types::subs::Variable::BOOL, + Alias(symbol, args, alias_type_id) => { + // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! + // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) + // different variables (once for each occurence). The recursion restriction is required + // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness + // attribute in the body, when + // + // Peano : [ S Peano, Z ] + // + // becomes + // + // Peano : [ S (Attr u Peano), Z ] + // + // This `u` variable can be different between lists, so giving just one variable to + // this type is incorrect. + // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable + + let alias_type = mempool.get(*alias_type_id); + let is_recursive = false; // alias_type.is_recursive(); + let no_args = args.is_empty(); + /* + if no_args && !is_recursive { + if let Some(var) = cached.get(symbol) { + return *var; + } + } + */ + + let mut arg_vars = Vec::with_capacity(args.len()); + let mut new_aliases = ImMap::default(); + + for (arg, arg_type_id) in args.iter(mempool) { + let arg_type = mempool.get(*arg_type_id); + + let arg_var = type_to_variable(mempool, subs, rank, pools, cached, arg_type); + + let arg_str = arg.as_str(mempool); + + arg_vars.push((roc_module::ident::Lowercase::from(arg_str), arg_var)); + new_aliases.insert(arg_str, arg_var); + } + + let alias_var = type_to_variable(mempool, subs, rank, pools, cached, alias_type); + let content = Content::Alias(*symbol, arg_vars, alias_var); + + let result = register(subs, rank, pools, content); + + if no_args && !is_recursive { + // cached.insert(*symbol, result); + } + + result + } + TagUnion(tags, ext_id) => { + let mut tag_vars = MutMap::default(); + let ext = mempool.get(*ext_id); + + for (_tag, tag_argument_types) in tags.iter(mempool) { + let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); + + for arg_type in tag_argument_types.iter(mempool) { + tag_argument_vars.push(type_to_variable( + mempool, subs, rank, pools, cached, arg_type, + )); + } + + tag_vars.insert( + roc_module::ident::TagName::Private(Symbol::NUM_NUM), + tag_argument_vars, + ); + } + + let temp_ext_var = type_to_variable(mempool, subs, rank, pools, cached, ext); + let mut ext_tag_vec = Vec::new(); + let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + subs, + temp_ext_var, + &mut ext_tag_vec, + ) { + Ok(()) => roc_types::subs::Variable::EMPTY_TAG_UNION, + Err((new, _)) => new, + }; + tag_vars.extend(ext_tag_vec.into_iter()); + + let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); + + register(subs, rank, pools, content) + } other => todo!("not implemented {:?}", &other), // // // This case is important for the rank of boolean variables @@ -723,35 +832,6 @@ fn type_to_variable( // // register(subs, rank, pools, content) // } - // TagUnion(tags, ext) => { - // let mut tag_vars = MutMap::default(); - // - // for (tag, tag_argument_types) in tags { - // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - // - // for arg_type in tag_argument_types { - // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); - // } - // - // tag_vars.insert(tag.clone(), tag_argument_vars); - // } - // - // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - // let mut ext_tag_vec = Vec::new(); - // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - // subs, - // temp_ext_var, - // &mut ext_tag_vec, - // ) { - // Ok(()) => Variable::EMPTY_TAG_UNION, - // Err((new, _)) => new, - // }; - // tag_vars.extend(ext_tag_vec.into_iter()); - // - // let content = Content::Structure(FlatType::TagUnion(tag_vars, new_ext_var)); - // - // register(subs, rank, pools, content) - // } // RecursiveTagUnion(rec_var, tags, ext) => { // let mut tag_vars = MutMap::default(); // @@ -792,54 +872,6 @@ fn type_to_variable( // // tag_union_var // } - // Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, - // Alias(symbol, args, alias_type) => { - // // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! - // // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) - // // different variables (once for each occurence). The recursion restriction is required - // // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness - // // attribute in the body, when - // // - // // Peano : [ S Peano, Z ] - // // - // // becomes - // // - // // Peano : [ S (Attr u Peano), Z ] - // // - // // This `u` variable can be different between lists, so giving just one variable to - // // this type is incorrect. - // // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable - // let is_recursive = alias_type.is_recursive(); - // let no_args = args.is_empty(); - // /* - // if no_args && !is_recursive { - // if let Some(var) = cached.get(symbol) { - // return *var; - // } - // } - // */ - // - // let mut arg_vars = Vec::with_capacity(args.len()); - // let mut new_aliases = ImMap::default(); - // - // for (arg, arg_type) in args { - // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); - // - // arg_vars.push((arg.clone(), arg_var)); - // new_aliases.insert(arg.clone(), arg_var); - // } - // - // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); - // let content = Content::Alias(*symbol, arg_vars, alias_var); - // - // let result = register(subs, rank, pools, content); - // - // if no_args && !is_recursive { - // // cached.insert(*symbol, result); - // } - // - // result - // } // HostExposedAlias { // name: symbol, // arguments: args, diff --git a/editor/src/lang/types.rs b/editor/src/lang/types.rs index 41b353e5cc..95edc0e143 100644 --- a/editor/src/lang/types.rs +++ b/editor/src/lang/types.rs @@ -60,7 +60,13 @@ pub enum Problem2 { impl ShallowClone for Type2 { fn shallow_clone(&self) -> Self { - todo!() + match self { + Self::Variable(var) => Self::Variable(*var), + Self::Alias(symbol, pool_vec, type_id) => { + Self::Alias(*symbol, pool_vec.shallow_clone(), type_id.clone()) + } + rest => todo!("{:?}", rest), + } } } diff --git a/editor/src/ui/text/text_pos.rs b/editor/src/ui/text/text_pos.rs index ef5d8507a8..894fb9c568 100644 --- a/editor/src/ui/text/text_pos.rs +++ b/editor/src/ui/text/text_pos.rs @@ -13,6 +13,19 @@ impl TextPos { column: self.column + 1, } } + + pub fn decrement_col(&self) -> TextPos { + let new_col = if self.column > 0 { + self.column - 1 + } else { + self.column + }; + + TextPos { + line: self.line, + column: new_col, + } + } } impl Ord for TextPos { diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs index a0e9290a00..345ac67388 100644 --- a/editor/tests/solve_expr2.rs +++ b/editor/tests/solve_expr2.rs @@ -16,6 +16,7 @@ use roc_editor::lang::{ types::Type2, }; use roc_module::ident::Lowercase; +use roc_module::symbol::Interns; use roc_module::symbol::Symbol; use roc_module::symbol::{IdentIds, ModuleIds}; use roc_region::all::Region; @@ -83,6 +84,7 @@ fn infer_eq(actual: &str, expected_str: &str) { match expr2_result { Ok((expr, _)) => { let constraint = constrain_expr( + &code_arena, &mut env, &expr, Expected::NoExpectation(Type2::Variable(var)), @@ -92,6 +94,7 @@ fn infer_eq(actual: &str, expected_str: &str) { let Env { pool, var_store: ref_var_store, + dep_idents, .. } = env; @@ -111,7 +114,11 @@ fn infer_eq(actual: &str, expected_str: &str) { let content = subs.get(var).content; - let actual_str = content_to_string(content, &subs, mod_id, &Default::default()); + let interns = Interns { + module_ids, + all_ident_ids: dep_idents, + }; + let actual_str = content_to_string(content, &subs, mod_id, &interns); assert_eq!(actual_str, expected_str); } @@ -142,3 +149,15 @@ fn constrain_empty_record() { "{}", ) } + +#[test] +fn constrain_small_int() { + infer_eq( + indoc!( + r#" + 12 + "# + ), + "Int *", + ) +}