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

299
nelli.lua Normal file
View File

@@ -0,0 +1,299 @@
require("interpret")
function B(scope,abstract,callback)
scope:insert(
Binding(
interpret(abstract),
callback
)
)
end
function R(scope,expression,chain,name)
name = name or "?"
assert_meta(Chain)(chain,"Arg 'chain' #3: %s")
assert_meta(Scope)(scope,"Arg 'scope' #1: %s")
assert_meta(Expression)(expression,"Arg 'expression' #2: %s")
local chain = Chain(expression:link(name)):from(chain)
return scope:run(expression,chain)
end
NelliScope = Scope()
local ns = NelliScope
-- Replace all :text() occurrences with ones in 'exp_map'
local function substitute(target,exp_map)
local text = target:text()
if text then -- Replace the parameter
local map = exp_map[text]
if map then
return Expression({map})
else
return target
end
else
local new = Expression()
for sub in iterate(target.items) do
local t = type_of(sub)
if t == "string" then
new:insert(sub)
elseif t == Expression then
new:insert(substitute(sub,exp_map))
end
end
return new
end
end
-- Iterate over all :text() occurrences
local function traverse(expression,callback)
for sub in iterate(expression.items) do
if type_of("sub") == Expression then
traverse(sub,callback)
else
callback(sub)
end
end
end
local function Bexpands(s,e,c)
local bound = {}
for sub in iterate(e.abstract.items) do
local t = type_of(sub)
if t == Expression then
local text = sub:text()
if not (
text:match("'.+'")
) then
--[[error(string.format(
"nel: Abstract parameter \"%s\" not in 'quotes'.",
text
))]]
else
table.insert(bound,text)
end
end
end
return Binding(e.abstract,function(_s,_e,_c)
local map = {}
for binding in iterate(bound) do
map[binding] = _e[binding]
end
local expanded = substitute(e.expression,map)
return R(_s,expanded,_c,"means: "..tostring(expanded))
end)
end
B(ns,"(abstract) means (expression)",Bexpands)
local function Bevaluate(s,e,c)
return Binding(e.abstract,function(_s,_e,_c)
local scope = Scope()
scope.parent = s
for name,expression in pairs(_e.items) do
scope:insert(Binding(
Expression({name}),
R(_s,_e,_c,string.format("expands: %s:",name))
))
end
return R(scope,e,c,"evaluate: result")
end)
end
B(ns,"(abstract) becomes (expression)",Bevaluates)
B(ns,"(identifier) is (expression)",function(s,e,c)
if not e.identifier:text(s,e,c) then
error("nel: text-only expression expected.")
end
return Binding(e.identifier,R())
end)
local function doAll(s,e,c)
local res
for item in iterate(e.items) do
if type_of(item) ~= Expression then
error(
string.format(
"Unexpected words '%s' in 'do' expression.",
tostring(item)
)
)
end
res = R(s,item,c,"do: line")
end
return res
end
-- Note that do actually runs stuff in the same scope it was called
-- Otherwise we can't have easily multiple mutations to the parent scope
local function Bdo(s,e,c)
local scope
if e.scope then
scope = R(s,e.scope,c,"do: scope")
else
scope = Scope()
scope.parent = s
end
return doAll(s,e.expression,c)
end
B(ns,"do (expression) in (scope)",Bdo)
B(ns,"do (expression)",Bdo)
B(ns,"(abstract) broken into (expressions) as do (results)",function(s,e,c)
return Binding(e.abstract,function(_s,_e,_c)
local params = List()
for index,item in pairs(_e) do
local expr = Object()
expr.identifier = index
expr.expression = item
table.insert(params,expr)
end
local expressions = e.expressions:text()
if not expressions then
error(
string.format(
"nel: binding: sub expressions: %s not text-only!",
e.expressions
)
)
end
local scope = Scope()
scope.parent = s
scope:insert(Binding(Expression{expressions},params))
return doAll(s,e.expressions,c)
end)
end)
local _list = {}
local function List()
return setmetatable(_list,{})
end
B(ns,"a new list",function(s,e,c)
return List()
end)
B(ns,"for each (item) in (list) do (expressions)",function(s,e,c)
local item = e.item:text()
if not property then
error(
string.format(
"nel: for each: item: '%s' not text only!",
e.property
)
)
end
local list = R(s,e.list,c,"for each: list")
assert_meta(List)(list,"for each: list: %s")
for index,item in ipairs(list) do
local scope = Scope()
scope.parent = s
scope:insert(Binding(Expression{item}))
doAll(s,e.expressions,c)
end
end)
B(ns,"add (item) to (list)",function(s,e,c)
local item = R(s,e.list,c,"")
local list = R(s,e.list,c,"add: list")
assert_meta(List)(list,"add: list: %s")
table.insert(list,item)
end)
-- I even quite dislike these, they are prone to gumming things up
local _table = {}
B(ns,"a new table",function(s,e,c) return {} end)
B(ns,"set (index) of (table) to (value)",function(s,e,c)
local index = e.index:text()
if not index then
index = R(s,e.index,c,"set: index")
end
local tab = R(s,e.table,c,"set: table")
local val = R(s,e.value,c,"set: value")
tab[index] = val
end)
B(ns,"get (index) of (table)",function(s,e,c)
local index = e.index:text()
if not index then
index = R(s,e.index,c,"get: index")
end
local tab = R(s,e.table,c,"get: table")
return tab[index]
end)
local _object = {}
local function Object()
return setmetatable(_object,{})
end
B(ns,"a new object",function(s,e,c)
return Object()
end)
B(ns,"(property) of (object) is (value)",function(s,e,c)
local property = e.property:text()
if not property then
error(
string.format(
"nel: property is: property: '%s' not text only!",
e.property
)
)
end
local object = R(s,e.object,c,"property is: object")
assert_meta(Object)(object,"nel: property is: object: %s")
local value = R(s,e.value,c,"property is: value")
object[property] = value
end)
B(ns,"(property) of (object)",function(s,e,c)
local property = e.property:text()
if not property then
error(
string.format(
"nel: property is: property: '%s' not text only!",
e.property
)
)
end
local object = R(s,e.object,c,"property is: object")
assert_meta(Object)(object,"nel: property is: object: %s")
return object[property]
end)
B(ns,"error (text)",function(s,e,c)
error("nel: "..R(s,e.text,c,"error: message"))
end)
B(ns,"this scope",function(s,e,c)
return s
end)
B(ns,"a new scope",function(s,e,c)
return Scope()
end)
-- Read a file in the current directly, more for compiling
B(ns,"read file (path)",function(s,e,c)
return read(R(s,e.path,c,"read: path"))
end)
-- Take some text and interpret it into an expression
B(ns,"evaluate (text) in (scope)",function(s,e,c)
local scope = R(s,e.scope,c,"evaluate: scope")
return R(s,interpret(R(scope,e.text,c,"include: source")),c,"include: result")
end)
B(ns,"print (text)",function(s,e,c)
log(R(s,e.text,c,"print: text"))
end)
B(ns,"text (literal)",function(s,e,c)
return e.literal:text()
end)
B(ns,"return (expression)",function(s,e,c)
return R(s,e.expression,c,"return: result")
end)
B(ns,"(macro) means (expansion)",function(s,e,c)
end)
B(ns,"(identifier) is (value)",function(s,e,c)
end)
B(ns,"(expression) becomes (result)",function(s,e,c)
end)
B(ns,"let (binding) in (scope)",function(s,e,c)
end)
local Hpath = "nel/helper.nel"
local Hchain = Chain("in internal: "..Hpath)
local success,result = Hchain:call(function(chain)
R(NelliScope,interpret(read(Hpath),Hpath,chain),chain)
end)
--[[ Documentation:
(a new scope) -> Becomes a created new scope
(this scope) -> Becomes the current scope
(macro) expands to (expansion) -> Becomes a binding which makes
(<macro>) become whatever (result) will become in place
(expression)
--]]
return ns