-- Implements the lexer for vstruct format strings.
-- Returns a function which, given a source string, returns a table of lexer
-- operations closed over that source.

-- Copyright (c) 2011 Ben "ToxicFrog" Kelly

local lexis = {}

local function lexeme(name)
	return function(pattern)
		lexis[#lexis+1] = { name=name, pattern="^"..pattern }
	end
end

lexeme (false) 	    "%s+"	-- whitespace
lexeme (false)      "%-%-[^\n]*" -- comments
lexeme "key"        "([%a_][%w_.]*):"
lexeme "name"	    "([-+@<>=])"
lexeme "name"       "([%a_]+)"
lexeme "number"     "([%d.,]+)"
lexeme "{"          "%{"
lexeme "}"          "%}"
lexeme "("          "%("
lexeme ")"          "%)"
--lexeme ":"          "%:"
lexeme "*"          "%*"
lexeme "["          "%["
lexeme "]"          "%]"
lexeme "|"          "%|"

return function(source)
    local orig = source
    local index = 1
    local hadwhitespace = false
           
    local function where()
        return ("character %d ('%s')."):format(index, source:sub(1,4))
    end

    local function find_match()
        for _,lexeme in ipairs(lexis) do
            if source:match(lexeme.pattern) then
                return lexeme,select(2, source:find(lexeme.pattern))
            end
        end
        error (("Lexical error in format string at %s."):format(where()))
    end

    local function eat_whitespace()
        local function aux()
            if #source == 0 then return end
            local match,size = find_match()
            
            if not match.name then
                hadwhitespace = true
                source = source:sub(size+1, -1)
                index = index + size
                return aux()
            end
        end
        hadwhitespace = false
        return aux()
    end

    local function whitespace()
        return hadwhitespace
    end
    
   local function next()
       eat_whitespace()

       if #source == 0 then return nil end

       local lexeme,size,text = find_match()

       source = source:sub(size+1, -1)
       index = index+size

       return { text = text, type = lexeme.name }
   end

   local function peek()
       eat_whitespace()

       if #source == 0 then return nil end

       local lexeme,size,text = find_match()

       return { text = text, type = lexeme.name }
   end

   return {
       next = next;
       peek = peek;
       where = where;
       whitespace = whitespace;
       tokens = function() return next end;
   }
end

