diff --git a/Cargo.lock b/Cargo.lock index 6b12ea25e..6219264f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,6 +186,17 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "littlewing" +version = "0.6.0" +source = "git+https://github.com/vinc/littlewing?branch=feature/add-no-std-support#830b3b596eb95d6d0179d6ab1789e32a64695d5a" +dependencies = [ + "lazy_static", + "no-std-compat", + "rand 0.7.3", + "rand_xorshift", +] + [[package]] name = "lock_api" version = "0.4.4" @@ -225,12 +236,13 @@ dependencies = [ "lazy_static", "libm", "linked_list_allocator", + "littlewing", "pbkdf2", "pc-keyboard", "pic8259", - "rand", - "rand_chacha", - "rand_core", + "rand 0.8.4", + "rand_chacha 0.3.0", + "rand_core 0.6.3", "raw-cpuid", "sha2", "smoltcp", @@ -241,6 +253,12 @@ dependencies = [ "x86_64", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "num-traits" version = "0.2.14" @@ -310,13 +328,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -326,15 +365,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.3", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "raw-cpuid" version = "10.2.0" diff --git a/Cargo.toml b/Cargo.toml index f6dc5a47e..84495dd9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ time = { version = "0.2.27", default-features = false } uart_16550 = "0.2.15" vte = "0.10.1" x86_64 = "0.14.4" +littlewing = { git = "https://github.com/vinc/littlewing", branch = "feature/add-no-std-support", default-features = false } +#littlewing = { path = "../littlewing", default-features = false } [package.metadata.bootimage] test-success-exit-code = 33 # (0x10 << 1) | 1 diff --git a/doc/chess.png b/doc/chess.png new file mode 100644 index 000000000..2d6ab940f Binary files /dev/null and b/doc/chess.png differ diff --git a/doc/index.md b/doc/index.md index 2a795818c..f99d12e82 100644 --- a/doc/index.md +++ b/doc/index.md @@ -2,27 +2,100 @@ MOROS is a hobby operating system written in Rust for the x86 architecture. +It targets 64 bits processors with a legacy BIOS, so roughly computers from the +2010 era, but it also run well on most hypervisors. + ![screenshot](moros.png) -Everything in MOROS is done from a command line interface and most tools are +The first task when running the OS is to install the +[filesystem](filesystem.md) on a disk (or in RAM) using the `install` program, +although it's possible to skip the installation and stay in read only mode. + +Everything in MOROS is done from a command line interface and most programs are rather minimalist. -It has a shell: +It has a [shell](shell.md): ![screenshot](shell.png) -With a few tools like `find` that use a regex engine to find files or lines: +With a few programs like `find` that use a [regex engine](regex.md) to find +files or lines: ![screenshot](find.png) -It also has a lisp interpreter: +It also has a [lisp](lisp.md) interpreter: ![screenshot](lisp.png) -And a text editor: +And a [text editor](editor.md): ![screenshot](edit.png) -It has a network stack with two drivers for RTL81339 and PCNET cards: +It has a [network stack](network.md) with two drivers for RTL81339 and PCNET cards: ![screenshot](network.png) + +It even has a chess game: + +![chess](chess.png) + +Finally here are a few commands to try it out: + + > date + 2021-08-12T20:16:48 + + > memory + Size: 16777216 + Used: 15400 + Free: 16761816 + + > disk + Size: 8388608 + Used: 445440 + Free: 7943168 + + > list /tmp + 5083 2021-08-07 15:10:09 alice.txt + 118 2021-08-07 15:10:09 fibonacci.lisp + + > goto /tmp + + > read fibonacci.lisp + (label fib + (lambda (n) + (cond + ((< n 2) n) + (true (+ (fib (- n 1)) (fib (- n 2))))))) + + (print (fib 6)) + + > lisp fibonacci.lisp + 8 + + > find /tmp --line "p.*nt" + /tmp/alice.txt + 9: bank, and of having nothing to do: once or twice she had peeped into the + 36: dipped suddenly down, so suddenly that Alice had not a moment to think + 41: plenty of time as she went down to look about her and to wonder what was + 48: disappointment it was empty: she did not like to drop the jar for fear + 49: of killing somebody, so managed to put it into one of the cupboards as + 85: began to get rather sleepy, and went on saying to herself, in a dreamy + + /tmp/fibonacci.lisp + 7: (print (fib 6)) + + > dhcp + DHCP Discover transmitted + DHCP Offer received + Leased: 10.0.2.15/24 + Router: 10.0.2.2 + DNS: 10.0.2.3 + + > tcp time.nist.gov 13 + Connecting to 132.163.97.4:13 + + 59438 21-08-12 20:18:27 50 0 0 358.8 UTC(NIST) * + + > halt + MOROS has reached its fate, the system is now halting. + [782.191890] ACPI Shutdown diff --git a/doc/lisp.md b/doc/lisp.md index 6a4af34f6..c173cf5fa 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -3,7 +3,7 @@ A minimalist Lisp interpreter is available in MOROS to extend the capabilities of the Shell. -It started from [Risp][https://github.com/stopachka/risp] and was extended to +It started from [Risp](https://github.com/stopachka/risp) and was extended to include the seven primitive operators and the two special forms of John McCarthy's paper "Recursive Functions of Symbolic Expressions and Their Computation by Machine" (1960) and "The Roots of Lisp" (2002) by Paul Graham. diff --git a/src/sys/allocator.rs b/src/sys/allocator.rs index aa35fceb5..804a3f077 100644 --- a/src/sys/allocator.rs +++ b/src/sys/allocator.rs @@ -11,7 +11,7 @@ use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, S use x86_64::VirtAddr; pub const HEAP_START: usize = 0x_4444_4444_0000; -pub const HEAP_SIZE: usize = 8 << 20; // MB +pub const HEAP_SIZE: usize = 16 << 20; // MB #[global_allocator] static ALLOCATOR: LockedHeap = LockedHeap::empty(); diff --git a/src/sys/fs/block_device.rs b/src/sys/fs/block_device.rs index 062daef29..59e8e75f3 100644 --- a/src/sys/fs/block_device.rs +++ b/src/sys/fs/block_device.rs @@ -63,11 +63,7 @@ impl BlockDeviceIO for MemBlockDevice { } pub fn mount_mem() { - let len = sys::allocator::HEAP_SIZE / 2 / 512; - // FIXME: `len` should be equal to `super::DISK_SIZE` which is set during - // compilation for now. But that's not the case because the allocator is - // too slow to allocate more than a few megabytes of memory. So we take - // half of the heap and will panic when this get full. + let len = super::DISK_SIZE / 2; let dev = MemBlockDevice::new(len); *BLOCK_DEVICE.lock() = Some(BlockDevice::Mem(dev)); } diff --git a/src/usr/chess.rs b/src/usr/chess.rs new file mode 100644 index 000000000..64196651c --- /dev/null +++ b/src/usr/chess.rs @@ -0,0 +1,289 @@ +use crate::{api, usr, sys}; +use crate::api::console::Style; +use crate::api::prompt::Prompt; + +use alloc::format; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; + +use littlewing::chess::*; +use lazy_static::lazy_static; +use spin::Mutex; + +lazy_static! { + static ref MOVES: Mutex> = Mutex::new(Vec::new()); +} + +const FEN: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +const COMMANDS: [&str; 8] = ["quit", "help", "init", "time", "move", "undo", "show", "perf"]; + +fn update_autocomplete(prompt: &mut Prompt, game: &mut Game) { + *MOVES.lock() = game.get_moves().into_iter().map(|m| m.to_lan()).collect(); + + fn chess_completer(line: &str) -> Vec { + let mut entries = Vec::new(); + let args: Vec<&str> = line.split(' ').collect(); + let i = args.len() - 1; + if i == 0 { // Autocomplete command + for &cmd in &COMMANDS { + if let Some(entry) = cmd.strip_prefix(args[i]) { + entries.push(entry.into()); + } + } + } else if i == 1 && (args[0] == "move" || args[0] == "m") { // Autocomplete moves + for m in &*MOVES.lock() { + if let Some(entry) = m.strip_prefix(args[1]) { + entries.push(entry.into()); + } + } + } + entries + } + prompt.completion.set(&chess_completer); +} + +fn system_time() -> u128 { + (api::syscall::realtime() * 1000.0) as u128 +} + +struct Chess { + game: Game, + csi_color: Style, + csi_error: Style, + csi_notif: Style, + csi_reset: Style, +} + +impl Chess { + fn new() -> Self { + Self { + game: Game::new(), + csi_color: Style::color("Cyan"), + csi_error: Style::color("LightRed"), + csi_notif: Style::color("Yellow"), + csi_reset: Style::reset(), + } + } + + fn play(&mut self) { + println!("MOROS Chess v0.1.0\n"); + let prompt_string = format!("{}>{} ", self.csi_color, self.csi_reset); + + let mut prompt = Prompt::new(); + let history_file = "~/.chess-history"; + prompt.history.load(history_file); + + self.game.show_coordinates = true; + self.game.clock = Clock::new(40, 5 * 60 * 1000); // 40 moves in 5 minutes + self.game.clock.system_time = Arc::new(system_time); + let size = 1 << 20; // MB + self.game.tt_resize(size); + self.game.load_fen(FEN).unwrap(); + println!("{}", self.game); + + update_autocomplete(&mut prompt, &mut self.game); + while let Some(cmd) = prompt.input(&prompt_string) { + let args: Vec<&str> = cmd.trim().split(' ').collect(); + match args[0] { + "q" | "quit" | "exit" => break, + "h" | "help" => self.cmd_help(args), + "i" | "init" => self.cmd_init(args), + "t" | "time" => self.cmd_time(args), + "m" | "move" => self.cmd_move(args), + "u" | "undo" => self.cmd_undo(args), + "s" | "show" => self.cmd_show(args), + "p" | "perf" => self.cmd_perf(args), + cmd => { + if cmd.is_empty() { + println!(); + } else { + println!("{}Error:{} unknown command '{}'\n", self.csi_error, self.csi_reset, cmd); + } + } + } + prompt.history.add(&cmd); + prompt.history.save(history_file); + update_autocomplete(&mut prompt, &mut self.game); + } + } + + fn cmd_help(&mut self, _args: Vec<&str>) { + println!("{}Commands:{}", self.csi_notif, self.csi_reset); + println!(); + let cmds = [ + ("q", "uit", "Exit this program\n"), + ("h", "elp", "Display this screen\n"), + ("i", "nit", "Initialize a new game\n"), + ("t", "ime