328 lines
9.3 KiB
Lua
328 lines
9.3 KiB
Lua
-- Prototype for nel
|
|
require("nellie.parser")
|
|
|
|
function Bind(scope,abstract,callback)
|
|
for _,expression in pairs(Parse(abstract).items) do
|
|
assert_meta(Expression)(expression)
|
|
scope:insert(Binding(
|
|
expression,
|
|
callback
|
|
))
|
|
end
|
|
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
|
|
function Do(scope,expression,chain,name)
|
|
local latest
|
|
for index,item in pairs(expression.items) do
|
|
latest = Run(scope,item,chain,"do: "..tostring(index))
|
|
end
|
|
return latest
|
|
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,"text (literal)",function(s,e,c) return e.literal:text() end)
|
|
Bind(ns,"let ((named (name)) be (value))",function(s,e,c) end)
|
|
Bind(ns,"new table",function(s,e,c) return {} end)
|
|
]]
|
|
|
|
Bind(ns,"text (text)",function(s,e,c) return e.text:text() end)
|
|
Bind(ns,"(closed)",function(s,e,c) print(e.closed) return Run(s,e.closed,c,"(...)") end)
|
|
Bind(ns,"log (text)",function(s,e,c) log(Run(s,e.text,c,"print: text")) end)
|
|
Bind(ns,"this scope",function(s,e,c) return s end)
|
|
Bind(ns,"new scope",function(s,e,c) return Scope() end) -- TODO: chains?!!?
|
|
Bind(ns,"index (table) with (key)",function(s,e,c) return Run(s,e,c,"") end)
|
|
Bind(ns,[[
|
|
(input) means (output);
|
|
(input) in (input_scope) means (output);
|
|
(input) means do (output) in (output_scope);
|
|
(input) in (input_scope) means (output) in (output_scope)
|
|
]],function(s1,e1,c1) -- A substitution
|
|
local input_scope = s1
|
|
if e1.input_scope then input_scope = Run(s1,e1.input_scope,c1,"means: input scope") end
|
|
local output_scope = s1
|
|
if e1.output_scope then output_scope = Run(s1,e1.output_scope,c1,"means: output scope") end
|
|
Bind(input_scope,e1.input,function(s2,e2,c2)
|
|
return Do(output_scope,e1.output,c2) -- TODO: chains?!
|
|
end)
|
|
end)
|
|
Bind(ns,"do (stuff) in (scope); do (stuff)",function(s,e,c)
|
|
s = s or Scope()
|
|
return Do(s,e.stuff,c)
|
|
end)
|
|
Bind(ns,"new table",function(s,e,c) return {} end)
|
|
|
|
local Hpath = "nellie/helper.nel"
|
|
local Hchain = Chain("in internal: "..Hpath)
|
|
local success,result = Hchain:call(function(chain)
|
|
print(Parse(read(Hpath),Hpath,chain))
|
|
Do(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 |