require("interpret") local nelli_scope = Scope() local function B(abstract,callback) nelli_scope:insert( Binding( interpret(abstract), callback ) ) end local function R(scope,expression,chain,name) name = name or "?" assert_meta(Chain)(chain,"Arg 'chain' #1: %s") local chain = Chain(expression:link(name)):from(chain) return scope:run(expression,chain) end B("print (text)",function(s,e,c) log(R(s,e.text,c,"print: text")) end) B("text (literal)",function(s,e,c) return e.literal:text() end) B("do (expressions)",function(s,e,c) --assert_meta(Chain)(chain,"Arg 'chain' #3: %s") scope = Scope() scope.parent = s local res for item in iterate(e.expressions.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) B("error (text)",function(s,e,c) error("nel: "..R(s,e.text,c,"error: message")) end) B("if (predicate) then (expression)",function(s,e,c) if R(s,e.predicate,c,"if: predicate") then return R(s,e.expression,c,"if: result") end end) B("true",function(s,e,c) return true end) B("false",function(s,e,c) return false end) B("(constant) = (expression)",function(s,e,c) local value = R(s,e.expression,c,"constant: expression") s:insert( Binding(e.constant:text(),function(s,e,c) return value end) ) end) B("format (string) with (terms)",function(s,e,c) local items = {} for expression in iterate(e.terms.items) do table.insert(items,R(s,expression),c,"format: term") end return string.format( R(s,e.string,c,"format: string"), table.unpack(items) ) end) B("while (predicate) (expression)",function(s,e,c) while R(s,e.predicate,c,"while: predicate") do R(s,e.expression,c,"while: loop") end end) B("repeat (expression) while (predicate)",function(s,e,c) while true do R(s,e.expression,c,"repeat: expression") if not R(s,e.predicate,c,"repeat: predicate") then break end end end) B("repeat (expression) until (predicate)",function(s,e,c) while true do R(s,e.expression,c,"repeat: expression") if R(s,e.predicate,c,"repeat: predicate") then break end end end) B("new table",function(s,e,c) return {} end) B("(index) of (table)",function(s,e,c) return (R(s,e.table,"of: index"))[R(s,e.index,c,"of: table")] end) B("set (index) of (table) to (item)",function(s,e,c) R(s,e.table,c,"of-to: index")[R(s,e.index,c,"of-to: table")] = s:run(e.item,c,"of-to: item") end) B("remove (index) of (table)",function(s,e,c) (R(s,e.table,"remove: table"))[R(s,e.index,c,"remove: index")] = nil end) B("nil",function(s,e,c) return nil end) --[[ Note A macro executes in the same scope of the callee. A defintion executes in the scope it was defined. I would argue that macros are more unpredictable! However, they allow for some very clean code. --]] B("macro (macro) expands to (expression)",function(s,e,c) s:insert(Binding(e.abstract,function(_s,_e,_c) local scope = Scope() scope.parent = _s for identifier,expression in pairs(_e) do scope:insert(Binding(Expression({identifier}),R(_s,expression,_c))) end return R(scope,e.expression,_c,"macro: expression") end)) end) B("define (abstract) as (expression)",function(s,e,c) s:insert(Binding(e.abstract,function(_s,_e,_c) local scope = Scope() scope.parent = s for identifier,expression in pairs(_e) do scope:insert(Binding(Expression({identifier}),R(_s,expression,_c))) end return R(scope,e.expression,_c,"define: expression") end)) end) local List = class("List") function List:new(...) self.items = {...} end B("new list",function(s,e,c) return List() end) B("insert (item) into (list)",function(s,e,c) table.insert(R(s,e.list).items,R(s,e.item),c,c) end) B("insert (item) into (list) at (index)",function(s,e,c) table.insert(s:evaulate(e.list).items,R(s,e.index),R(s,item),c,c) end) B("remove (needle) from (haystack)",function(s,e,c) local tab = R(s,e.haystack,c) table.remove(tab,table.find(tab,R(s,e.needle)),c) end) B("remove from (haystack) at (index)",function(s,e,c) table.remove(R(s,e.list),R(s,e.index),c,c) end) local Variable = class("Variable") function Variable:new() end function Variable:set(item) self.data = item end function Variable:get() return self.data end B("let (variable)",function(s,e,c) local variable = Variable() s:insert( Binding(e.variable,function(s,e,c) return variable end) ) return variable end) B("set (variable) to (expression)",function(s,e,c) local var = R(s,e.variable,c,"set: identifier") local value = R(s,e.expression,c,"set: expression") assert_meta(Variable)(var) var:set(value) end) B("get (variable)",function(s,e,c) local var = R(s,e.variable,c) return var:get(value) end) local args = {...} local function main() -- Take arguments as neli files to read and interpret local last local root = Chain( string.format( "in cmd 'nel {%s}'", table.concat(args,";") ) ) local success,result = xpcall(function() for _,parameter in pairs(args) do last = parameter local chain = Chain( string.format("in 'call: root' @'%s'",parameter) ):from(root) local root = interpret( read(parameter), parameter, chain ) assert_meta(Chain)(chain,"?") nelli_scope:run(root,chain) end end,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) if not success then log(string.format( "nel: %s\nNEL traceback:\n\t%s", result, table.concat(root:flatten(),"\n\t") )) end end main()