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 function Expression:empty() return #self.items == 0 end -- insert a word or sub expression into this one 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({ table = function(expr) insert_expression(expr) end, string = function(word) insert_word(word) end })(type(item),item) end end 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 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 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 Consumer = class("Consumer") function Consumer:new(content) self.range = Range(Reader(content)) end function Consumer:remaining() return self.range:text() 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 -- 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 -- Pass back consumed result to return ffunc(fexcess,self.range.reader:match(fpattern,findex)) end Binding = class("Binding") -- Construct from a neli string to infer a binding -- note that you must wrap this definition in "()"! function Binding:new(expression,callback) assert_meta(Expression)(expression,"Arg 'expression' #1: %s") assert_type("function")(callback,"Arg 'callback' #2: %s") self.template = expression:abstract(true) self.callback = callback end function Binding:call(scope,binds) return self.callback(scope,binds) end function Binding:check(expression) assert_meta(Expression)(expression,"Arg 'expression' #1: %s") local candid = expression:abstract() 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 data[template_item:text()] = 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 -- 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:insert(binding) assert_meta(binding,Binding) table.insert(self.bindings,binding) end function Scope:evaluate(expression) assert_meta(Expression)(expression,"Arg 'expression' #1: %s") local parent = self.parent -- Could be multiple bindings so make a table local binds = {} for binding in iterate(self.bindings) do local bind = binding:check(expression) -- Insert into table if bind then binds[binding] = bind end end local binding,bind = next(binds) -- Check for multiple if not bind then if parent then parent:evaluate(expression) else error( string.format( "No binding for '%s' in transitive scope.", tostring(expression) ),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( "Ambiguous bindings in scope for '%s': %s", tostring(expression:abstract()), table.concat(candidates) ) ,2) else return binding:call(self,bind) end end return _G