-- Prototype for nel require("nellie.parser") function Bind(scope,abstract,callback) for _,expression in pairs(Parse(abstract)) 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,"(closed)",function(s,e,c) 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 do (output); (input) in (input_scope) means do (output); (input) means do (output) in (output_scope); (input) in (input_scope) means do (output) in (output_scope) ]],function(s,e,c1) -- A substitution local input_scope = s if e.input_scope then input_scope = Run(s,e.input_scope,c1,"means: input scope") end local output_scope = s if e.output_scope then output_scope = Run(s,e.output_scope,c2,"means: output scope") end Bind(e.input_scope,e.input,function(s,e,c2) return Do(e.output_scope,e.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) 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 () become whatever (result) will become in place (expression) --]] return ns