hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--boot/heap.zig2
-rw-r--r--build.zig12
-rw-r--r--build.zig.zon2
-rw-r--r--gils/ed.rv357
-rw-r--r--kernel/fs.rv517
-rw-r--r--kernel/input.rv8
-rw-r--r--kernel/main.rv4
-rw-r--r--kernel/seed.rv13
-rw-r--r--kernel/shell.rv482
-rw-r--r--kernel/tty.rv3
11 files changed, 1115 insertions, 303 deletions
diff --git a/README.md b/README.md
index 1c859ff..ce90856 100644
--- a/README.md
+++ b/README.md
@@ -12,5 +12,21 @@ god so needy
$ make run
```
-## License
+# handbook
+once you boot in, you wanna see `help`
+
+your motd lies in `/etc/motd` and the profile script in `/etc/profile`
+
+## working with files
+`cat`, `ls`, `rm`, `mkdir`, whatever, work as you'd expect them to
+
+`ed` is your only text editor\
+it's cooler than gnu ed. type `h` to see help\
+you also get some revo-specific commands, for free!\
+- `x` to execute and show the result
+- `!` to execute
+- `v` to check whether a file is valid
+- `o` to get an outline
+
+# License
MIT, see [LICENSE](LICENSE).
diff --git a/boot/heap.zig b/boot/heap.zig
index b232585..ff8bfad 100644
--- a/boot/heap.zig
+++ b/boot/heap.zig
@@ -1,4 +1,4 @@
-// freelist allocator type shit
+//! freelist allocator type shit
const std = @import("std");
const pmm = @import("pmm.zig");
diff --git a/build.zig b/build.zig
index c8797d6..b8683c5 100644
--- a/build.zig
+++ b/build.zig
@@ -21,9 +21,9 @@ pub fn build(b: *std.Build) void {
};
const Feature = std.Target.x86.Feature;
- query.cpu_features_sub.addFeature(@intFromEnum(Feature.mmx));
- query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx));
- query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx2));
+ query.cpu_features_sub.addFeature(@backingInt(Feature.mmx));
+ query.cpu_features_sub.addFeature(@backingInt(Feature.avx));
+ query.cpu_features_sub.addFeature(@backingInt(Feature.avx2));
const target = b.resolveTargetQuery(query);
@@ -44,7 +44,11 @@ pub fn build(b: *std.Build) void {
kernel.setLinkerScript(b.path("boot/linker.ld"));
// the big guns
- const revo_dep = b.dependency("revo", .{ .target = target, .optimize = optimize });
+ const revo_dep = b.dependency("revo", .{
+ .target = target,
+ .optimize = optimize,
+ .features = "regex",
+ });
const revo_mod = revo_dep.module("revo");
var visited: std.AutoHashMap(*std.Build.Module, void) = .init(b.allocator);
diff --git a/build.zig.zon b/build.zig.zon
index 114983a..e0dd3e9 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -2,7 +2,7 @@
.name = .revel,
.version = "0.1.0",
.fingerprint = 0x453ae5f0bb5a7c9f,
- .minimum_zig_version = "0.16.0",
+ .minimum_zig_version = "0.17.0",
.dependencies = .{
.revo = .{ .path = "revo" },
},
diff --git a/gils/ed.rv b/gils/ed.rv
index 7f1d849..023ce15 100644
--- a/gils/ed.rv
+++ b/gils/ed.rv
@@ -1,89 +1,157 @@
# simple ass modal ass line ass editor
-fn list_insert(items, index, value) do
+#* printed by the h command *#
+const ed_usage = """
+ ed, the only good and standard text editor on earth
+
+ addresses: . current | $ last | , or % all | n nth line
+ p print | n numbered | a append | i insert | c change | d delete
+ w [file] write | q quit | = number | h help | o outline | empty prints next
+ ! [file] run buffer or file | low,high! run range | v check | x eval and show
+ a/i/c take lines until a lone .
+ """
+
+fn list_insert(items, index, value) -> table do
let result = {}
- for position in 0..index do result:push(items[position]) end
+
+ for position in 0..index do
+ result:push(items[position])
+ end
+
result:push(value)
- for position in index..items:len() do result:push(items[position]) end
+
+ for position in index..items:len() do
+ result:push(items[position])
+ end
+
return result
end
-fn list_delete(items, low, high) do
+fn list_delete(items, low, high) -> table do
let result = {}
- for position in 0..items:len() do if (position < low or position > high) result:push(items[position]) end
+
+ for position in 0..items:len() do
+ if (position < low or position > high) result:push(items[position])
+ end
+
return result
end
-fn ed_is_digit(character) do
+fn ed_is_digit(character) -> bool do
const code = character:ascii()
+
return code >= 48 and code <= 57
end
-# parse one address at index
-fn ed_addr(state, line, index) do
- if index >= line:len() return { value = -1, next = index }
- if line[index] == "." return { value = state.dot, next = index + 1 }
- if line[index] == "$" return { value = state.lines:len() - 1, next = index + 1 }
+#* parse one address at index *#
+fn ed_addr(state, line, index) -> { value: num, next: num } do
+ if index >= line:len() do
+ return { value = -1, next = index }
+ end
+
+ if line[index] == "." do
+ return { value = state.dot, next = index + 1 }
+ end
+
+ if line[index] == "$" do
+ return { value = state.lines:len() - 1, next = index + 1 }
+ end
+
if ed_is_digit(line[index]) do
let number = 0
let scan = index
- while (scan < line:len() and ed_is_digit(line[scan])) do number = number * 10 + (line[scan]:ascii() - 48) scan += 1 end
+
+ while (scan < line:len() and ed_is_digit(line[scan])) do
+ number = number * 10 + (line[scan]:ascii() - 48)
+ scan += 1
+ end
+
return { value = number - 1, next = scan }
end
+
return { value = -1, next = index }
end
-# split a command line into low, high and command
-fn ed_parse(state, line) do
+#* split a command line into low, high and command *#
+fn ed_parse(state, line) -> { low: num, high: num, command: string } do
let index = 0
let low = -1
let high = -1
- if (line:len() > 0 and (line[0] == "," or line[0] == "%")) do
+
+ if line:len() > 0 and (line[0] == "," or line[0] == "%") do
low = 0
high = state.lines:len() - 1
index = 1
- end
- else do
+ end else do
const first_addr = ed_addr(state, line, 0)
- if first_addr.value >= 0 do low = first_addr.value index = first_addr.next end
- if (index < line:len() and line[index] == ",") do
+
+ if first_addr.value >= 0 do
+ low = first_addr.value
+ index = first_addr.next
+ end
+
+ if index < line:len() and line[index] == "," do
index += 1
+
const second_addr = ed_addr(state, line, index)
- if second_addr.value >= 0 do high = second_addr.value index = second_addr.next end
- else do high = state.lines:len() - 1 end
+
+ if second_addr.value >= 0 do
+ high = second_addr.value
+ index = second_addr.next
+ end else do
+ high = state.lines:len() - 1
+ end
end
end
- if low < 0 do low = state.dot end
- if high < 0 do high = low end
- const command = if index < line:len() line[index..] else ""
+
+ if (low < 0) low = state.dot
+ if (high < 0) high = low
+
+ const command = if (index < line:len()) line[index..] else ""
+
return { low = low, high = high, command = command }
end
fn ed_print(state, low, high, numbered) do
let index = low
- while index <= high do
- if (index >= 0 and index < state.lines:len()) do
- if numbered do puts("#{index + 1}\t#{state.lines[index]}\n") end
- else do puts("#{state.lines[index]}\n") end
+
+ while (index <= high) do
+ if index >= 0 and index < state.lines:len() do
+ if numbered do
+ # todo make the a \t once it renders right
+ puts("#{index + 1} #{state.lines[index]}\n")
+ end else do
+ puts("#{state.lines[index]}\n")
+ end
end
+
index += 1
end
- if (high >= 0 and high < state.lines:len()) do state.dot = high end
+
+ if (high >= 0 and high < state.lines:len()) state.dot = high
end
fn ed_goto(state, low) do
- if state.lines:len() == 0 do puts("?\n") return :nil end
+ if state.lines:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
let target = low
- if target < 0 do target = 0 end
- if target >= state.lines:len() do target = state.lines:len() - 1 end
+
+ if (target < 0) target = 0
+ if (target >= state.lines:len()) target = state.lines:len() - 1
+
state.dot = target
puts("#{state.lines[target]}\n")
end
fn ed_begin_input(state, position) do
let clamped = position
- if clamped < 0 do clamped = 0 end
- if clamped > state.lines:len() do clamped = state.lines:len() end
+
+ if (clamped < 0) clamped = 0
+ if (clamped > state.lines:len()) clamped = state.lines:len()
+
state.input = :true
state.insert_at = clamped
end
@@ -91,9 +159,12 @@ end
fn ed_delete(state, low, high) do
state.lines = list_delete(state.lines, low, high)
state.dirty = :true
+
let new_dot = low
- if new_dot >= state.lines:len() do new_dot = state.lines:len() - 1 end
- if new_dot < 0 do new_dot = 0 end
+
+ if (new_dot >= state.lines:len()) new_dot = state.lines:len() - 1
+ if (new_dot < 0) new_dot = 0
+
state.dot = new_dot
end
@@ -102,20 +173,44 @@ fn ed_change(state, low, high) do
ed_begin_input(state, low)
end
-fn ed_join(items) do
+fn ed_join(items) -> string do
let result = ""
- for index in 0..items:len() do result = result ~ items[index] ~ "\n" end
+
+ for index in 0..items:len() do
+ result = result ~ items[index] ~ "\n"
+ end
+
return result
end
+fn ed_range_text(state, low, high) -> string do
+ let range = {}
+
+ for index in low..(high + 1) do
+ if index >= 0 and index < state.lines:len() do
+ range:push(state.lines[index])
+ end
+ end
+
+ return ed_join(range)
+end
+
fn ed_write(state, command) do
let path = state.path
+
if command:len() > 1 do
const filename = command[1..]:trim()
- if filename:len() > 0 do path = expand_path(filename) end
+
+ if (filename:len() > 0) path = expand_path(filename)
+ end
+
+ if path:len() == 0 do
+ puts("?\n")
+ return :nil
end
- if path:len() == 0 do puts("?\n") return :nil end
+
const contents = ed_join(state.lines)
+
fs:store(path, contents)
state.path = path
state.dirty = :false
@@ -127,64 +222,200 @@ fn ed_quit(state) do
puts(prompt())
end
+fn ed_run_file(state, line, parsed) do
+ if line:len() > 0 and line[0] != "!" do
+ const text = ed_range_text(state, parsed.low, parsed.high)
+
+ if text:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ env.ctx = make_ctx("", {""})
+
+ const error_msg = eval_script(text)
+
+ if error_msg != :nil do
+ puts("!: #{error_msg}\n")
+ end
+
+ return :nil
+ end
+
+ let rest = ""
+
+ if parsed.command:len() > 1 do
+ rest = parsed.command[1..]:trim()
+ end
+
+ if rest:len() == 0 do
+ if state.lines:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ env.ctx = make_ctx("", {""})
+
+ const error_msg = eval_script(ed_join(state.lines))
+
+ if error_msg != :nil do
+ puts("!: #{error_msg}\n")
+ end
+
+ return :nil
+ end
+
+ const path = expand_path(rest:split(" ")[0])
+
+ if fs:size(path) < 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ run_script_path(path, path, make_ctx(rest, rest:split(" ")))
+end
+
+#* check the buffer (or range) without running it *#
+fn ed_check(state, line, parsed) do
+ let text = ed_join(state.lines)
+
+ if line:len() > 0 and line[0] != "v" do
+ text = ed_range_text(state, parsed.low, parsed.high)
+ end
+
+ if text:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ match revo.compile(text)
+ | {:ok, _} => puts("ok\n")
+ | {:err, e} => puts("#{e}\n")
+end
+
+#* eval and show the value, like the repl does *#
+fn ed_eval_show(state, line, parsed) do
+ let text = ed_join(state.lines)
+
+ if line:len() > 0 and line[0] != "x" do
+ text = ed_range_text(state, parsed.low, parsed.high)
+ end
+
+ if text:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ env.ctx = make_ctx("", {""})
+
+ match revo.eval(text)
+ | {:ok, v} => puts("#{v}\n")
+ | {:err, e} => puts("#{e}\n")
+end
+
+#* list fn and global definitions with line numbers *#
+fn ed_outline(state) do
+ for index in 0..state.lines:len() do
+ const text = state.lines[index]:trim()
+
+ if text:starts_with?("fn ") or text:starts_with?("global ") do
+ puts("#{index + 1}\t#{text}\n")
+ end
+ end
+end
+
global ed_state = { active = :false, input = :false, path = "", lines = {}, dot = 0, insert_at = 0, dirty = :false }
-fn gils_ed(context) do
+#* open the editor, takes over the shell until quit *#
+fn gils_ed(context) -> :hold do
ed_state.active = :true
ed_state.input = :false
ed_state.dirty = :false
ed_state.lines = {}
- ed_state.path = if context.argv:len() > 1 expand_path(context.argv[1]) else ""
+ ed_state.path = if (context.argv:len() > 1)
+ expand_path(context.argv[1])
+ else ""
+
if ed_state.path:len() > 0 do
const size = fs:size(ed_state.path)
+
if size > 0 do
const contents = fs:read_bytes(ed_state.path, 0, size)
const raw_lines = contents:split("\n")
+
for index in 0..raw_lines:len() do
- if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) do ed_state.lines:push(raw_lines[index]) end
+ if index < raw_lines:len() - 1 or raw_lines[index]:len() > 0 do
+ ed_state.lines:push(raw_lines[index])
+ end
end
+
puts("#{size}\n")
+ end else do
+ puts("#{ed_state.path}: new file\n")
end
- else do puts("#{ed_state.path}: new file\n") end
end
- ed_state.dot = if ed_state.lines:len() > 0 ed_state.lines:len() - 1 else 0
+
+ ed_state.dot =
+ if (ed_state.lines:len() > 0)
+ ed_state.lines:len() - 1
+ else 0
+
return :hold
end
fn gils_ed_feed(line) do
const state = ed_state
+
if state.input do
- if line == "." do state.input = :false end
- else do
+ if line == "." do
+ state.input = :false
+ end else do
state.lines = list_insert(state.lines, state.insert_at, line)
state.insert_at += 1
state.dot = state.insert_at - 1
state.dirty = :true
end
+
return :nil
end
+
if line:len() == 0 do
- if state.lines:len() == 0 do puts("?\n") return :nil end
- if state.dot < state.lines:len() - 1 do state.dot += 1 end
+ if state.lines:len() == 0 do
+ puts("?\n")
+ return :nil
+ end
+
+ if (state.dot < state.lines:len() - 1) state.dot += 1
+
puts("#{state.lines[state.dot]}\n")
+
return :nil
end
+
const parsed = ed_parse(state, line)
- const command_char = if parsed.command:len() > 0 parsed.command[0] else ""
+ const command_char = if (parsed.command:len() > 0) parsed.command[0] else ""
+
match command_char
- | "p" => ed_print(state, parsed.low, parsed.high, :false)
- | "n" => ed_print(state, parsed.low, parsed.high, :true)
- | "a" => ed_begin_input(state, parsed.low + 1)
- | "i" => ed_begin_input(state, parsed.low)
- | "c" => ed_change(state, parsed.low, parsed.high)
- | "d" => ed_delete(state, parsed.low, parsed.high)
- | "w" => ed_write(state, parsed.command)
- | "q" => ed_quit(state)
- | "=" => puts("#{state.dot + 1}\n")
- | "" => ed_goto(state, parsed.low)
- | _ => puts("?\n")
+ | "p" => ed_print(state, parsed.low, parsed.high, numbered = :false)
+ | "n" => ed_print(state, parsed.low, parsed.high, numbered = :true)
+ | "a" => ed_begin_input(state, parsed.low + 1)
+ | "i" => ed_begin_input(state, parsed.low)
+ | "c" => ed_change(state, parsed.low, parsed.high)
+ | "d" => ed_delete(state, parsed.low, parsed.high)
+ | "w" => ed_write(state, parsed.command)
+ | "!" => ed_run_file(state, line, parsed)
+ | "v" => ed_check(state, line, parsed)
+ | "x" => ed_eval_show(state, line, parsed)
+ | "q" => ed_quit(state)
+ | "h" => puts(ed_usage ~ "\n")
+ | "o" => ed_outline(state)
+ | "=" => puts("#{state.dot + 1}\n")
+ | "" => ed_goto(state, parsed.low)
+ | _ => puts("?\n")
end
-fn gils_ed_active() ed_state.active
+fn gils_ed_active() -> bool do
+ return ed_state.active
+end
-register("ed [path]", "gils", "the standard editor (a i d p w q)", gils_ed)
+register("ed [path]", "gils", "the editor (type h for help)", gils_ed)
diff --git a/kernel/fs.rv b/kernel/fs.rv
index 1e72dc4..f732573 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -19,220 +19,329 @@ const END = 0xffff # end of chain marker in the fat
const T_FILE = 1
const T_DIR = 2
-fn split_path(path) do
+#* into non empty parts *#
+fn split_path(path) -> table<string> do
const raw_parts = path:split("/")
let parts = {}
+
for index in 0..raw_parts:len() do
- if raw_parts[index]:len() > 0 parts:push(raw_parts[index])
+ if (raw_parts[index]:len() > 0) parts:push(raw_parts[index])
end
+
return parts
end
-# scratch is a spare sector for superblocks/dirs/data, fat is a separate scratch
-# so fat lookups dont trample a dir block were mid edit.
global fs = {
- scratch = 0, fat = 0, ready = :false, total = 0, root = 0,
+ # scratch is a spare sector for superblocks/dirs/data
+ scratch = 0,
+ # fat is a separate scratch
+ # so fat lookups dont trample a dir block were mid edit.
+ fat = 0,
+ ready = :false, total = 0, root = 0,
- fat_get = fn(self, block) do
+ fn fat_get(self, block) -> num do
disk_read(FAT_LBA + (block shr 8), self.fat)
- mem_read(self.fat + (block band 255) * 2, 2)
+
+ return mem_read(self.fat + (block band 255) * 2, 2)
end,
- fat_set = fn(self, block, value) do
+ fn fat_set(self, block, value) do
const sector = FAT_LBA + (block shr 8)
disk_read(sector, self.fat)
mem_write(self.fat + (block band 255) * 2, value, 2)
disk_write(sector, self.fat)
end,
- fat_alloc = fn(self) do
+ fn fat_alloc(self) -> num do
let block = DATA_LBA
- while block < self.total do
+
+ while (block < self.total) do
disk_read(FAT_LBA + (block shr 8), self.fat)
+
let offset = block band 255
+
while (offset < 256 and block < self.total) do
- if mem_read(self.fat + offset * 2, 2) == 0 return block
+ if mem_read(self.fat + offset * 2, 2) == 0 do
+ return block
+ end
+
offset += 1
block += 1
end
end
- 0
+
+ return 0
end,
- free_chain = fn(self, first) do
+ fn free_chain(self, first) do
let block = first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
self:fat_set(block, 0)
block = next_block
end
end,
- # entry is 32 bytes:
- # +0 type (0 free/1 file/2 dir)
- # +1 name len
- # +2 first block
- # +4 size
- # +8 name bytes
- ent_name = fn(self, entry_offset) do
+ #* entry is 32 bytes:
+ - +0 type (0 free/1 file/2 dir)
+ - +1 name len
+ - +2 first block
+ - +4 size
+ - +8 name bytes *#
+ fn ent_name(self, entry_offset) -> string do
const name_length = mem_read(self.scratch + entry_offset + 1, 1)
let result = ""
- for index in 0..name_length do result = result ~ string.of_ascii(mem_read(self.scratch + entry_offset + 8 + index, 1)) end
- result
+
+ for index in 0..name_length do
+ result = result ~ string.of_ascii(mem_read(self.scratch + entry_offset + 8 + index, 1))
+ end
+
+ return result
end,
- ent_write = fn(self, entry_offset, type, name, first, size) do
+ fn ent_write(self, entry_offset, type, name, first, size) do
mem_write(self.scratch + entry_offset, type, 1)
mem_write(self.scratch + entry_offset + 1, name:len(), 1)
mem_write(self.scratch + entry_offset + 2, first, 2)
mem_write(self.scratch + entry_offset + 4, size, 4)
- for index in 0..name:len() do mem_write(self.scratch + entry_offset + 8 + index, name[index]:ascii(), 1) end
+
+ for index in 0..name:len() do
+ mem_write(self.scratch + entry_offset + 8 + index, name[index]:ascii(), 1)
+ end
end,
- dir_lookup = fn(self, dir_first, name) do
+ fn dir_lookup(self, dir_first, name) -> { type: num, first: num, size: num } | :nil do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) != 0 and self:ent_name(entry_offset) == name do
return { type = mem_read(self.scratch + entry_offset, 1), first = mem_read(self.scratch + entry_offset + 2, 2), size = mem_read(self.scratch + entry_offset + 4, 4) }
end
end
+
block = next_block
end
+
return :nil
end,
- # add or overwrite `name` in the dir
- # grows a block if every slot is taken
- dir_set = fn(self, dir_first, name, type, first, size) do
+ #* add or overwrite `name` in the dir
+ grows a block if every slot is taken *#
+ fn dir_set(self, dir_first, name, type, first, size) -> bool do
let block = dir_first
let found_block = 0
let found_offset = 0
let free_block = 0
let free_offset = -1
let last_block = dir_first
+
while (block != 0 and block != END) do
last_block = block
+
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) == 0 do
- if free_offset < 0 do free_block = block free_offset = entry_offset end
- end
- else do
- if self:ent_name(entry_offset) == name do found_block = block found_offset = entry_offset end
+ if free_offset < 0 do
+ free_block = block
+ free_offset = entry_offset
+ end
+ end else do
+ if self:ent_name(entry_offset) == name do
+ found_block = block
+ found_offset = entry_offset
+ end
end
end
+
block = next_block
end
let target_block = 0
let target_offset = 0
- if found_block != 0 do target_block = found_block target_offset = found_offset end
- else if free_offset >= 0 do target_block = free_block target_offset = free_offset end
- else do
- const new_block = self:fat_alloc()
- if new_block == 0 return :false
- self:fat_set(new_block, END)
- self:fat_set(last_block, new_block)
- mem_zero(self.scratch, BLOCK)
- disk_write(new_block, self.scratch)
- target_block = new_block
- target_offset = 0
- end
+
+ match
+ | found_block != 0 => do
+ target_block = found_block
+ target_offset = found_offset
+ end
+ | free_offset >= 0 => do
+ target_block = free_block
+ target_offset = free_offset
+ end
+ | _ => do
+ const new_block = self:fat_alloc()
+
+ if new_block == 0 do
+ return :false
+ end
+
+ self:fat_set(new_block, END)
+ self:fat_set(last_block, new_block)
+ mem_zero(self.scratch, BLOCK)
+ disk_write(new_block, self.scratch)
+ target_block = new_block
+ target_offset = 0
+ end
disk_read(target_block, self.scratch)
self:ent_write(target_offset, type, name, first, size)
disk_write(target_block, self.scratch)
+
return :true
end,
- dir_clear = fn(self, dir_first, name) do
+ fn dir_clear(self, dir_first, name) -> { type: num, first: num } | :nil do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) != 0 and self:ent_name(entry_offset) == name do
const info = { type = mem_read(self.scratch + entry_offset, 1), first = mem_read(self.scratch + entry_offset + 2, 2) }
+
mem_write(self.scratch + entry_offset, 0, 1)
disk_write(block, self.scratch)
+
return info
end
end
+
block = next_block
end
+
return :nil
end,
- dir_empty = fn(self, dir_first) do
+ fn dir_empty(self, dir_first) -> bool do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
- if mem_read(self.scratch + slot * 32, 1) != 0 return :false
+ if mem_read(self.scratch + slot * 32, 1) != 0 do
+ return :false
+ end
end
+
block = next_block
end
+
return :true
end,
- resolve = fn(self, path) do
+ # resolve a path to its entry
+ fn resolve(self, path) -> { type: num, first: num, size: num } | :nil do
const parts = split_path(path)
let current = self.root
let entry = { type = T_DIR, first = self.root, size = 0 }
+
for index in 0..parts:len() do
- if entry.type != T_DIR return :nil
+ if entry.type != T_DIR do
+ return :nil
+ end
+
const found = self:dir_lookup(current, parts[index])
- if found == :nil return :nil
+
+ if found == :nil do
+ return :nil
+ end
+
entry = found
current = found.first
end
+
return entry
end,
- resolve_parent = fn(self, path) do
+ # resolve a path to its parent dir and name
+ fn resolve_parent(self, path) -> { dir: num, name: string } | :nil do
const parts = split_path(path)
- if parts:len() == 0 return :nil
+
+ if parts:len() == 0 do
+ return :nil
+ end
+
let parent_dir = self.root
+
for index in 0..(parts:len() - 1) do
const found = self:dir_lookup(parent_dir, parts[index])
- if found == :nil or found.type != T_DIR return :nil
+
+ if found == :nil or found.type != T_DIR do
+ return :nil
+ end
+
parent_dir = found.first
end
+
return { dir = parent_dir, name = parts[parts:len() - 1] }
end,
- mount = fn(self) do
+ fn mount(self) do
self.scratch = frame_alloc()
self.fat = frame_alloc()
self.total = disk_sectors()
- if self.total == 0 do self.ready = :false return :nil end
+
+ if self.total == 0 do
+ self.ready = :false
+ return :nil
+ end
+
disk_read(SB_LBA, self.scratch)
+
if mem_read(self.scratch, 4) == MAGIC do
self.root = mem_read(self.scratch + 8, 2)
self.ready = :true
+ end else do
+ self.ready = :false
end
- else do self.ready = :false end
end,
- ensure = fn(self) do
- if self.ready return :true
+ fn ensure(self) -> bool do
+ if self.ready do
+ return :true
+ end
+
puts("no filesystem, run mkfs first\n")
- :false
+
+ return :false
end,
- format = fn(self) do
- if disk_sectors() == 0 do puts("mkfs: no disk\n") return :nil end
+ fn format(self) do
+ if disk_sectors() == 0 do
+ puts("mkfs: no disk\n")
+ return :nil
+ end
+
mem_zero(self.scratch, BLOCK)
- for sector in 0..FAT_SECTORS do disk_write(FAT_LBA + sector, self.scratch) end
+
+ for sector in 0..FAT_SECTORS do
+ disk_write(FAT_LBA + sector, self.scratch)
+ end
+
const root = self:fat_alloc()
+
self:fat_set(root, END)
mem_zero(self.scratch, BLOCK)
disk_write(root, self.scratch)
@@ -243,31 +352,70 @@ global fs = {
mem_write(self.scratch + 8, root, 2)
disk_write(SB_LBA, self.scratch)
self.ready = :true
+
puts("formatted #{self.total} sectors, #{(self.total shr 1)} KiB\n")
end,
- mkdir = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn mkdir(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do fail("mkdir: #{path}: bad path\n") return :nil end
- if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return :nil end
- if self:dir_lookup(parent.dir, parent.name) != :nil do fail("mkdir: #{parent.name}: already exists\n") return :nil end
+
+ if parent == :nil do
+ fail("mkdir: #{path}: bad path\n")
+ return :nil
+ end
+
+ if parent.name:len() > NAME_MAX do
+ fail("name too long (max #{NAME_MAX})\n")
+ return :nil
+ end
+
+ if self:dir_lookup(parent.dir, parent.name) != :nil do
+ fail("mkdir: #{parent.name}: already exists\n")
+ return :nil
+ end
+
const new_block = self:fat_alloc()
- if new_block == 0 do fail("disk full\n") return :nil end
+
+ if new_block == 0 do
+ fail("disk full\n")
+ return :nil
+ end
+
self:fat_set(new_block, END)
mem_zero(self.scratch, BLOCK)
disk_write(new_block, self.scratch)
self:dir_set(parent.dir, parent.name, T_DIR, new_block, 0)
end,
- store = fn(self, path, text) do
- if self:ensure() == :false return 0
+ fn store(self, path, text) -> num do
+ if self:ensure() == :false do
+ return 0
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do puts("#{path}: bad path\n") return 0 end
- if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end
+
+ if parent == :nil do
+ puts("#{path}: bad path\n")
+ return 0
+ end
+
+ if parent.name:len() > NAME_MAX do
+ puts("name too long (max #{NAME_MAX})\n")
+ return 0
+ end
+
const existing = self:dir_lookup(parent.dir, parent.name)
+
if existing != :nil do
- if existing.type == T_DIR do fail("#{parent.name}: is a directory\n") return 0 end
+ if existing.type == T_DIR do
+ fail("#{parent.name}: is a directory\n")
+ return 0
+ end
+
self:free_chain(existing.first)
end
@@ -275,185 +423,318 @@ global fs = {
let first = 0
let previous = 0
let written = 0
- while written < size do
+
+ while (written < size) do
const block = self:fat_alloc()
- if block == 0 do fail("disk full\n") return 0 end
+
+ if block == 0 do
+ fail("disk full\n")
+ return 0
+ end
+
self:fat_set(block, END)
- if first == 0 do first = block end
- else do self:fat_set(previous, block) end
+
+ if first == 0 do
+ first = block
+ end else do
+ self:fat_set(previous, block)
+ end
+
mem_zero(self.scratch, BLOCK)
# blit a whole block of the string at once, per byte was O(n^2) and choked
# the second files got into the kilobytes
let chunk = size - written
- if chunk > BLOCK do chunk = BLOCK end
+
+ if (chunk > BLOCK) chunk = BLOCK
+
blit_str(self.scratch, text[written..(written + chunk)])
disk_write(block, self.scratch)
written += chunk
previous = block
end
+
self:dir_set(parent.dir, parent.name, T_FILE, first, size)
+
return 1
end,
- write = fn(self, path, text) do
- if self:store(path, text) == 1 puts("wrote #{path} (#{text:len()} bytes)\n")
+ fn write(self, path, text) do
+ if (self:store(path, text) == 1) puts("wrote #{path} (#{text:len()} bytes)\n")
end,
- size = fn(self, path) do
+ fn size(self, path) -> num do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return -1
+
+ if entry == :nil or entry.type == T_DIR do
+ return -1
+ end
+
return entry.size
end,
- read_bytes = fn(self, path, offset, length) do
+ fn read_bytes(self, path, offset, length) -> string do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return ""
+
+ if entry == :nil or entry.type == T_DIR do
+ return ""
+ end
+
let block = entry.first
let stop = offset + length
- if stop > entry.size do stop = entry.size end
+
+ if (stop > entry.size) stop = entry.size
+
let position = 0
let result = ""
+
while (block != 0 and block != END and position < stop) do
const next_block = self:fat_get(block)
- if (position + BLOCK > offset and position < stop) do
+
+ if position + BLOCK > offset and position < stop do
disk_read(block, self.scratch)
# pull this blocks slice in one go, per byte concat was O(n^2)
let from = 0
- if offset > position do from = offset - position end
+
+ if (offset > position) from = offset - position
+
let to = BLOCK
- if stop - position < BLOCK do to = stop - position end
+
+ if (stop - position < BLOCK) to = stop - position
+
result = result ~ phys_to_str(self.scratch + from, to - from)
end
+
position += BLOCK
block = next_block
end
+
return result
end,
- read_into = fn(self, path, dest) do
+ fn read_into(self, path, dest) -> num do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return 0
+
+ if entry == :nil or entry.type == T_DIR do
+ return 0
+ end
+
let block = entry.first
let offset = 0
let remaining = entry.size
+
while (block != 0 and block != END and remaining > 0) do
const next_block = self:fat_get(block)
+
disk_read(block, dest + offset)
offset += BLOCK
remaining -= BLOCK
block = next_block
end
+
return entry.size
end,
# TODO: ts needs a rewrite its not cheap and rewrites every file from scratch
- write_at = fn(self, path, offset, bytes) do
+ fn write_at(self, path, offset, bytes) -> num do
const size = self:size(path)
let base = ""
+
if size > 0 do base = self:read_bytes(path, 0, size) end
+
let prefix = ""
- if offset <= base:len() do prefix = base[0..offset] end
- else do
+
+ if offset <= base:len() do
+ prefix = base[0..offset]
+ end else do
prefix = base
- while prefix:len() < offset do prefix = prefix ~ string.of_ascii(0) end
+ while (prefix:len() < offset) do prefix = prefix ~ string.of_ascii(0) end
end
+
const after = offset + bytes:len()
let tail = ""
+
if after < base:len() do tail = base[after..] end
+
return self:store(path, prefix ~ bytes ~ tail)
end,
- cat = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn cat(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const entry = self:resolve(path)
- if entry == :nil do fail("cat: #{path}: no such file\n") return :nil end
- if entry.type == T_DIR do fail("cat: #{path}: is a directory\n") return :nil end
+
+ if entry == :nil do
+ fail("cat: #{path}: no such file\n")
+ return :nil
+ end
+
+ if entry.type == T_DIR do
+ fail("cat: #{path}: is a directory\n")
+ return :nil
+ end
+
let block = entry.first
let remaining = entry.size
let result = ""
+
while (block != 0 and block != END and remaining > 0) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
- const chunk = if remaining > BLOCK BLOCK else remaining
- for index in 0..chunk do result = result ~ string.of_ascii(mem_read(self.scratch + index, 1)) end
+
+ const chunk = if (remaining > BLOCK) BLOCK else remaining
+
+ for index in 0..chunk do
+ result = result ~ string.of_ascii(mem_read(self.scratch + index, 1))
+ end
+
remaining -= chunk
block = next_block
end
+
puts(result)
puts("\n")
end,
- ls = fn(self, path) do
- if self:ensure() == :false return -1
+ fn ls(self, path) -> num do
+ if self:ensure() == :false do
+ return -1
+ end
+
const entry = self:resolve(path)
- if entry == :nil do fail("ls: #{path}: no such file\n") return -1 end
- if entry.type != T_DIR do puts(" #{path} #{entry.size} bytes\n") return -1 end
+
+ if entry == :nil do
+ fail("ls: #{path}: no such file\n")
+ return -1
+ end
+
+ if entry.type != T_DIR do
+ puts(" #{path} #{entry.size} bytes\n")
+ return -1
+ end
+
let count = 0
let block = entry.first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
const entry_type = mem_read(self.scratch + entry_offset, 1)
+
if entry_type != 0 do
const name = self:ent_name(entry_offset)
- if entry_type == T_DIR do puts(" #{name}/\n") end
- else do puts(" #{name} #{mem_read(self.scratch + entry_offset + 4, 4)} bytes\n") end
+
+ if entry_type == T_DIR do
+ puts(" #{name}/\n")
+ end else do
+ puts(" #{name} #{mem_read(self.scratch + entry_offset + 4, 4)} bytes\n")
+ end
+
count += 1
end
end
+
block = next_block
end
+
return count
end,
- rm = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn rm(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do fail("rm: #{path}: no such file\n") return :nil end
+
+ if parent == :nil do
+ fail("rm: #{path}: no such file\n")
+ return :nil
+ end
+
const entry = self:dir_lookup(parent.dir, parent.name)
- if entry == :nil do fail("rm: #{path}: no such file\n") return :nil end
+
+ if entry == :nil do
+ fail("rm: #{path}: no such file\n")
+ return :nil
+ end
+
if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
fail("rm: #{parent.name}: directory not empty\n")
return :nil
end
+
self:free_chain(entry.first)
self:dir_clear(parent.dir, parent.name)
puts("removed #{parent.name}\n")
end,
- is_dir = fn(self, path) do
+ fn is_dir(self, path) -> bool do
const entry = self:resolve(path)
+
return entry != :nil and entry.type == T_DIR
end,
- entries = fn(self, path) do
+ fn entries(self, path) -> table | :nil do
const entry = self:resolve(path)
- if entry == :nil or entry.type != T_DIR return :nil
+
+ if entry == :nil or entry.type != T_DIR do
+ return :nil
+ end
+
let result = {}
let block = entry.first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
const entry_type = mem_read(self.scratch + entry_offset, 1)
- if entry_type != 0 result:push({ name = self:ent_name(entry_offset), type = entry_type, size = mem_read(self.scratch + entry_offset + 4, 4) })
+
+ if entry_type != 0 do
+ result:push({
+ name = self:ent_name(entry_offset),
+ type = entry_type,
+ size = mem_read(self.scratch + entry_offset + 4, 4),
+ })
+ end
end
+
block = next_block
end
+
return result
end,
- unlink = fn(self, path) do
+ fn unlink(self, path) -> bool do
const parent = self:resolve_parent(path)
- if parent == :nil return :false
+
+ if parent == :nil do
+ return :false
+ end
+
const entry = self:dir_lookup(parent.dir, parent.name)
- if entry == :nil return :false
- if (entry.type == T_DIR and self:dir_empty(entry.first) == :false) return :false
+
+ if entry == :nil do
+ return :false
+ end
+
+ if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
+ return :false
+ end
+
self:free_chain(entry.first)
self:dir_clear(parent.dir, parent.name)
+
return :true
end,
}
diff --git a/kernel/input.rv b/kernel/input.rv
index ed0f4f7..139edf0 100644
--- a/kernel/input.rv
+++ b/kernel/input.rv
@@ -34,7 +34,13 @@ fn decode_key(code) do
if code == 0x9d do shell.ctrl = :false return :nil end # ctrl up
if code >= 128 return :nil # other releases
if code == 28 return "\n" # enter
- if code == 14 return "\x08" # backspace
+ if code == 14 do
+ if shell.ctrl do
+ return "\x7f"
+ end
+
+ return "\x08"
+ end # backspace (ctrl: word erase)
if code < 2 return :nil
if code >= KEYMAP:len() return :nil
const character = if shell.shift KEYMAP_SHIFT[code] else KEYMAP[code]
diff --git a/kernel/main.rv b/kernel/main.rv
index 93de758..3547665 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -15,6 +15,10 @@ if (disk_sectors() != 0 and fs.ready == :false) do
seed_fs()
end
+if fs:size("/etc/profile") >= 0 do
+ run_script_path("/etc/profile", "profile", make_ctx("", { "" }))
+end
+
# lung will be happy
const default_face = load_ttf("/usr/share/terminess.ttf", 32)
if default_face != :nil do
diff --git a/kernel/seed.rv b/kernel/seed.rv
index f673f68..9dc2dc5 100644
--- a/kernel/seed.rv
+++ b/kernel/seed.rv
@@ -24,8 +24,17 @@ fn seed_fs() do
fs:mkdir("/home")
fs:store("/etc/motd", "welcome to revel")
- fs:store("/home/balls.txt", "its lowk insane how big my balls are")
- fs:store("/home/i have big balls.txt", "dude my balls are actually massive")
+ fs:store("/etc/profile", """
+ # /etc/profile runs once at shell startup, keep it quick
+
+ const motd_size = fs:size("/etc/motd")
+ if motd_size > 0 do
+ puts(fs:read_bytes("/etc/motd", 0, motd_size))
+ puts("\n")
+ end
+ """)
+ fs:store("/home/balls.txt", "its lowk insane how big my balls are lol jk\n\t- hachem")
+ fs:store("/home/i have big balls.txt", "dude my balls are actually massive lol jk\n\t- hachem")
seed_bin()
seed_fonts()
diff --git a/kernel/shell.rv b/kernel/shell.rv
index 2811e5a..99ead73 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -1,21 +1,31 @@
# i eventually want the shell to be a standalone program and shit
-fn pad(text, width) do
+fn pad(text, width) -> string do
let result = text
- while result:len() < width do result = result ~ " " end
+
+ while (result:len() < width)
+ result = result ~ " "
+
return result
end
fn help_row(label, description) do
let col = pad(label, 22)
- if label:len() >= 22 do col = label ~ " " end
+
+ if label:len() >= 22 do
+ col = label ~ " "
+ end
+
puts(" #{col}#{description}\n")
end
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
+ if commands[index].group == group_name do
+ help_row(commands[index].usage, commands[index].help)
+ end
end
end
@@ -44,12 +54,17 @@ fn cmd_help(context) do
puts("\n fonts on disk (font load /usr/share/x.psf)\n")
for index in 0..font_files:len() do help_row(font_files[index].name, "") end
end
+
puts("\n")
end
fn welcome() do
let fs_status = "no filesystem"
- if fs.ready do fs_status = "revelfs mounted" end
+
+ 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")
@@ -58,288 +73,533 @@ fn welcome() do
end
# global so /bin scripts can call it from a nested eval fiber
-global arg_name = fn(line, start) do
+global arg_name = fn(line, start) -> { name: string, rest: num } do
let quoted = :false
let begin = start
- if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end
+
+ 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
+ while (index < line:len() and line[index] != "\"")
+ index += 1
+ end else do
+ while (index < line:len() and line[index] != " ")
+ index += 1
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 }
+
+ if quoted and rest < line:len() do
+ rest += 1
+ end
+
+ if rest < line:len() and line[rest] == " " do
+ rest += 1
+ end
+
+ return { name = name, rest = rest }
end
-fn parent_of(path) do
- if path == "/" return "/"
+fn parent_of(path) -> string do
+ if path == "/" do
+ return "/"
+ end
+
let index = path:len() - 1
- while (index > 0 and path[index] != "/") do index -= 1 end
- if index == 0 return "/"
+
+ while (index > 0 and path[index] != "/")
+ index -= 1
+
+ if index == 0 do
+ return "/"
+ end
+
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 expand_path(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") shell_status.code = 1 end
+ const arg = if (context.argv:len() > 1) context.argv[1] else "/"
+
+ const target = if (arg == "..") parent_of(wd.path) else expand_path(arg)
+
+ if target == "/proc"
+ or target == "/bin"
+ or fs:is_dir(target) do
+ wd.path = target
+ end else do
+ fail("cd: #{arg}: not a directory\n")
+ end
end
-fn cmd_pwd(context) puts("#{wd.path}\n")
+fn cmd_pwd(context)
+ puts("#{wd.path}\n")
-fn wipe() console:clear()
-fn cmd_clear(context) wipe()
+fn wipe()
+ console:clear()
+
+fn cmd_clear(context)
+ wipe()
fn zoom_by(delta) do
const entry = console.font_entry
+
# ttf is vector, stepping the size interactively is fine
- if (entry != :nil and entry.kind == :ttf) do
+ if entry != :nil and entry.kind == :ttf do
let size = entry.size + delta * 4
- if size < 8 do size = 8 end
- if size > 72 do size = 72 end
- if size == entry.size do puts("zoom #{size}px\n") return :nil end
+
+ if (size < 8) size = 8
+ if (size > 72) size = 72
+
+ if size == entry.size do
+ puts("zoom #{size}px\n")
+ return :nil
+ end
+
const face = load_ttf(entry.path, size)
- if face == :nil do puts("zoom: rasterize failed\n") return :nil end
+
+ if face == :nil do
+ puts("zoom: rasterize failed\n")
+ return :nil
+ end
+
add_font(entry.name, face.glyphs, face.width, face.height, :ttf, entry.path, size)
console:set_font(find_font(entry.name))
puts("zoom #{size}px\n")
+
return :nil
end
+
# bitmap fonts js use integer pixel scale
let scale = console.scale + delta
- if scale < 1 do scale = 1 end
- if scale > 6 do scale = 6 end
+
+ if (scale < 1) scale = 1
+ if (scale > 6) scale = 6
+
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
+ const direction =
+ if (context.argv:len() > 1)
+ context.argv[1]
+ else ""
+
+ match direction
+ | "in" => zoom_by(1)
+ | "out" => zoom_by(-1)
+ | _ => puts("usage: zoom in | zoom out\n")
end
-fn font_basename(path) do
+fn font_basename(path) -> string do
let start = 0
let index = 0
- while index < path:len() do
- if path[index] == "/" do start = index + 1 end
+
+ while (index < path:len()) do
+ if path[index] == "/" do
+ start = index + 1
+ end
+
index += 1
end
+
let name = path[start..]
let dot = -1
- for i in 0..name:len() do if name[i] == "." do dot = i end end
- if dot > 0 do name = name[0..dot] end
+
+ for i in 0..name:len() do
+ if (name[i] == ".")
+ dot = i
+ end
+
+ if (dot > 0)
+ name = name[0..dot]
+
return name
end
-fn load_psf(path) do
+fn load_psf(path) -> { glyphs: table, width: num, height: num } | :nil do
const size = fs:size(path)
- if size < 4 return :nil
+
+ if size < 4 do
+ return :nil
+ end
+
const data = fs:read_bytes(path, 0, size)
- if (data[0]:ascii() != 0x36 or data[1]:ascii() != 0x04) do return :nil end
+
+ if data[0]:ascii() != 0x36 or data[1]:ascii() != 0x04 do
+ return :nil
+ end
+
const height = data[3]:ascii()
- if (height == 0 or size < 4 + 128 * height) do return :nil end
+
+ if height == 0 or size < 4 + 128 * height do
+ return :nil
+ end
+
let glyphs = {}
+
for code in 32..128 do
const base = 4 + code * height
let rows = {}
- for row in 0..height do rows:push(reverse8(data[base + row]:ascii())) end
+
+ for row in 0..height do
+ rows:push(reverse8(data[base + row]:ascii()))
+ end
+
glyphs:push(rows)
end
+
return { glyphs = glyphs, width = 8, height = height }
end
-fn has_slash(text) do
- for index in 0..text:len() do if text[index] == "/" return :true end
+fn has_slash(text) -> bool do
+ for index in 0..text:len() do
+ if (text[index] == "/")
+ return :true
+ end
+
return :false
end
-fn ends_with(text, suffix) do
- if text:len() < suffix:len() return :false
+fn ends_with(text, suffix) -> bool do
+ if text:len() < suffix:len() do
+ return :false
+ end
+
return text[(text:len() - suffix:len())..] == suffix
end
-fn str_to_int(text) do
+fn str_to_int(text) -> num do
let value = 0
+
for index in 0..text:len() do
const code = text[index]:ascii()
- if (code >= 48 and code <= 57) do value = value * 10 + (code - 48) end
+
+ if code >= 48 and code <= 57 do
+ value = value * 10 + (code - 48)
+ end
end
+
return value
end
-fn font_file(name) do
- if has_slash(name) do
- const p = expand_path(name)
- if (ends_with(p, ".ttf") and fs:size(p) >= 0) return { path = p, kind = :ttf }
- if (ends_with(p, ".psf") and fs:size(p) >= 0) return { path = p, kind = :psf }
- if fs:size(p ~ ".ttf") >= 0 return { path = p ~ ".ttf", kind = :ttf }
- if fs:size(p ~ ".psf") >= 0 return { path = p ~ ".psf", kind = :psf }
- return { path = p, kind = :none }
- end
- if fs:size("/usr/share/#{name}.psf") >= 0 return { path = "/usr/share/#{name}.psf", kind = :psf }
- if fs:size("/usr/share/#{name}.ttf") >= 0 return { path = "/usr/share/#{name}.ttf", kind = :ttf }
- return { path = name, kind = :none }
+#* resolve a font name to a path and a kind *#
+fn font_file(name) -> { path: string, kind: ( :ttf | :psf | :none ) } do
+ return match
+ | has_slash(name) => do
+ const p = expand_path(name)
+
+ if ends_with(p, ".ttf") and fs:size(p) >= 0 do
+ return { path = p, kind = :ttf }
+ end
+
+ if ends_with(p, ".psf") and fs:size(p) >= 0 do
+ return { path = p, kind = :psf }
+ end
+
+ if fs:size(p ~ ".ttf") >= 0 do
+ return { path = p ~ ".ttf", kind = :ttf }
+ end
+
+ if fs:size(p ~ ".psf") >= 0 do
+ return { path = p ~ ".psf", kind = :psf }
+ end
+
+ return { path = p, kind = :none }
+ end
+ | fs:size("/usr/share/#{name}.psf") >= 0 => { path = "/usr/share/#{name}.psf", kind = :psf }
+ | fs:size("/usr/share/#{name}.ttf") >= 0 => { path = "/usr/share/#{name}.ttf", kind = :ttf }
+ | _ => { path = name, kind = :none }
end
fn cmd_font(context) do
if context.argv:len() < 2 do
puts("fonts:")
- for index in 0..fonts:len() do puts(" #{fonts[index].name}(#{fonts[index].width}x#{fonts[index].height})") end
+
+ for index in 0..fonts:len() do
+ puts(" #{fonts[index].name}(#{fonts[index].width}x#{fonts[index].height})")
+ end
+
puts("\n")
const font_files = fs:entries("/usr/share")
if font_files != :nil do
puts("on disk:")
- for index in 0..font_files:len() do puts(" #{font_files[index].name}") end
+
+ for index in 0..font_files:len() do
+ puts(" #{font_files[index].name}")
+ end
+
puts("\n")
end
+
return :nil
end
+
# `font load <path> [size]` still works, but a bare name/path is the same thing
let name = context.argv[1]
let arg_at = 1
+
if name == "load" do
- if context.argv:len() < 3 do puts("usage: font load <path> [size]\n") return :nil end
+ if context.argv:len() < 3 do
+ puts("usage: font load <path> [size]\n")
+ return :nil
+ end
+
name = context.argv[2]
arg_at = 2
end
+
# an explicit size (ttf only) forces a fresh rasterize. without one we just
# switch to whatever is already in the registry under that name
let size = 0
- if context.argv:len() > arg_at + 1 do size = str_to_int(context.argv[arg_at + 1]) end
+
+ if context.argv:len() > arg_at + 1 do
+ size = str_to_int(context.argv[arg_at + 1])
+ end
+
if size == 0 do
const existing = find_font(name)
- if existing != :nil do console:set_font(existing) return :nil end
+
+ if existing != :nil do
+ console:set_font(existing)
+ return :nil
+ end
end
+
const info = font_file(name)
- if info.kind == :none do puts("font: #{name}: no such font\n") return :nil end
+
+ if info.kind == :none do
+ puts("font: #{name}: no such font\n")
+ return :nil
+ end
+
const reg_name = font_basename(info.path)
+
if info.kind == :psf do
const prior = find_font(reg_name)
- if prior != :nil do console:set_font(prior) return :nil end
+
+ if prior != :nil do
+ console:set_font(prior)
+ return :nil
+ end
+
const parsed = load_psf(info.path)
- if parsed == :nil do puts("font: #{name}: not a psf font\n") return :nil end
+
+ if parsed == :nil do
+ puts("font: #{name}: not a psf font\n")
+ return :nil
+ end
+
add_font(reg_name, parsed.glyphs, parsed.width, parsed.height, :bitmap, :nil, 0)
console:set_font(find_font(reg_name))
+
return :nil
end
+
# ttf: rasterize ascii at the requested size (default 18)
- if size == 0 do size = 18 end
+ if size == 0 do
+ size = 18
+ end
+
const face = load_ttf(info.path, size)
- if face == :nil do puts("font: #{name}: bad ttf\n") return :nil end
+
+ if face == :nil do
+ puts("font: #{name}: bad ttf\n")
+ return :nil
+ end
+
add_font(reg_name, face.glyphs, face.width, face.height, :ttf, info.path, size)
console:set_font(find_font(reg_name))
end
-fn bcd(byte) (byte shr 4) * 10 + (byte band 15)
-fn rtc(cmos_reg) do
+fn bcd(byte) -> num do
+ return (byte shr 4) * 10 + (byte band 15)
+end
+
+fn rtc(cmos_reg) -> num do
outb(0x70, cmos_reg)
- inb(0x71)
+ return 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
+fn cmd_time(context) do
+ const hour = 4 |> rtc |> bcd
+ const min = 2 |> rtc |> bcd
+ const sec = 0 |> rtc |> bcd
+
+ puts("utc #{hour}:#{min}:#{sec}\n")
+end
+
+#* 0x prefixed 16 digit hex *#
+fn hex(value) -> string 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}"
+
+ for index in 0..16 do
+ result = result ~ digits[(value shr ((15 - index) * 4)) band 15]
+ end
+
+ return "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"
+fn mem_type_name(kind) -> string do
+ match kind
+ | 0 => "usable"
+ | 1 => "reserved"
+ | 2 => "acpi-reclaimable"
+ | 3 => "acpi-nvs"
+ | 4 => "bad"
+ | 5 => "bootloader-reclaimable"
+ | 6 => "kernel+modules"
+ | 7 => "framebuffer"
+ | _ => "unknown"
end
-# the boot memory map dump
+#* 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_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 exec_file(target) do
+fn cmd_echo(context) do
+ if context.line:len() > 5 do
+ puts("#{context.line[5..]}\n")
+ end else do
+ puts("\n")
+ end
+end
+
+fn cmd_mkfs(context)
+ fs:format()
+
+#* resolve a command name to a path and a kind *#
+fn exec_file(target) -> { path: string, kind: ( :elf | :script | :none ) } do
let candidates = {}
+
if has_slash(target) do
candidates:push(expand_path(target))
candidates:push(expand_path(target) ~ ".rv")
- end
- else do
+ end else do
candidates:push(expand_path(target))
candidates:push(expand_path(target) ~ ".rv")
candidates:push("/usr/bin/#{target}")
candidates:push("/bin/#{target}.rv")
end
+
for index in 0..candidates:len() do
const path = candidates[index]
+
if fs:size(path) >= 0 do
const head = fs:read_bytes(path, 0, 4)
- if (head:len() >= 4 and head[0]:ascii() == 127 and head[1]:ascii() == 69 and head[2]:ascii() == 76 and head[3]:ascii() == 70) return { path = path, kind = :elf }
+
+ if head:len() >= 4
+ and head[0]:ascii() == 127
+ and head[1]:ascii() == 69
+ and head[2]:ascii() == 76
+ and head[3]:ascii() == 70 do
+ return { path = path, kind = :elf }
+ end
+
return { path = path, kind = :script }
end
end
+
return { path = target, kind = :none }
end
fn cmd_run(context) do
- if context.argv:len() <= 1 do list_progs() return :nil end
+ if context.argv:len() <= 1 do
+ list_progs()
+ return :nil
+ end
+
const target = context.argv[1]
const info = exec_file(target)
- if info.kind == :none do fail("run: #{target}: not found\n") return :nil end
+
+ if info.kind == :none do
+ fail("run: #{target}: not found\n")
+ return :nil
+ end
+
const sub_argv = context.rest:split(" ")
const sub_ctx = make_ctx(context.rest, sub_argv)
+
if info.kind == :script do
run_script_path(info.path, target, sub_ctx)
+
return :nil
end
- if load_file_elf(info.path, sub_ctx.rest) == :true return :hold
+
+ if load_file_elf(info.path, sub_ctx.rest) == :true do
+ return :hold
+ end
+
fail("run: #{target}: not executable\n")
+
return :nil
end
-fn run(line) do
+#* run one shell line *#
+fn run(line) -> :nil do
# the fact that the shell needs to interface directly with ed isnt, very nice
# this is a TODO btw
- if gils_ed_active() do gils_ed_feed(line) return :nil end
- if line:trim():len() == 0 do puts(prompt()) return :nil end
+ if gils_ed_active() do
+ gils_ed_feed(line)
+ return :nil
+ end
+
+ if line:trim():len() == 0 do
+ puts(prompt())
+ return :nil
+ end
+
const argv = line:split(" ")
const context = make_ctx(line, argv)
shell_status.code = 0
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(prompt()) end
+
+ if result != :hold do
+ puts(prompt())
+ end
+
+ return :nil
+ end
+
+ if run_script(argv[0], context) == :true do
+ puts(prompt())
+ return :nil
+ end
+
+ if run_prog(argv[0], context.rest) == :true do
return :nil
end
- if run_script(argv[0], context) == :true do puts(prompt()) return :nil end
- if run_prog(argv[0], context.rest) == :true return :nil
shell_status.code = 127
puts("#{argv[0]}: command not found\n")
puts(prompt())
diff --git a/kernel/tty.rv b/kernel/tty.rv
index 5fcd224..680289f 100644
--- a/kernel/tty.rv
+++ b/kernel/tty.rv
@@ -137,7 +137,8 @@ global tty = {
if byte == 27 do self.esc = 1 return :nil end # ESC, start a sequence
if byte == 10 do self:submit() return :nil end # enter
- if byte == 8 or byte == 127 do self:backspace() return :nil end # bs / del
+ if byte == 8 do self:backspace() return :nil end # bs
+ if byte == 127 do self:erase_word() return :nil end # del (ctrl-bs): erase word
if byte == 1 do self:home() return :nil end # ctrl-a
if byte == 5 do self:to_end() return :nil end # ctrl-e
if byte == 3 do self:cancel() return :nil end # ctrl-c