1 //: Phase 3: Start running a loaded and transformed recipe.
  2 //:
  3 //:   The process of running Mu code:
  4 //:     load -> transform -> run
  5 //:
  6 //: So far we've seen recipes as lists of instructions, and instructions point
  7 //: at other recipes. To kick things off Mu needs to know how to run certain
  8 //: 'primitive' recipes. That will then give the ability to run recipes
  9 //: containing these primitives.
 10 //:
 11 //: This layer defines a skeleton with just two primitive recipes: IDLE which
 12 //: does nothing, and COPY, which can copy numbers from one memory location to
 13 //: another. Later layers will add more primitives.
 14 
 15 :(scenario copy_literal)
 16 def main [
 17   1:num <- copy 23
 18 ]
 19 +run: {1: "number"} <- copy {23: "literal"}
 20 +mem: storing 23 in location 1
 21 
 22 :(scenario copy)
 23 def main [
 24   1:num <- copy 23
 25   2:num <- copy 1:num
 26 ]
 27 +run: {2: "number"} <- copy {1: "number"}
 28 +mem: location 1 is 23
 29 +mem: storing 23 in location 2
 30 
 31 :(scenario copy_multiple)
 32 def main [
 33   1:num, 2:num <- copy 23, 24
 34 ]
 35 +mem: storing 23 in location 1
 36 +mem: storing 24 in location 2
 37 
 38 :(before "End Types")
 39 // Book-keeping while running a recipe.
 40 //: Later layers will replace this to support running multiple routines at once.
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
package msg

import (
	"errors"
	"time"

	"git.sr.ht/~sircmpwn/aerc/lib"
	"git.sr.ht/~sircmpwn/aerc/models"
	"git.sr.ht/~sircmpwn/aerc/widgets"
	"git.sr.ht/~sircmpwn/aerc/worker/types"
)

type Delete struct{}

func init() {
	register(Delete{})
}

func (Delete) Aliases() []string {
	return []string{"delete", "delete-message"}
}

func (Delete) Complete(aerc *widgets.Aerc, args []string) []string {
	return nil
}

func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
	if len(args) != 1 {
		return errors.New("Usage: :delete")
	}

	h := newHelper(aerc)
	store, err := h.store()
	if err != nil {
		return err
	}
	uids, err := h.markedOrSelectedUids()
	if err != nil {
		return err
	}
	acct, err := h.account()
	if err != nil {
		return err
	}
	store.Delete(uids, func(msg types.WorkerMessage) {
		switch msg := msg.(type) {
		case *types.Done:
			aerc.PushStatus("Messages deleted.", 10*time.Second)
		case *types.Error:
			aerc.PushError(" " + msg.Error.Error())
		}
	})

	//caution, can be nil
	next := findNextNonDeleted(uids, store)

	mv, isMsgView := h.msgProvider.(*widgets.MessageViewer)
	if isMsgView {
		if !aerc.Config().Ui.NextMessageOnDelete {
			aerc.RemoveTab(h.msgProvider)
		} else {
			// no more messages in the list
			if next == nil {
				aerc.RemoveTab(h.msgProvider)
				acct.Messages().Scroll()
				return nil
			}
			lib.NewMessageStoreView(next, store, aerc.DecryptKeys,
				func(view lib.MessageView, err error) {
					if err != nil {
						aerc.PushError(err.Error())
						return
					}
					nextMv := widgets.NewMessageViewer(acct, aerc.Config(), view)
					aerc.ReplaceTab(mv, nextMv, next.Envelope.Subject)
				})
		}
	}
	acct.Messages().Scroll()
	return nil
}

func findNextNonDeleted(deleted []uint32, store *lib.MessageStore) *models.MessageInfo {
	selected := store.Selected()
	if !contains(deleted, selected.Uid) {
		return selected
	}
	for {
		store.Next()
		next := store.Selected()
		if next == selected || next == nil {
			// the last message is in the deleted state or doesn't exist
			return nil
		}
		return next
	}
	return nil // Never reached
}

