Files
nel/parse.lua

372 lines
10 KiB
Lua

require("helper")
-- CLASS DEFINITIONS
-- A stream of text (kind of)
-- with some helping methods
Reader = class("Reader")
-- Construct a reader from
function Reader:new(text)
assert_type("string")(text,"Arg 'text' #1: %s")
self.text = text
end
-- Get the substring of the readers text in a bounds
function Reader:get(first,final)
assert_type("number")(first,"Arg 'first' #1: %s")
assert_type("number","nil")(final,"Arg 'final' #2: %s")
return self.text:sub(first,final)
end
-- Match on the reader's text from an index
function Reader:find(pattern,init)
assert_type("string")(pattern,"Arg 'pattern' #1: %s")
assert_type("number","nil")(init,"Arg 'init' #2: %s")
return self.text:find(pattern,init)
end
function Reader:match(pattern,init)
assert_type("string")(pattern,"Arg 'pattern' #1: %s")
assert_type("number")(init,"Arg 'init' #2: %s")
return self.text:match(pattern,init)
end
-- A range of text being considered
-- like a cursor
Range = class("Range")
-- Construct a range of text from a reader and bounds
function Range:new(reader,first,final)
assert_meta(Reader)(reader)
assert_type("number","nil")(first,"Arg 'first' #1: %s")
assert_type("number","nil")(final,"Arg 'final' #2: %s")
self.reader = reader
self.first = first
self.final = final
end
function Range:text()
return self.reader:get(self.first or 1,self.final)
end
function Range:find(pattern)
return self.reader:find(pattern,self.first)
end
function Range:move(first,final)
assert_type("number","nil")(first,"Arg 'first' #1: %s")
assert_type("number","nil")(final,"Arg 'final' #2: %s")
self.first = first
self.final = final
-- note that this function returns itself
return self
end
Expression = class("Expression")
-- Construct an expression from items (or not)
function Expression:new(items)
items = items or {}
assert_type("table")(items)
self.items = {}
for item in iterate(items) do
self:insert(item)
end
end
function Expression:empty()
return #self.items == 0
end
-- insert a word or sub expression into this one
function Expression:insert(...)
-- closures for nvim highlighting
local function insert_word(word)
-- Whitespace might confuse bindings, remove them
local cleaned = word:match("^%s*(.-)%s*$")
if cleaned ~= "" then
table.insert(self.items,cleaned)
end
end
local function insert_expression(expr)
assert_meta(expr,Expression)
table.insert(self.items,expr)
end
for item in iterate({...}) do
case_of({
table = function(expr)
insert_expression(expr)
end,
string = function(word)
insert_word(word)
end
})(type(item),item)
end
end
function Expression:string()
local parts = {}
for index,item in pairs(self.items) do
parts[index] = tostring(item)
end
return string.format("(%s)",table.concat(parts," "))
end
function Expression:abstract(infer)
local parts = {}
local count = 0
for index,item in pairs(self.items) do
parts[index] = case_of({
[Expression] = function(item)
local text
if infer then
text = item:text()
if not text then
count = count + 1
text = tostring(count)
end
else
text = "..."
end
return Expression({text})
end,
string = id
})(type_of(item),item)
end
return Expression(parts)
end
function Expression:text()
-- Get the text
local text = self.items[#self.items]
-- Ensure that the subexpression is valid abstract
if type(text) ~= "string" then return end
return text
end
function Expression:locate(str,uri)
self.location = str
self.source = uri
return self
end
function Expression:link(msg)
return string.format(
"in '%s' @%s (%s)",
msg,
tostring(self),
tostring(self.location or "?"),
tostring(self.source or "?.nel")
)
end
Consumer = class("Consumer")
function Consumer:new(content)
self.range = Range(Reader(content))
self.col = 1
self.row = 1
end
function Consumer:remaining()
return self.range:text()
end
function Consumer:mark()
end
-- From a table of actions of patterns,
-- consume the earliest pattern (index)
-- and call the value
function Consumer:consume(t)
-- t: {string = function...}
assert_type("table")(t,"Arg 't' #1: %s")
if not next(t) then
error("The match table cannot be empty!",2)
end
for index,func in pairs(t) do
assert_type("string")(index,"Bad 't' index: %s")
assert_type("function")(func,"Bad 't' item: %s")
end
local origin = self.range.first or 1
-- Get the next earliest match
local findex,ffinal,fpattern,ffunc,fexcess
for pattern,new_func in pairs(t) do
local new_index,new_final = self.range:find(pattern)
if new_index and
((not findex) or (findex > new_index))
then
if not new_index then return end
findex = new_index
ffinal = new_final
fpattern = pattern
ffunc = new_func
fexcess = self.range.reader:get(
self.range.first or 1,
new_final - 1
)
end
end
-- Pass into the func
if not ffunc then
return nil
end
assert(findex) assert(ffinal)
self.range:move(ffinal+1) -- Move range to after the match
local fmatch = self.range.reader:match(fpattern,findex)
-- Pass back consumed result to
local sum = self.range.reader:get(origin+1,self.range.first)
:gsub("\r\n","\n")
:gsub("\r","\n")
for char in sum:gmatch(".") do
if char == "\n" then
self.row = self.row + 1
self.col = 1
else
self.col = self.col + 1
end
end
return ffunc(fexcess,fmatch)
end
Binding = class("Binding")
-- Construct from a neli string to infer a binding
-- note that you must wrap this definition in "(<def>)"!
function Binding:new(expression,callback)
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
if type_of(callback) == "function" then
self.callback = callback
else
self.value = callback
end
self.template = expression:abstract(true)
end
function Binding:string()
return string.format("Binding %s: %s",self.template,tostring(self.value or self.callback))
end
function Binding:call(s,e,c)
assert_meta(Scope)(s)
assert_meta(Chain)(c)
return self.value or self.callback(s,e,c)
end
function Binding:check(expression)
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
local candid = expression:abstract()
local data = {}
local index
local loop = true
if #candid.items ~= #self.template.items then
return false
end
while loop do
local old = index
index,template_item = next(self.template.items,old)
index,candid_item = next(candid.items,old)
local result = case_of({
[Expression] = function()
-- item is not an expression, cannot bind
if type_of(candid_item) ~= Expression then
loop = false
else
data[template_item:text()] = expression.items[index]
end
end,
string = function()
-- words don't match, cannot bind
if candid_item ~= template_item then
loop = false
end
end,
["nil"] = function()
-- base case where both expressions terminate together
if not candid_item then
return data
end
end
})(type_of(template_item))
if result then return result end
end
end
Chain = class("Chain")
function Chain:new(data)
assert_type("string")(data,"Arg 'data' #1: %s")
self.data = data
end
function Chain:from(previous)
assert_meta(Chain)(previous,"Arg 'previous' #1: %s")
previous.next = self
self.previous = previous
return self
end
function Chain:string()
return string.format("Chain:%s",table.concat(self:flatten(),", "))
end
function Chain:flatten(tab)
tab = tab or {}
if self.next then
self.next:flatten(tab)
end
table.insert(tab,self.data)
return tab
end
-- The scope of a neli expression under evaluation
-- without exact faith to traditional language scopes
Scope = class("Scope")
function Scope:new(bindings)
self.bindings = {}
for binding in iterate(bindings or {}) do
self:insert(binding)
end
end
function Scope:string()
return string.format("Scope: {\n\t%s\n}",table.concat(map(self.bindings,tostring),"\n\t"))
end
function Scope:insert(binding)
assert_meta(Binding)(binding,"Arg 'binding' #1: %s")
for competitor in iterate(self.bindings) do
if competitor:check(binding.template) then
nerror("Conflicting sibling bindings in scope.")
end
end
table.insert(self.bindings,binding)
end
function Scope:run(expression,chain,original)
original = original or self
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
assert_meta(Chain)(chain,"Arg 'chain' #2: %s")
-- Could be multiple bindings so make a table
local binds = {}
for binding in iterate(self.bindings) do
local bind = binding:check(expression)
-- Insert into table
if bind then binds[binding] = bind end
end
local binding,bind = next(binds)
-- Check for multiple
if not bind then
if self.parent then
return self.parent:run(expression,chain,self)
else
local extra = ""
if #expression.items == 1 then
local item = expression.items[1]
if type_of(item) == Expression then
extra = extra .. "\nNote: This expression is formed (just bracketed) of another single expression. Maybe too many brackets were used here?"
else
extra = extra .. "\n\tNote: This is a 'text' expression, it has no subexpressions. It is most likely that an expected object definition was not bound to the scope or there is a typo."
end
end
error(
string.format(
"nel: No binding for '%s' in scope.",
tostring(expression:abstract()),
extra
),3
)
end
-- Should never happen!
elseif next(binds,binding) then
local candidates = {}
for binding,bind in pairs(binds) do
table.insert(candidates,tostring(binding.template))
end
error(
string.format(
"nel: Ambiguous bindings in scope for '%s': %s",
tostring(expression:abstract()),
table.concat(candidates)
)
,2)
else
return binding:call(
original,
bind,
chain
)
end
end
return _G