Still working on standards.

This commit is contained in:
2026-02-14 23:35:41 +00:00
parent 5a3c126100
commit e2ad04b692
18 changed files with 1046 additions and 234 deletions

129
parse.lua
View File

@@ -64,10 +64,18 @@ function Expression:new(items)
self:insert(item)
end
end
-- Return the iterator through the expression
function Expression:iterate()
return iterate(self.items)
end
-- Is the expression empty?
function Expression:empty()
return #self.items == 0
end
-- insert a word or sub expression into this one
--[[
Insert a word or sub expression into this one
Note: The expression has a .items = {} list.
]]
function Expression:insert(...)
-- closures for nvim highlighting
local function insert_word(word)
@@ -83,15 +91,19 @@ function Expression:insert(...)
end
for item in iterate({...}) do
case_of({
table = function(expr)
[Expression] = function(expr)
insert_expression(expr)
end,
string = function(word)
insert_word(word)
end
})(type(item),item)
})(type_of(item),item)
end
end
--[[
Converts an expression to meaningful text format.
This should be reparseable.
]]
function Expression:string()
local parts = {}
for index,item in pairs(self.items) do
@@ -99,6 +111,30 @@ function Expression:string()
end
return string.format("(%s)",table.concat(parts," "))
end
--[[
Similarly to abstract, returns this expression
with subexpression:reduce(depth-1)
Useful for more informative debugging
]]
function Expression:reduce(depth)
local parts = {}
for index,item in pairs(self.items) do
parts[index] = case_of({
[Expression] = function(item)
if depth == 0 then
return Expression({"..."})
else
return item:reduce(depth - 1)
end
end,
string = id
})(type_of(item),item)
end
end
--[[
Returns this expression with all sub expressions
simplified to text by :text() or '...'
]]
function Expression:abstract(infer)
local parts = {}
local count = 0
@@ -122,6 +158,10 @@ function Expression:abstract(infer)
end
return Expression(parts)
end
--[[
Return the single 'words' part of this expression
if it is alone. Otherwise return nil.
]]
function Expression:text()
-- Get the text
local text = self.items[#self.items]
@@ -129,21 +169,32 @@ function Expression:text()
if type(text) ~= "string" then return end
return text
end
--[[
Give this expression a meaningful debug location
referring to some source code.
]]
function Expression:locate(str,uri)
self.location = str
self.source = uri
return self
end
--[[
Return the meaningful debug location as a string msg.
]]
function Expression:link(msg)
return string.format(
"in '%s' @%s (%s)",
msg,
tostring(self),
tostring(self:abstract()),
tostring(self.location or "?"),
tostring(self.source or "?.nel")
)
end
--[[
A class which parses text using the 'consumer-meal'
model.
]]
Consumer = class("Consumer")
function Consumer:new(content)
self.range = Range(Reader(content))
@@ -226,13 +277,18 @@ 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)
for item in iterate(e) do
assert_meta(Expression)(item)
end
assert_meta(Scope)(s,"Arg 'scope' #1: %s")
assert_meta(Chain)(c,"Arg 'chain' #3: %s")
return self.value or self.callback(s,e,c)
end
function Binding:check(expression)
-- Expecting an abstract expression!
function Binding:check(expression,candid)
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
local candid = expression:abstract()
candid = candid or expression:abstract()
assert_meta(Expression)(candid,"Arg 'abstract' #2: %s")
local data = {}
local index
local loop = true
@@ -249,7 +305,18 @@ function Binding:check(expression)
if type_of(candid_item) ~= Expression then
loop = false
else
data[template_item:text()] = expression.items[index]
local text = template_item:text()
if not text then
error(
string.format(
[[There was a subexpression without
just text in this expression: %s]],
tostring(candid)
)
)
end
data[text] = expression.items[index]
assert_meta(Expression)(expression.items[index])
end
end,
string = function()
@@ -291,6 +358,31 @@ function Chain:flatten(tab)
table.insert(tab,self.data)
return tab
end
function Chain:pcall(func)
local success,result = xpcall(func,function(msg)
-- If this is a 'nel:' error, strip the line responsible
-- Otherwise just return the whole traceback since something went really bad
local match = msg:match("nel:%s(.*)")
if match then
return match
else
return "internal: "..debug.traceback(msg,2)
end
end,self)
if not success then
log(string.format(
"nel: %s\nNEL traceback:\n\t%s",
result,
table.concat(self:flatten(),"\n\t")
))
end
return successs,result
end
function Chain:call(func)
local success,result = self:pcall(func)
if not success then error("Chain: Halt!") end
return result
end
-- The scope of a neli expression under evaluation
-- without exact faith to traditional language scopes
@@ -308,7 +400,7 @@ 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.")
error(string.format("nel: Conflicting sibling bindings in scope for '%s'.",binding.template))
end
end
table.insert(self.bindings,binding)
@@ -317,10 +409,12 @@ 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")
assert_meta(Scope,original,"Arg 'scope' #3: %s")
-- Could be multiple bindings so make a table
local binds = {}
local abstract = expression:abstract()
for binding in iterate(self.bindings) do
local bind = binding:check(expression)
local bind = binding:check(expression,abstract)
-- Insert into table
if bind then binds[binding] = bind end
end
@@ -328,21 +422,21 @@ function Scope:run(expression,chain,original)
-- Check for multiple
if not bind then
if self.parent then
return self.parent:run(expression,chain,self)
return self.parent:run(expression,chain,original)
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?"
extra = extra .. "\nNote: This expression is formed (just bracketed) of another single expression. Maybe too many brackets were used here? Are you using curly braces?"
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."
extra = extra .. "\n\tNote: This is a 'word only' 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()),
"nel: No binding for '%s' in scope.%s",
tostring(expression),
extra
),3
)
@@ -361,6 +455,9 @@ function Scope:run(expression,chain,original)
)
,2)
else
for index,item in pairs(binds) do
print("index:",index,"item:",item)
end
return binding:call(
original,
bind,