# i eventually want the shell to be a standalone program and shit fn pad(text, width) do let result = text while result:len() < width do result = result ~ " " end return result end fn help_row(label, description) puts(" #{pad(label, 20)}#{description}\n") fn help_group(title, group_name) do puts("\n #{title}\n") for index in 0..commands:len() do if commands[index].group == group_name do help_row(commands[index].usage, commands[index].help) end end end fn cmd_help(context) do help_group("shell", "shell") help_group("gils (builtin)", "gils") # the rest of gils, and the programs, are real files in /bin now const items = fs:entries("/bin") if items != :nil do puts("\n gils (/bin scripts)\n") for index in 0..items:len() do const name = items[index].name if (name:len() > 3 and name[(name:len() - 3)..] == ".rv") do help_row(name[0..(name:len() - 3)], "") end end puts("\n programs (/bin)\n") for index in 0..items:len() do const name = items[index].name if (name:len() <= 3 or name[(name:len() - 3)..] != ".rv") do help_row(name, "") end end end puts("\n") end fn welcome() do let fs_status = "no filesystem" if fs.ready do fs_status = "revelfs mounted" end puts("\n") puts(" kernel written in revo - by an alcoholic\n") puts(" #{(mem_total() shr 20)} MiB ram | #{fs_status} | #{prog_count()} programs\n") puts(" type help to see what it does\n\n% ") end # global so /bin scripts can call it from a nested eval fiber global arg_name = fn(line, start) do let quoted = :false let begin = start if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end let index = begin if quoted do while (index < line:len() and line[index] != "\"") do index += 1 end end else do while (index < line:len() and line[index] != " ") do index += 1 end end const name = line[begin..index] let rest = index if (quoted and rest < line:len()) do rest += 1 end if (rest < line:len() and line[rest] == " ") do rest += 1 end { name = name, rest = rest } end fn parent_of(path) do if path == "/" return "/" let index = path:len() - 1 while (index > 0 and path[index] != "/") do index -= 1 end if index == 0 return "/" return path[0..index] end fn cmd_cd(context) do const arg = if context.argv:len() > 1 context.argv[1] else "/" const target = if arg == ".." parent_of(wd.path) else abspath(arg) if target == "/proc" or target == "/bin" or fs:is_dir(target) do wd.path = target end else do puts("cd: #{arg}: not a directory\n") end end fn cmd_pwd(context) puts("#{wd.path}\n") fn wipe() console:clear() fn cmd_clear(context) wipe() fn zoom_by(delta) do let scale = console.scale + delta if scale < 1 do scale = 1 end if scale > 6 do scale = 6 end console.scale = scale wipe() puts("zoom #{scale}x\n") end fn cmd_zoom(context) do const direction = if context.argv:len() > 1 context.argv[1] else "" if direction == "in" do zoom_by(1) end else if direction == "out" do zoom_by(-1) end else do puts("usage: zoom in | zoom out\n") end end fn bcd(byte) (byte shr 4) * 10 + (byte band 15) fn rtc(cmos_reg) do outb(0x70, cmos_reg) inb(0x71) end fn cmd_time(context) puts("utc #{bcd(rtc(4))}:#{bcd(rtc(2))}:#{bcd(rtc(0))}\n") # 0x prefixed 16 digit hex fn hex(value) do const digits = "0123456789abcdef" let result = "" for index in 0..16 do result = result ~ digits[(value shr ((15 - index) * 4)) band 15] end "0x#{result}" end fn mem_type_name(kind) do if kind == 0 return "usable" if kind == 1 return "reserved" if kind == 2 return "acpi-reclaimable" if kind == 3 return "acpi-nvs" if kind == 4 return "bad" if kind == 5 return "bootloader-reclaimable" if kind == 6 return "kernel+modules" if kind == 7 return "framebuffer" "unknown" end # the boot memory map dump fn dump_memmap() do puts("memory map:\n") for index in 0..memmap_count() do puts(" #{hex(memmap_base(index))} len=#{hex(memmap_len(index))} type=#{mem_type_name(memmap_kind(index))}\n") end puts("usable: #{(mem_total() shr 20)} MiB, free: #{(mem_free() shr 20)} MiB\n") end fn cmd_mem(context) puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB usable\n") fn cmd_uptime(context) puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n") fn cmd_ps(context) puts("#{proc_count()} process(es) running\n") fn cmd_echo(context) do if context.line:len() > 5 puts("#{context.line[5..]}\n") else puts("\n") end fn cmd_mkfs(context) fs:format() fn cmd_run(context) do if context.argv:len() <= 1 do list_progs() return :nil end const parsed = arg_name(context.rest, 0) if run_prog(parsed.name, context.rest[parsed.rest..]) == :true return :hold puts("no such program: #{parsed.name}\n") return :nil end fn run(line) do # the fact that the shell needs to interface directly with ed isnt, very nice if gils_ed_active() do gils_ed_feed(line) return :nil end if line:trim():len() == 0 do puts("% ") return :nil end const argv = line:split(" ") const context = make_ctx(line, argv) const command = lookup(argv[0]) if command != :nil do # :hold means the command took over (modal editor / launched a program) so # we dont draw a fresh prompt, otherwise we do const result = command.handler(context) if result != :hold do puts("% ") end return :nil end if run_script(argv[0], context) == :true do puts("% ") return :nil end if run_prog(argv[0], context.rest) == :true return :nil puts("#{argv[0]}: command not found\n% ") end register("help", "shell", "show this", cmd_help) register("clear", "shell", "wipe the screen", cmd_clear) register("time", "shell", "utc clock", cmd_time) register("mem", "shell", "free / total ram", cmd_mem) register("uptime", "shell", "seconds since boot", cmd_uptime) register("ps", "shell", "running processes", cmd_ps) register("zoom in|out", "shell", "scale the font", cmd_zoom) register("echo ", "shell", "print it back", cmd_echo) register("mkfs", "shell", "format the disk", cmd_mkfs) register("cd ", "gils", "change directory", cmd_cd) register("pwd", "gils", "print working dir", cmd_pwd) register("run [name] [arg]", "shell", "run a program explicitly", cmd_run)