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(Expression)(expression,"Arg 'expression' #2: %s") assert_meta(Chain)(chain,"Arg 'chain' #3: %s") local chain = Chain(expression:link(name)):from(chain) return scope:run(expression,chain,scope) end NelliScope = Scope() local ns = NelliScope 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) -- 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 Bmeans(s,e,c) local scope if e.scope then scope = R(s,e.scope,c,"means: using: scope: %s") end 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(scope or _s,expanded,_c,"means: "..tostring(expanded)) end) end B(ns,"(abstract) means (expression) using (scope)",Bmeans) 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 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(scope,item,c,"do: line") end return res end B(ns,"do (expressions)",Bdo) B(ns,"do (expression) in (scope)",function(s,e,c) local scope = R(s,e.scope,c,"do: scope") assert_meta(Scope)(scope,"nel: (do: scope): %s") R(scope,e.expression,c,"do: expression") end) B(ns,"error (text)",function(s,e,c) error("nel: "..R(s,e.text,c,"error: message")) end) B(ns,"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(ns,"true",function(s,e,c) return true end) B(ns,"false",function(s,e,c) return false end) B(ns,"constant (constant) = (expression)",function(s,e,c) local value = R(s,e.expression,c,"constant: expression") e.constant:text() s:insert( Binding(e.constant,function(s,e,c) return value end) ) end) B(ns,"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(ns,"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(ns,"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(ns,"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(ns,"new table",function(s,e,c) return {} end) B(ns,"(index) of (table)",function(s,e,c) return (R(s,e.table,"of: index"))[R(s,e.index,c,"of: table")] end) B(ns,"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(ns,"remove (index) of (table)",function(s,e,c) (R(s,e.table,"remove: table"))[R(s,e.index,c,"remove: index")] = nil end) B(ns,"nothing",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(ns,"here",function(s,e,c) return s end) B(ns,"return (object)",function(s,e,c) return R(s,e.object,c,"return: object") end) B(ns,"this scope",function(s,e,c) return s end) B(ns,"in scope (scope) (expressions)",function(s,e,c) return R(R(s,e.scope,c,"in scope: scope"),e.scope,c,"in scope: expression") end) local function Bdef(s,e,c) if e.scope then s = R(s,e.scope,c,"define: scope") end 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,string.format( "define: param: \"%s\"", identifier )) ) ) end return R(scope,e.expression,_c,string.format( "define: expression:\n\t\t%s\n\t", table.concat(c:flatten(),"\n\t") )) end)) end B(ns,"define (abstract) as (expression)",Bdef) B(ns,"define (abstract) as (expression) in (scope)",Bdef) -- Create a constant binding B(ns,"(abstract) is (expression)",function(s,e,c) local result = R(s,e.expression,c,"be: expression") return Binding(e.abstract,result) end) -- Insert a binding into the current scope B(ns,"here (bindings)",function(s,e,c) for meaning in e.binding:iterate() do for sub in e.binding:iterate() do local t = type_of(sub) if t ~= Expression then print(t,getmetatable(t)) error(string.format( "nel: (here: bindings): %s%s\n\t%s", "Expected subexpressions only got: ", tostring(sub), "Note: Are you using '{...}' style brackets?" )) else local bind = R(s,sub,c,"here: binding") assert_meta(Binding)(bind,"nel: (here: binding): %s") s:insert(bind) end end end end) -- Insert a binding into a scope B(ns,"in (scope) let (binding)",function(s,e,c) assert_meta(Binding)(e.binding,"nel: (here: binding): %s") assert_meta(Scope)(e.scope,"nel: (here: scope): %s") R(s,e.scope,c,"let: scope"):insert(R(s,e.binding,c,"let: binding")) end) B(ns,[[ define (abstract) as (expression) in (scope) using (parameter_scope) ]],Bdef) local List = class("List") function List:new(...) self.items = {...} end B(ns,"new list",function(s,e,c) return List() end) B(ns,"insert (item) into (list)",function(s,e,c) table.insert(R(s,e.list).items,R(s,e.item),c,c) end) B(ns,"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(ns,"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(ns,"remove from (haystack) at (index)",function(s,e,c) table.remove(R(s,e.list),R(s,e.index),c,c) end) -- Create a scope parented to the current scope as an object B(ns,"new scope here",function(s,e,c) local scope = Scope scope.parent = s return scope end) -- Create a scope with nothing in it as an object (weird) B(ns,"new scope",function(s,e,c) return Scope() end) -- Read a file in the current directly, more for compiling B(ns,"read (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,"include (text)",function(s,e,c) return R(s,interpret(R(s,e.text,c,"include: source")),c,"include: result") end) interpret([[ here { (here ('name') is ('value')) means (here { ('name') is ('value') }); (here ('abstract') means ('expression')) means (here { ('abstract') means ('expression') }); (here ('abstract') means ('expression') using ('scope')) means (here { ('abstract') means ('expression') } using ('scope')) } ]]) return ns