local chars = {
	whitespace = { -- characters that get dismissed at the start of a line
		[' '] = true,
		['\n'] = true,
		['\t'] = true,
	},
	indent = { -- characters that identify the scope of an object
		[' '] = true,
		['\t'] = true,
	},
	line = { -- characters for ending a line
		['\n'] = true,
		[';'] = true,
	},
	
	string = { -- characters for STARTING a string
		["'"] = true,
		['"'] = true,
		['['] = true,
	},

	code = {
		['`'] = true,
	},

	escape = {
		['\\'] = true,
	},
	
	var = { -- characters able to be used in variable names
		['_'] = true,
		['-'] = true,
	},
	
	digits = { -- digits (that cannot be at the start of variable names)
	},

	eof = {
		[''] = true,
	},

	comment = {
		['-'] = true,
		['/'] = true,
	},

	equals = {
		['='] = true,
		[':'] = true,
	},

	operator = {
		['+'] = true,
		['-'] = true,
		['/'] = true,
		['^'] = true,
		['*'] = true,
		['('] = true,
		[')'] = true,
	}
}

local a = string.byte("a")
local z = string.byte("z")
local A = string.byte("A")
local Z = string.byte("Z")
for c=a,z do
	chars.var[string.char(c)] = true
end
for c=A,Z do
	chars.var[string.char(c)] = true
end

for c=0,9 do
	chars.digits[tostring(c)] = true
end

local function lex(text)
	local char = 1
	local function get(p)
		local tPos = p or char
		return text:sub(tPos,tPos)
	end
	local beginline = true
	local isProperty = true
	local data = {{}}
	local line = data[1]
	local posOffset = 0
	local function newline()
		posOffset = char
		table.insert(data,{})
		line = data[#data]
	end
	local function addData(txt,type)
		if line[#line] and line[#line].type == type then
			line[#line].data = line[#line].data..txt
			line[#line].posLast = line[#line].posLast+#txt
		else
			table.insert(line,{data=txt,posFirst=(char-posOffset),posLast=(char-posOffset)+(#txt-1),type=type})
		end
	end
	while char <= #text do
		local txt = get()
		if chars.whitespace[txt] and txt ~= "\n" then
			addData(txt,"whitespace")
		elseif txt == "\\" then
			addData(txt..text:sub(char+1,char+1),"escape")
			char = char+1
		elseif txt == "[" and get(char+1) == "[" then
			beginline = false
			addData(txt,"string")
			char = char+1
			while char <= #text do
				local c = get()
				if c == "\\" then
					addData(c..get(char+1),"escape")
					char = char+2
				elseif c == "]" and get(char+1) == "]" then
					addData(c,"string")
					if c == "]" then
						addData(get(char+1),"string")
						char = char+1
					end
					break
				else
					if c == "\n" then
						newline()
					else
						addData(c,"string")
					end
					char = char+1
				end
			end
		elseif chars.string[txt] then
			beginline = false
			local bchar = txt
			local echar = bchar
			addData(txt,"string")
			char = char+1
			while char <= #text do
				local c = text:sub(char,char)
				if c == "\\" then
					addData(c..get(char+1),"escape")
					char = char+1
					if chars.digits[get()] then
						char = char+1
						for i=1,2 do
							if chars.digits[get()] then
								addData(get(),"escape")
								char = char+1
							else
								break
							end
						end
					else
						char = char+1
					end
				elseif c == echar then
					addData(c,"string")
					break
				else
					if c == "\n" then
						newline()
					else
						addData(c,"string")
					end
					char = char+1
				end
			end
			if not isProperty then
				isProperty = true
			end
		elseif (chars.comment[txt] and chars.comment[get(char+1)] and txt == get(char+1)) or (txt == "/" and get(char+1) == "*") then
			addData(txt..get(char+1),"comment")
			local b1,b2 = txt,get(char+1)
			char = char+2
			local c1,c2 = get(),get(char+1)
			local inline = false
			if txt == "-" and c1 == "[" and c2 == "[" then
				addData(c1..c2,"comment")
				inline = true
				char = char+2
			elseif b1 == "/" and b2 == "*" then
				inline = true
			end
			local sLine = cLine
			while true do
				local ch = get()
				if ch == "" then
					break
				elseif inline and ((c1 == "[" and ch == "]" and get(char+1) == "]") or (b1 == "/" and ch == "*" and get(char+1) == "/")) then
					addData(ch..get(char+1),"comment")
					char = char+1
					break
				elseif chars.line[ch] and not inline then
					if ch == "\n" then
						newline()
					else
						addData(ch,"line")
					end
					break
				else
					if ch == "\n" then
						newline()
					else
						addData(ch,"comment")
					end
					char = char+1
				end
			end
		elseif chars.code[txt] then
			beginline = false
			local echar = txt
			addData(txt,"code")
			char = char+1
			while true do
				local c = text:sub(char,char)
				if c == "" then
					break
				elseif c == echar then
					addData(c,"code")
					break
				else
					if c == "\n" then
						newline()
					else
						addData(c,"code")
					end
					char = char+1
				end
			end
		elseif chars.var[txt] or chars.digits[txt] then
			if beginline then
				addData(txt,"object")
				if chars.var[get(char+1)] then
					char = char+1
					while true do
						local ch = get()
						if ch == "" then
							break
						elseif chars.var[ch] then
							addData(ch,"object")
							char = char+1
						else
							char = char-1
							break
						end
					end
				end
				beginline = false
			else
				local pType
				if isProperty then
					pType = "property"
				else
					if chars.digits[txt] or txt == "-" then
						pType = "number"
					else
						pType = "value"
					end
				end
				addData(txt,pType)
				if chars.var[get(char+1)] or chars.digits[get(char+1)] or get(char+1) == "%" then
					char = char+1
					while true do
						local ch = get()
						if ch == "" then
							break
						elseif chars.operator[ch] and pType == "number" then
							addData(ch,"operator")
							char = char+1
						elseif chars.var[ch] or chars.digits[ch] or ch == "%" then
							addData(ch,pType)
							char = char+1
						else
							char = char-1
							break
						end
					end
				end
				if not isProperty then
					isProperty = true
				else
					local p = #line-1
					if line[p] and line[p].type == "whitespace" then p = p-1 end
					if line[p] and line[p].type == "property" and line[p-2] and line[p-2].type == "object" then
						line[p].type = "value"
					end
				end
			end
		elseif chars.operator[txt] then
			addData(txt,"operator")
		elseif chars.equals[txt] then
			addData(txt,"equals")
			isProperty = false
		elseif txt ~= "\n" then
			addData(txt,"unidentified")
		end
		if txt == "\n" then
			newline()
			beginline = true
			isProperty = true
		end
		--write(txt)
		char = char+1
	end
	return data
end

return lex