oats/src/oats.lua

161 lines
3.9 KiB
Lua
Raw Normal View History

2024-11-20 20:33:22 +00:00
local oats = {}
--- @class tag
--- @field name string
--- @overload fun(table): tag
local tag = setmetatable({}, {__call = function(self, new)
if not new.name then
error("Attempting to create a tag without a name", 2)
end
setmetatable(new, self)
end})
tag.__index = tag
local t1 = tag { name = "tester" }
--- @alias node string|tag
--- @alias callback fun(event: "text"|"open"|"close"|"warn", content: string|nil): nil
--- @param callback callback
--- @param last number
--- @param current number
--- @param number number
local function handledepth(callback, last, current, number)
for _ = current, last do
callback("close")
end
if current > last+1 then
error(string.format("Line %i: Indentation increased by more than one level: %i -> %i", number, last, current))
end
end
2024-12-20 22:21:46 +00:00
local function parsetext(callback, text, number)
local start = 1
local inline while true do
inline = text:find("[", start, true)
if inline then
callback("text", text:sub(start, inline - 1))
local close = text:find("]", inline, true)
if close then
local inline_content = text:sub(inline+1, close-1)
local inline_name, inline_text
inline_name, inline_text = inline_content:match("^([^%s]+)%s+(.+)$")
if not inline_name then
inline_name = inline_content
end
callback("open", inline_name)
if inline_text then
callback("text", inline_text)
end
callback("close")
start = close + 1
else
error(string.format("Endless inline tag on %i:%i", number, inline))
end
else
break
end
end
2025-03-12 10:56:11 +00:00
callback("text", (start == 1) and text or text:sub(start))
2024-12-20 22:21:46 +00:00
end
2024-11-20 20:33:22 +00:00
--- @param callback callback
--- @param line string
--- @param number number Line number currently being processed
local function parseline(callback, line, lastdepth, number)
local depth, name, text
depth, name = line:match("^\t*()%[([^%s]+)%]$")
if depth then
handledepth(callback, lastdepth, depth, number)
callback("open", name)
return depth
end
depth, name, text = line:match("^\t*()%[([^%s]+)%]%s*(.*)$")
if depth then
handledepth(callback, lastdepth, depth, number)
callback("open", name)
2024-12-20 22:21:46 +00:00
parsetext(callback, text, number)
2024-11-20 20:33:22 +00:00
callback("close")
return depth-1 -- Minus one because the tag is already closed
end
if line:match("^%s*$") then
return lastdepth
end
depth, text = line:match("^\t*()(.*)$")
if depth then
handledepth(callback, lastdepth, depth, number)
2024-12-20 22:21:46 +00:00
parsetext(callback, text, number)
2024-11-20 20:33:22 +00:00
return depth-1 -- Minus one because text doesn't get closed
end
end
--- Decodes a stream of lines
--- @param callback callback
function oats.parse(callback, ...)
local line = 1
local depth = 0
for content in ... do
depth = parseline(callback, content, depth, line)
line = line + 1
end
end
--- @return (node)[]
--- @return callback
local function consumer(document)
document = document or {}
local current = document
local path = {current}
local function callback(event, content)
if event == "text" then
table.insert(current, content)
elseif event == "open" then
local next = {name=content}
table.insert(current, next)
table.insert(path, next)
current = next
elseif event == "close" then
local i = #path
path[i] = nil
current = path[i-1]
end
end
return document, callback
end
--- Decodes a string
--- @param input string An entire OATS document contained in a string
--- @return (node)[] # A list of top-level elements
--- @overload fun(path:string): nil,string
function oats.decode(input)
local document, callback = consumer()
oats.parse(callback, input:gmatch("[^\n]+"))
return document
end
--- Decodes a file
--- @param path string Path to the file to decode
--- @return (node)[] # A list of top-level elements
--- @overload fun(path:string): nil,string
function oats.decodefile(path)
local file, err = io.open(path)
if not file then
err = err or "Unknown error opening file"
return nil, err
end
local document, callback = consumer()
oats.parse(callback, file:lines())
return document
end
return oats