Renaming.

This commit is contained in:
2026-03-03 14:22:18 +00:00
parent b89e015170
commit d7ca3739aa
9 changed files with 13 additions and 15 deletions

203
nellie/helper.lua Normal file
View File

@@ -0,0 +1,203 @@
local old_print = print
function print(...)
local info = debug.getinfo(2)
local line = info.currentline
local name = info.source
old_print(string.format("%i%s> ",line,name),...)
end
function log(...)
old_print(...)
end
function map(tab,func)
local new = {}
for index,item in pairs(tab) do
new[index] = func(item)
end
return new
end
-- HELPER FUNCTIONS
-- Insert every item in t1 into t0
function just(...)
local items = {...}
return function()
return table.unpack(items)
end
end
id = function(...) return ... end
function union(t0,t1)
for index,item in pairs(t0) do
t0[index] = t1
end
return t0
end
-- Iterate (ipairs without the index)
function iterate(...)
local iterator,tab,i = ipairs(...)
return function(...)
local index,item = iterator(tab,i)
i = index
return item
end,tab,i
end
function indices(t)
if type(t) ~= "table" then
error(
string.format(
"Argument 1 '%s' must be a table",
tostring(t)
)
,2)
end
local indices = {}
for index,_ in pairs(t) do
table.insert(indices,index)
end
return indices
end
-- Ensures a table has a metatable
function ensure_metatable(t)
local meta = getmetatable(t)
if not meta then meta = {} setmetatable(t,meta) end
return meta
end
function strings(t,item)
ensure_metatable(t).__tostring = item
end
-- Makes a table callable
function calls(t,fun)
ensure_metatable(t).__call = fun
end
-- Makes a table record access
function indexes(t,fun)
ensure_metatable(t).__index = fun
end
-- Makes a table record setting
function modifies(t,fun)
ensure_metatable(t).__newindex = fun
end
-- Shallow table to string
function table_tostring(tab,sep)
local items = {}
for item in iterate(tab) do
table.insert(items,tostring(item))
end
return table.concat(items,sep)
end
function type_of(item)
local type_name = type(item)
if type_name == "table" then
local meta = getmetatable(item)
if not meta then
return "table"
else
return meta
end
else
return type_name
end
end
-- DEBUGGING FUNCTIONS
-- Runs a function
function assert_meta(...)
local metas = {...}
return function(x,raise,handler)
handler = handler or error
local meta = getmetatable(x)
for tab in iterate(metas) do
if meta == tab then return end
end
local s_metas = {}
for meta in iterate(metas) do
table.insert(s_metas,tostring(meta))
end
local level = 2
local msg = string.format(
"Expected metatables '%s' but got '%s'",
table.concat(s_metas,","),
type(x)
)
if raise then
level = 3
msg = string.format(raise,msg)
end
handler(msg,level)
end
end
function assert_type(...)
local types = {...}
return function(x,raise)
local t = type(x)
for name in iterate(types) do
if t == name then return end
end
local level = 2
local msg = string.format(
"Expected type '%s' but got '%s'",
table_tostring(types," or "),
type(x))
-- For argument errors
if raise then
level = 3
msg = string.format(raise,msg)
end
error(msg,level)
end
end
-- HELPER FUNCTIONS
function case_of(t)
return function(item,...)
return (t[item] or error(
string.format(
"Couldn't match '%s' in case.",
tostring(item)
),2
))(...)
end
end
-- Creates a (p)OOP class
function class(name)
local meta = {}
union(meta,{restrict = {}})
meta.__index = meta
strings(meta,just(name))
calls(meta,function(meta,...)
local new = {}
setmetatable(new,meta)
if meta.new then new:new(...) end
return new
end)
meta.__tostring = function(this)
if meta.string then return this:string() end
return name
end
return meta
end
-- Read a file's full contents
function read(path)
local file = io.open(path)
if not file then
error(
string.match("Could not open file '%s'.",path),3
)
end
local content = file:read("a")
return content
end
return _G

27
nellie/index.lua Normal file
View File

@@ -0,0 +1,27 @@
require("nelli")
local function main(args)
-- Take arguments as neli files to read and interpret
local root = Chain(
string.format(
"in cmd 'nelli {%s}'",
table.concat(args,";")
)
)
for _,parameter in pairs(args) do
local chain = Chain(
string.format("in 'call: root' @'%s'",parameter)
):from(root)
root:call(function()
local root = interpret(
read(parameter),
parameter,
chain
)
NelliScope:run(root,chain)
end)
end
end
-- A list of arguments passed into this
main({...})

468
nellie/interpreter.lua Normal file
View File