func contains(uids []uint32, uid uint32) bool {
	for _, item := range uids {
		if item == uid {
			return true
		}
	}
	return false
}
176 //? exit(0); 177 178 //: Step 2: load any .mu files provided at the commandline 179 :(before "End Commandline Parsing") 180 // Check For .mu Files 181 //? START_TRACING_UNTIL_END_OF_SCOPE 182 //? Dump_trace = true; 183 if (argc > 1) { 184 // skip argv[0] 185 ++argv; 186 --argc; 187 while (argc > 0) { 188 ¦ // ignore argv past '--'; that's commandline args for 'main' 189 ¦ if (string(*argv) == "--") break; 190 ¦ if (starts_with(*argv, "--")) 191 ¦ ¦ cerr << "treating " << *argv << " as a file rather than an option\n"; 192 ¦ load_file_or_directory(*argv); 193 ¦ --argc; 194 ¦ ++argv; 195 } 196 if (Run_tests) Recipe.erase(get(Recipe_ordinal, "main")); 197 } 198 transform_all(); 199 //? cerr << to_original_string(get(Type_ordinal, "editor")) << '\n'; 200 //? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "event-loop"))) << '\n'; 201 //? DUMP(""); 202 //? exit(0); 203 if (trace_contains_errors()) return 1; 204 save_snapshots(); 205 206 //: Step 3: if we aren't running tests, locate a recipe called 'main' and 207 //: start running it. 208 :(before "End Main") 209 if (!Run_tests && contains_key(Recipe_ordinal, "main") && contains_key(Recipe, get(Recipe_ordinal, "main"))) { 210 // Running Main 211 reset(); 212 if (Start_tracing) { 213 ¦ Trace_stream = new trace_stream; 214 ¦ Save_trace = true; 215 } 216 trace(2, "run") << "=== Starting to run" << end(); 217 assert(Num_calls_to_transform_all == 1); 218 run_main(argc, argv); 219 } 220 :(code) 221 void run_main(int argc, char* argv[]) { 222 recipe_ordinal r = get(Recipe_ordinal, "main"); 223 if (r) run(r); 224 } 225 226 //: By default we don't maintain the trace while running main because its 227 //: overheads can grow rapidly. However, it's useful when debugging. 228 :(before "End Globals") 229 bool Start_tracing = false; 230 :(before "End Commandline Options(*arg)") 231 else if (is_equal(*arg, "--trace")) { 232 Start_tracing = true; 233 } 234 235 :(code) 236 void cleanup_main() { 237 if (Save_trace && Trace_stream) { 238 ¦ cerr << "writing trace to 'last_run'\n"; 239 ¦ ofstream fout("last_run"); 240 ¦ fout << Trace_stream->readable_contents(""); 241 ¦ fout.close(); 242 } 243 if (Trace_stream) delete Trace_stream, Trace_stream = NULL; 244 } 245 :(before "End One-time Setup") 246 atexit(cleanup_main); 247 248 :(code) 249 void load_file_or_directory(string filename) { 250 if (is_directory(filename)) { 251 ¦ load_all(filename); 252 ¦ return; 253 } 254 ifstream fin(filename.c_str()); 255 if (!fin) { 256 ¦ cerr << "no such file '" << filename << "'\n" << end(); // don't raise, just warn. just in case it's just a name for a scenario to run. 257 ¦ return; 258 } 259 trace(9990, "load") << "=== " << filename << end(); 260 load(fin); 261 fin.close(); 262 } 263 264 bool is_directory(string path) { 265 struct stat info; 266 if (stat(path.c_str(), &info)) return false; // error 267 return info.st_mode & S_IFDIR; 268 } 269 270 void load_all(string dir) { 271 dirent** files; 272 int num_files = scandir(dir.c_str(), &files, NULL, alphasort); 273 for (int i = 0; i < num_files; ++i) { 274 ¦ string curr_file = files[i]->d_name; 275 ¦ if (isdigit(curr_file.at(0))) 276 ¦ ¦ load_file_or_directory(dir+'/'+curr_file); 277 ¦ free(files[i]); 278 ¦ files[i] = NULL; 279 } 280 free(files); 281 } 282 :(before "End Includes") 283 #include <dirent.h> 284 #include <sys/stat.h> 285 286 //:: Reading from memory, writing to memory. 287 288 :(code) 289 vector<double> read_memory(reagent/*copy*/ x) { 290 // Begin Preprocess read_memory(x) 291 vector<double> result; 292 if (is_literal(x)) { 293 ¦ result.push_back(x.value); 294 ¦ return result; 295 } 296 // End Preprocess read_memory(x) 297 int size = size_of(x); 298 for (int offset = 0; offset < size; ++offset) { 299 ¦ double val = get_or_insert(Memory, x.value+offset); 300 ¦ trace(9999, "mem") << "location " << x.value+offset << " is " << no_scientific(val) << end(); 301 ¦ result.push_back(val); 302 } 303 return result; 304 } 305 306 void write_memory(reagent/*copy*/ x, const vector<double>& data) { 307 assert(Current_routine); // run-time only 308 // Begin Preprocess write_memory(x, data) 309 if (!x.type) { 310 ¦ raise << "can't write to '" << to_string(x) << "'; no type\n" << end(); 311 ¦ return; 312 } 313 if (is_dummy(x)) return; 314 if (is_literal(x)) return; 315 // End Preprocess write_memory(x, data) 316 if (x.value == 0) { 317 ¦ raise << "can't write to location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); 318 ¦ return; 319 } 320 if (size_mismatch(x, data)) { 321 ¦ raise << maybe(current_recipe_name()) << "size mismatch in storing to '" << x.original_string << "' (" << size_of(x) << " vs " << SIZE(data) << ") at '" << to_original_string(current_instruction()) << "'\n" << end(); 322 ¦ return; 323 } 324 // End write_memory(x) Special-cases 325 for (int offset = 0; offset < SIZE(data); ++offset) { 326 ¦ assert(x.value+offset > 0); 327 ¦ trace(9999, "mem") << "storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << end(); 328 ¦ put(Memory, x.value+offset, data.at(offset)); 329 } 330 } 331 332 :(code) 333 int size_of(const reagent& r) { 334 if (!r.type) return 0; 335 // End size_of(reagent r) Special-cases 336 return size_of(r.type); 337 } 338 int size_of(const type_tree* type) { 339 if (!type) return 0; 340 if (type->atom) { 341 ¦ if (type->value == -1) return 1; // error value, but we'll raise it elsewhere 342 ¦ if (type->value == 0) return 1; 343 ¦ // End size_of(type) Atom Special-cases 344 } 345 else { 346 ¦ if (!type->left->atom) { 347 ¦ ¦ raise << "invalid type " << to_string(type) << '\n' << end(); 348 ¦ ¦ return 0; 349 ¦ } 350 ¦ if (type->left->value == get(Type_ordinal, "address")) return 1; 351 ¦ // End size_of(type) Non-atom Special-cases 352 } 353 // End size_of(type) Special-cases 354 return 1; 355 } 356 357 bool size_mismatch(const reagent& x, const vector<double>& data) { 358 if (!x.type) return true; 359 // End size_mismatch(x) Special-cases 360 //? if (size_of(x) != SIZE(data)) cerr << size_of(x) << " vs " << SIZE(data) << '\n'; 361 return size_of(x) != SIZE(data); 362 } 363 364 bool is_literal(const reagent& r) { 365 return is_literal(r.type); 366 } 367 bool is_literal(const type_tree* type) { 368 if (!type) return false; 369 if (!type->atom) return false; 370 return type->value == 0; 371 } 372 373 bool scalar(const vector<int>& x) { 374 return SIZE(x) == 1; 375 } 376 bool scalar(const vector<double>& x) { 377 return SIZE(x) == 1; 378 } 379 380 // helper for tests 381 void run(const string& form) { 382 vector<recipe_ordinal> tmp = load(form); 383 transform_all(); 384 if (tmp.empty()) return; 385 if (trace_contains_errors()) return; 386 // if a test defines main, it probably wants to start there regardless of 387 // definition order 388 if (contains_key(Recipe, get(Recipe_ordinal, "main"))) 389 ¦ run(get(Recipe_ordinal, "main")); 390 else 391 ¦ run(tmp.front()); 392 } 393 394 :(scenario run_label) 395 def main [ 396 +foo 397 1:num <- copy 23 398 2:num <- copy 1:num 399 ] 400 +run: {1: "number"} <- copy {23: "literal"} 401 +run: {2: "number"} <- copy {1: "number"} 402 -run: +foo 403 404 :(scenario run_dummy) 405 def main [ 406 _ <- copy 0 407 ] 408 +run: _ <- copy {0: "literal"} 409 410 :(scenario write_to_0_disallowed) 411 % Hide_errors = true; 412 def main [ 413 0:num <- copy 34 414 ] 415 -mem: storing 34 in location 0 416 417 //: Mu is robust to various combinations of commas and spaces. You just have 418 //: to put spaces around the '<-'. 419 420 :(scenario comma_without_space) 421 def main [ 422 1:num, 2:num <- copy 2,2 423 ] 424 +mem: storing 2 in location 1 425 426 :(scenario space_without_comma) 427 def main [ 428 1:num, 2:num <- copy 2 2 429 ] 430 +mem: storing 2 in location 1 431 432 :(scenario comma_before_space) 433 def main [ 434 1:num, 2:num <- copy 2, 2 435 ] 436 +mem: storing 2 in location 1 437 438 :(scenario comma_after_space) 439 def main [ 440 1:num, 2:num <- copy 2 ,2 441 ] 442 +mem: storing 2 in location 1 443 444 //:: Counters for trying to understand where Mu programs are spending their 445 //:: time. 446 447 :(before "End Globals") 448 bool Run_profiler = false; 449 // We'll key profile information by recipe_ordinal rather than name because 450 // it's more efficient, and because later layers will show more than just the 451 // name of a recipe. 452 // 453 // One drawback: if you're clearing recipes your profile will be inaccurate. 454 // So far that happens in tests, and in 'run-sandboxed' in a later layer. 455 map<recipe_ordinal, int> Instructions_running; 456 :(before "End Commandline Options(*arg)") 457 else if (is_equal(*arg, "--profile")) { 458 Run_profiler = true; 459 } 460 :(after "Running One Instruction") 461 if (Run_profiler) Instructions_running[currently_running_recipe()]++; 462 :(before "End One-time Setup") 463 atexit(dump_profile); 464 :(code) 465 void dump_profile() { 466 if (!Run_profiler) return; 467 if (Run_tests) { 468 ¦ cerr << "It's not a good idea to profile a run with tests, since tests can create conflicting recipes and mislead you. To try it anyway, comment out this check in the code.\n"; 469 ¦ return; 470 } 471 ofstream fout; 472 fout.open("profile.instructions"); 473 if (fout) { 474 ¦ for (map<recipe_ordinal, int>::iterator p = Instructions_running.begin(); p != Instructions_running.end(); ++p) { 475 ¦ ¦ fout << std::setw(9) << p->second << ' ' << header_label(p->first) << '\n'; 476 ¦ } 477 } 478 fout.close(); 479 // End dump_profile 480 } 481 482 // overridden in a later layer 483 string header_label(const recipe_ordinal r) { 484 return get(Recipe, r).name; 485 }