hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/gils
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-08 22:44:54 +0200
committerhachem <im@hachem.wtf>2026-10-08 22:44:54 +0200
commit8cb66100fe802c38d180ea29a11a902decf0fd8b (patch)
tree30138f3e07cfaa258611c858ede95e73c58adcbd /gils
parent852a5b237116e8b53ae18ea8ea00505e44b40dfe (diff)
parent93f080a04032011a4a2f90ab1fe29635cc85cdf9 (diff)
Merge branch 'if-not-nil-main'
Diffstat (limited to 'gils')
-rw-r--r--gils/ed.rv357
1 files changed, 294 insertions, 63 deletions
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)