@@ -0,0 +1,468 @@
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
-- 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
Note: The expression has a .items = {} list.
]]
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({
[Expression] = function(expr)
insert_expression(expr)
end,
string = function(word)
insert_word(word)
end
})(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
parts[index] = tostring(item)
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
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
--[[
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]
-- Ensure that the subexpression is valid abstract
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: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))
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)
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
-- Expecting an abstract expression!
function Binding:check(expression,candid)
assert_meta(Expression)(expression,"Arg 'expression' #1: %s")
candid = candid or expression:abstract()
assert_meta(Expression)(candid,"Arg 'abstract' #2: %s")
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
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()
-- 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
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 success,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
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
error(string.format("nel: Conflicting sibling bindings in scope for '%s'.",binding.template))
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")
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,abstract)
-- 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,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? Are you using curly braces?"
else
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.%s",
tostring(expression),
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
for index,item in pairs(binds) do
print("index:",index,"item:",item)
end
if not binding then error("No binding") end -- todo: improve error
return binding:call(
original,
bind,
chain
)
end
end

194
nellie/parser.lua Normal file
View File

@@ -0,0 +1,194 @@
require("interpreter")
-- TODO: This should really be a separate API and module
-- please break it out
-- Interpret some text
function Parse(content,uri,chain)
-- LOCALS
-- The pattern for words inbetween expressions
local expressionWordPattern = "(.*)"
-- The patterns to open and close expressions,
-- the words scattered inbetween them will be matches
local expressionOpenPattern = "%("
local expressionClosePattern = "%)"
local subExpressionOpenPattern = "%:"
local subExpressionClosePattern = "%,"
local blockOpenPattern = "%{"
local blockClosePattern = "%}"
local blockDelimiterPattern = "[;]+"
-- So that all functions have access to eachother
local consume,
consumeExpression,
processWords,
processClosure,
processOpening,
consumeBlock,
consumeText,
consumeNumber
local consumer = Consumer(content)
local function expression(...)
return Expression(...):locate(
string.format(
"%i:%i",
consumer.row,
consumer.col
),uri)
end
local baseExpression = expression()
-- FUNCTIONS
-- Consume an expression, with its sub expressions
-- given that an opening has just been consumed
local singleLineStringPattern = "\""
local function singleLineStringMeal(current,match)
return function(words,_)
current:insert(words,consumer:consume({
[match] = function(words,_)
return expression({"text",Expression({words})})
end
}))
end
end
local multiLineStringOpenPattern = "%[%["
local multiLineStringClosePattern = "%]%]"
local function multiLineStringMeal(current,match)
return function(words,_)
current:insert(words,consumer:consume({
["[\n\r]"] = function()
error("Incomplete string literal")
end,
[match] = function(words,_)
return expression({"text",Expression({words})})
end
}))
end
end
local uriOpenPattern = "<"
local uriClosePattern = ">"
local function URIMeal(current,match)
return function(words,_)
current:insert(words,consumer:consume({
["[\n\r]"] = function()
current:error("Incomplete URI literal")
end,
[match] = function(path)
return read(path)
end
}))
end
end
local function expressionMeal(current)
return function(words,_)
current:insert(words,consumeExpression())
end
end
local singleLineCommentPattern = "//"
local function singleLineCommentMeal(current)
return function(words,_)
current:insert(words) -- Consume what was left
consumer:consume({
["[\n\r]"] = function() end
})
end
end
local multiLineCommentPattern = "/%*"
local function multiLineCommentMeal(current)
return function(words,_) -- consume what was left
current:insert(words)
consumer:consume({
["%*/"] = function() end
})
end
end
local colonSyntaxPattern = ":"
local function colonSyntaxMeal(current)
return function(words,_)
current:insert(words,consumeColonExpression())
end
end
function consumeBlock()
local expressions = {}
local current = expression()
local loop = true
while loop do
local expr = consumer:consume({
[multiLineCommentPattern] = multiLineCommentMeal(current),
[singleLineCommentPattern] = singleLineCommentMeal(current),
[uriOpenPattern] = URIMeal(current,uriClosePattern),
[expressionOpenPattern] = expressionMeal(current),
[multiLineStringOpenPattern] =
multiLineStringMeal(
current,multiLineStringClosePattern),
[singleLineStringPattern] =
singleLineStringMeal(
current,singleLineStringPattern),
[blockDelimiterPattern] = function(words,_)
current:insert(words)
if current:empty() then
--error("Extravenous semicolon.")
else
table.insert(expressions,current)
end
current = expression()
end,
[blockClosePattern] = function(words,_)
current:insert(words)
loop = false
end,
[blockOpenPattern] = function(words,_)
current:insert(
words,
consumeBlock()
)
end,
})
end
if #current.items ~= 0 then
table.insert(expressions,current)
end
return expression(expressions)
end
function consumeExpression()
local current = expression()
-- Loop, adding new expressions and words,
-- until closing that is
local loop = true
while loop do
local remaining = consumer:remaining()
local expr = consumer:consume({
[multiLineCommentPattern] = multiLineCommentMeal(current),
[singleLineCommentPattern] = singleLineCommentMeal(current),
[uriOpenPattern] = URIMeal(current,uriClosePattern),
[expressionOpenPattern] = expressionMeal(current),
[multiLineStringOpenPattern] =
multiLineStringMeal(
current,multiLineStringClosePattern),
[singleLineStringPattern] =
singleLineStringMeal(
current,singleLineStringPattern),
[expressionOpenPattern] = expressionMeal(current),
[expressionClosePattern] = function(words,_)
current:insert(words)
loop = false
end,
[blockOpenPattern] = function(words,_)
current:insert(
words,
consumeBlock()
)
end,
["$"] = function(words,last)
current:insert(remaining)
loop = false
end
})
-- This single line below made me procrastinate life for a total of 4 hours on instagram reels; lock in.
-- if not expr then print("brk") break end -- Since now closing
end
return current
end
return consumeExpression()
-- TODO: check for later expressions please?
end

296
nellie/proto.lua Normal file
View File

@@ -0,0 +1,296 @@
-- Prototype for nel
require("interpreter")
function Bind(scope,abstract,callback)
scope:insert(
Binding(
Parse(abstract),
callback
)
)
end
function Run(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 Run(_s,expanded,_c,"means: "..tostring(expanded))
end)
end]]
--[[Bind(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}),
Run(_s,_e,_c,string.format("expands: %s:",name))
))
end
return Run(scope,e,c,"evaluate: result")
end)
end
Bind(ns,"(abstract) becomes (expression)",Bevaluates)
Bind(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,Run())
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 = Run(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 = Run(s,e.scope,c,"do: scope")
else
scope = Scope()
scope.parent = s
end
return doAll(s,e.expression,c)
end
Bind(ns,"do (expression) in (scope)",Bdo)
Bind(ns,"do (expression)",Bdo)
Bind(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
Bind(ns,"a new list",function(s,e,c)
return List()
end)
Bind(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 = Run(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)
Bind(ns,"add (item) to (list)",function(s,e,c)
local item = Run(s,e.list,c,"")
local list = Run(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 = {}
Bind(ns,"a new table",function(s,e,c) return {} end)
Bind(ns,"set (index) of (table) to (value)",function(s,e,c)
local index = e.index:text()
if not index then
index = Run(s,e.index,c,"set: index")
end
local tab = Run(s,e.table,c,"set: table")
local val = Run(s,e.value,c,"set: value")
tab[index] = val
end)
Bind(ns,"get (index) of (table)",function(s,e,c)
local index = e.index:text()
if not index then
index = Run(s,e.index,c,"get: index")
end
local tab = Run(s,e.table,c,"get: table")
return tab[index]
end)
local _object = {}
local function Object()
return setmetatable(_object,{})
end
Bind(ns,"a new object",function(s,e,c)
return Object()
end)
Bind(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 = Run(s,e.object,c,"property is: object")
assert_meta(Object)(object,"nel: property is: object: %s")
local value = Run(s,e.value,c,"property is: value")
object[property] = value
end)
Bind(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 = Run(s,e.object,c,"property is: object")
assert_meta(Object)(object,"nel: property is: object: %s")
return object[property]
end)
Bind(ns,"error (text)",function(s,e,c)
error("nel: "..Run(s,e.text,c,"error: message"))
end)
Bind(ns,"this scope",function(s,e,c)
return s
end)
Bind(ns,"a new scope",function(s,e,c)
return Scope()
end)
-- Read a file in the current directly, more for compiling
Bind(ns,"read file (path)",function(s,e,c)
return read(Run(s,e.path,c,"read: path"))
end)
-- Take some text and interpret it into an expression
Bind(ns,"evaluate (text) in (scope)",function(s,e,c)
local scope = Run(s,e.scope,c,"evaluate: scope")
return Run(s,interpret(Run(scope,e.text,c,"include: source")),c,"include: result")
end)
Bind(ns,"text (literal)",function(s,e,c)
return e.literal:text()
end)
Bind(ns,"return (expression)",function(s,e,c)
return Run(s,e.expression,c,"return: result")
end)
Bind(ns,"let (binding) in (scope)",function(s,e,c)
end)
]]
Bind(ns,"print (text)",function(s,e,c)
log(Run(s,e.text,c,"print: text"))
end)
local Hpath = "nel/helper.nel"
local Hchain = Chain("in internal: "..Hpath)
local success,result = Hchain:call(function(chain)
Run(NelliScope,Parse(read(Hpath),Hpath,chain),chain)
end)
--[[ Documentation:
(log (text)) -> Log text to the output stream (print?)
(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